Record handles (ref)
One rule makes the whole surface guessable:
> bs.records is a collection. bs.records.ref(42) is a record handle.
> A record's relation is again a collection.
What ref is
ref(id) returns a lightweight handle to one record. It is lazy — it
makes no API call until you ask for something. You can also pass a model you
already have, which seeds the handle's cache.
c = bs.records.ref(42) # 0 API calls — just a reference
c = bs.records(42) # Python sugar for .ref(42)
rec = c.get() # 1 call — fetch + cache
c.field_values() # 0 calls if cached, else 1
const c = bs.records.ref(42); // .ref() everywhere (PHP/TS/Go); Python also bs.records(42)
$c = $bs->records->ref(42);
c := bs.Records.Ref(42) // or Ref(existingModel)
What a handle can do
| Method | Cost | Notes |
|---|---|---|
get() |
1 | fetch and cache the record |
update(data) |
1 | update and re-cache |
delete() |
1 | delete by id |
field_values() |
0 cached / 1 | field values keyed by field name |
expand([...]) |
1 per relation | eager-load related records (phase 3) |
<belongsTo>() e.g. c.space() |
1 (0 if cached) | follow a foreign key to the related record |
<hasMany>() e.g. c.labels() |
— | a relation-collection (see relations-expand.md) |
Why a handle and not of()
The earlier of(entity) navigator required you to fetch the model first and
read backwards. ref reads forward ("record 42 → its labels"),
works from a bare id without a fetch, and groups everything about one record
under one autocomplete. of() remains as a @deprecated alias during the
transition.
Models stay pure data — no hidden I/O on a model object. The handle holds the cache and every method documents its API-call cost.