mvvm¶
The pure-Go, Ruby-runtime-independent core of the Ruby mvvm gem: the
data-binding layer of the go-widgets ecosystem — an Observable property,
a Command action and an ObservableList collection. It is a thin
adapter over the dependency-free primitives of
go-widgets/mvvm.
CGO-free, builds and tests identically on amd64, arm64, riscv64,
loong64, ppc64le, s390x. 100 % statement coverage, race-clean.
The callback seam (id + drain)¶
A hosted Go library cannot synchronously call a Ruby block, so this package
does not try to. Instead every notification — an observable change, a
command execution, a collection change — is a registered callback id
plus a queued, Ruby-shaped event Hash. The rbgo binding polls the queue
with drain_events once per UI tick and dispatches each Hash to the Ruby
callback named by its "callback_id":
Ruby block ──registers──▶ callback id ──stored on subscribe/observe/command
▲ │
│ Go mutation queues an event Hash
└────────dispatch──── drain_events ◀───────────┘ (once per UI tick)
subscribe / observe take a callback id and return an integer subscription
id (pass it to unsubscribe / unobserve); drain_events yields the
pending events and empties the queue. Ruby owns the actual blocks.
Module¶
The package-level receiver (the Mvvm module under rbgo):
| Method | Returns |
|---|---|
observable(initial) |
an Observable handle |
command(can_execute_id, execute_id) |
a Command handle |
observable_list(items) |
an ObservableList handle |
drain_events |
Array of event Hashes (drains the queue) |
Observable — a bindable property¶
| Method | Returns |
|---|---|
get |
the current value (scalar, Hash or Array) |
set(value) |
— (queues a changed event when the value differs by content) |
subscribe(callback_id) |
an Int subscription id |
unsubscribe(sub_id) |
— |
Command — a bindable action¶
| Method | Returns |
|---|---|
can_execute? |
Bool |
execute(args) |
— (queues an execute event carrying args when executable) |
set_can_execute(bool) |
— (records executability, fires a change) |
raise_can_execute_changed |
— |
ObservableList — a bindable collection¶
| Method | Returns |
|---|---|
add(v) / insert(i, v) / set(i, v) / remove_at(i) / move(from, to) / clear |
— (each queues a collection-changed event) |
get(i) |
the item, or raises IndexError out of range |
size |
Int |
slice |
a defensive-copy Array |
observe(callback_id) |
an Int observer id |
unobserve(sub_id) |
— |
Event shapes¶
Each drained event is a Hash with a "callback_id", a "kind" and its
payload:
# observable change
{ "callback_id"=>, "kind"=>"changed", "value"=> }
# command
{ "callback_id"=>, "kind"=>"execute", "args"=>[...] }
{ "callback_id"=>, "kind"=>"can_execute_changed" }
# collection change (kind => "collection_changed")
{ "callback_id"=>, "action"=>"insert"|"remove"|"replace"|"move"|"reset",
"index"=>, "to"=>, "count"=>, "items"=>[...] }
Usage from Go¶
m := mvvm.NewModule()
name := m.Observable("")
name.Subscribe("on_name")
name.Set("Ada")
names := m.ObservableList(nil)
names.Observe("on_names")
names.Add("Ada")
save := m.Command("can_save", "do_save")
save.SetCanExecute(true)
save.Execute([]any{"now"})
for _, ev := range m.DrainEvents() {
h := ev.(map[string]any)
fmt.Println(h["callback_id"], h["kind"])
}
// on_name changed
// on_names collection_changed
// can_save can_execute_changed
// do_save execute
Usage from Ruby¶
require "mvvm"
name = Mvvm.observable("")
name.subscribe(:on_name)
names = Mvvm.observable_list([])
names.observe(:on_names)
save = Mvvm.command(:can_save, :do_save)
save.set_can_execute(true)
name.set("Ada")
names.add(name.get)
save.execute([]) # queues {callback_id: :do_save, ...}
Mvvm.drain_events.each do |ev| # => Array<Hash>, once per UI tick
dispatch(ev[:callback_id], ev) # Ruby runs the actual block
end
Methods(recv) lists every snake_case name Call accepts for a handle, and
Call(recv, name, args...) is the uniform dynamic entry point rbgo binds.
The require "mvvm" binding lives in rbgo; it is pending in that repo —
see Getting started in rbgo.