Documentation
¶
Overview ¶
Package mvvm is 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 — shaped so that github.com/go-embedded-ruby/ruby (rbgo) can bind it as `require "mvvm"`.
It is a thin adapter over the dependency-free primitives of github.com/go-widgets/mvvm. It exposes them through Ruby-facing handles (Module, Observable, Command, ObservableList) whose methods take and return Ruby-shaped values: a Hash (map[string]any), an Array ([]any) or a scalar. A single dynamic entry point, Call, dispatches a Ruby-style snake_case method name to the matching handle method and coerces the arguments, which is exactly what an rbgo binding drives from method_missing. Nothing here imports the Ruby runtime, so the package is equally usable as a standalone Go library — a sibling of go-ruby-regexp/regexp, go-ruby-erb/erb and go-ruby-opentype/opentype.
The callback seam ¶
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 DrainEvents once per UI tick and dispatches each Hash to the Ruby callback named by its "callback_id". This "id + drain" pull model is the whole seam: Subscribe / Observe take a callback id and return a subscription id; DrainEvents yields the pending events; Ruby owns the actual blocks.
Handles ¶
- Module is the package-level receiver (the `Mvvm` module under rbgo): it builds Observables, Commands and ObservableLists and owns the event queue they write to (DrainEvents).
- Observable is a bindable property: Get, Set, Subscribe(callback_id) and Unsubscribe(sub_id). Values may be any Ruby scalar, Hash or Array; equality is by content (reflect.DeepEqual), so setting an equal value is a no-op.
- Command is a bindable action: CanExecute, Execute(args), SetCanExecute and RaiseCanExecuteChanged. Firing it queues an execute event for Ruby to run.
- ObservableList is a bindable collection: Add, Insert, RemoveAt, Set, Move, Clear, Get, Size, Slice, Observe(callback_id) and Unobserve(sub_id). Each mutation queues a Ruby-shaped collection-changed event {action:, index:, items:, ...}.
Usage from Go ¶
m := mvvm.NewModule()
name := m.Observable("")
name.Subscribe("on_name") // "on_name" is a Ruby callback id
name.Set("Ada") // queues {callback_id:"on_name", ...}
events := m.DrainEvents() // an Array of Hashes for Ruby to dispatch
Usage from Ruby ¶
Under rbgo, `require "mvvm"` gives an `Mvvm` module whose snake_case methods are these operations, returning Ruby Hashes, Arrays and scalars:
require "mvvm"
name = Mvvm.observable("")
name.subscribe(:on_name)
name.set("Ada")
names = Mvvm.observable_list([])
names.observe(:on_names)
save = Mvvm.command(:can_save, :do_save)
save.set_can_execute(true)
save.execute([]) # queues {callback_id: :do_save, ...}
Mvvm.drain_events.each do |ev| # => Array<Hash>
dispatch(ev[:callback_id], ev) # Ruby runs the actual block
end
The `require "mvvm"` binding lives in rbgo (a thin method_missing shim over Call that maintains the callback-id table and drains); it is pending in that repo.
Example ¶
Example mirrors the README: an Observable notifies through the drain queue, a Command queues an execute event for Ruby to run, and an ObservableList reports a granular collection change — every result a Ruby-shaped value.
package main
import (
"fmt"
"github.com/go-ruby-widgets/mvvm"
)
func main() {
m := mvvm.NewModule()
// A property: subscribe with a Ruby callback id, then change it.
name := m.Observable("")
name.Subscribe("on_name")
name.Set("Ada")
// A collection: observe it, then append.
names := m.ObservableList(nil)
names.Observe("on_names")
names.Add("Ada")
// An action: fire it once it is executable.
save := m.Command("can_save", "do_save")
save.SetCanExecute(true)
save.Execute([]any{"now"})
// Ruby drains the queue each tick and dispatches by "callback_id".
for _, ev := range m.DrainEvents() {
h := ev.(map[string]any)
fmt.Println(h["callback_id"], h["kind"])
}
}
Output: on_name changed on_names collection_changed can_save can_execute_changed do_save execute
Index ¶
- func Call(recv any, method string, args ...any) (any, error)
- func DrainEvents() []any
- func Methods(recv any) []string
- type Command
- type Module
- type Observable
- type ObservableList
- func (l *ObservableList) Add(v any)
- func (l *ObservableList) Clear()
- func (l *ObservableList) Get(i int) (any, error)
- func (l *ObservableList) Insert(i int, v any)
- func (l *ObservableList) Move(from, to int)
- func (l *ObservableList) Observe(callbackID any) int
- func (l *ObservableList) RemoveAt(i int)
- func (l *ObservableList) Set(i int, v any)
- func (l *ObservableList) Size() int
- func (l *ObservableList) Slice() []any
- func (l *ObservableList) Unobserve(subID int)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Call ¶
Call dispatches a Ruby-style snake_case method name to the matching exported method of recv (a *Module, *Observable, *Command or *ObservableList), coercing each Ruby-supplied argument to the Go parameter type. A trailing "?" or "!" (Ruby predicate/bang convention) is ignored. Trailing arguments may be omitted; they default to nil. The result is the method's Ruby-shaped return value (or nil for a method that returns nothing); a trailing error return is unwrapped into Call's own error. This is the single entry point an rbgo binding drives.
func DrainEvents ¶
func DrainEvents() []any
DrainEvents drains the default module's event queue (see Module.DrainEvents).
Types ¶
type Command ¶
type Command struct {
// contains filtered or unexported fields
}
Command is a Ruby-facing handle over a github.com/go-widgets/mvvm Command. Its action and executability are owned by Ruby: firing the command queues an execute event for Ruby to run, and SetCanExecute records the executability Ruby has computed.
func (*Command) CanExecute ¶
CanExecute reports whether the command may run right now.
func (*Command) Execute ¶
Execute fires the command with args (a Ruby Array; nil for none). When CanExecute is true this queues an execute event carrying the args for Ruby to run; when false it is a no-op.
func (*Command) RaiseCanExecuteChanged ¶
func (c *Command) RaiseCanExecuteChanged()
RaiseCanExecuteChanged notifies every can-execute observer without changing the executability — use it when the Ruby predicate's inputs changed.
func (*Command) SetCanExecute ¶
SetCanExecute records the command's executability and notifies every can-execute observer (queueing a can-execute-changed event when the command was built with a canExecuteID).
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is the package-level Ruby receiver: the `Mvvm` module under rbgo. It is a factory for the three data-binding primitives — Observable, Command and ObservableList — and owns the event queue those primitives write to. Because a hosted Go library cannot call back into the Ruby runtime, every notification (an observable change, a command execution, a collection change) is appended to this queue as a Ruby-shaped Hash; the rbgo binding drains it with DrainEvents on each UI tick and dispatches each Hash to the Ruby callback named by its "callback_id". A Module is NOT safe for concurrent use — drive it from the UI goroutine, matching the single-threaded contract of the underlying github.com/go-widgets/mvvm primitives.
func Default ¶
func Default() *Module
Default returns the shared default Module that the Ruby `Mvvm` receiver binds to. Its factory names (Observable, Command, ObservableList) match the handle type names, so the convenience surface is the module itself rather than same-named package functions: use Default().Observable(v), or drive it dynamically with Call(mvvm.Default(), "observable", v).
func NewModule ¶
func NewModule() *Module
NewModule returns a fresh Module with its own, independent event queue. The package-level convenience functions (Observable, Command, ObservableList and DrainEvents) delegate to a shared default Module instead.
func (*Module) Command ¶
Command creates a Command handle. canExecuteID and executeID are opaque Ruby callback identifiers (any value, or nil for none): executeID names the action run when the command fires, canExecuteID names the observer notified whenever executability changes. A new command is executable until SetCanExecute says otherwise.
func (*Module) DrainEvents ¶
DrainEvents returns every event queued since the last drain (an Array of Hashes) and empties the queue. Each Hash carries a "callback_id" naming the Ruby callback to invoke plus a "kind" and its payload. This is the pull half of the callback seam: nothing here calls Ruby, so the rbgo binding polls this once per UI tick and dispatches. When nothing is pending it returns an empty Array.
func (*Module) Observable ¶
func (m *Module) Observable(initial any) *Observable
Observable creates an Observable handle seeded with initial (any Ruby scalar, Hash or Array). Equality uses reflect.DeepEqual so that Hash- and Array-valued observables are compared by content rather than by an identity that would panic under ==; setting a deeply-equal value is a no-op that keeps two-way bindings loop-free.
func (*Module) ObservableList ¶
func (m *Module) ObservableList(items []any) *ObservableList
ObservableList creates an ObservableList handle seeded with items (a Ruby Array; nil for empty).
type Observable ¶
type Observable struct {
// contains filtered or unexported fields
}
Observable is a Ruby-facing handle over a github.com/go-widgets/mvvm Observable[any] plus a table of drain-backed subscriptions.
func (*Observable) Set ¶
func (o *Observable) Set(v any)
Set assigns v and, when it differs (per reflect.DeepEqual) from the current value, queues a change event for every subscriber.
func (*Observable) Subscribe ¶
func (o *Observable) Subscribe(callbackID any) int
Subscribe registers the Ruby callback callbackID and returns an Int subscription id for Unsubscribe. On each change the observable queues a Hash {"callback_id"=>callbackID, "kind"=>"changed", "value"=>newValue} that DrainEvents later yields.
func (*Observable) Unsubscribe ¶
func (o *Observable) Unsubscribe(subID int)
Unsubscribe detaches the subscription created by Subscribe. An unknown id is ignored.
type ObservableList ¶
type ObservableList struct {
// contains filtered or unexported fields
}
ObservableList is a Ruby-facing handle over a github.com/go-widgets/mvvm ObservableList[any] plus a table of drain-backed observers.
func (*ObservableList) Add ¶
func (l *ObservableList) Add(v any)
Add appends v and queues a collection-changed event.
func (*ObservableList) Clear ¶
func (l *ObservableList) Clear()
Clear removes every item and queues a reset collection-changed event.
func (*ObservableList) Get ¶
func (l *ObservableList) Get(i int) (any, error)
Get returns the item at i, or an error (a Ruby IndexError under rbgo) when i is out of range — the fetch-style accessor. It also demonstrates Call's trailing-error unwrapping.
func (*ObservableList) Insert ¶
func (l *ObservableList) Insert(i int, v any)
Insert places v at index i (clamped to [0, size]) and queues a collection-changed event.
func (*ObservableList) Move ¶
func (l *ObservableList) Move(from, to int)
Move relocates the item at from to to and queues a collection-changed event. An out-of-range index, or from == to, is ignored.
func (*ObservableList) Observe ¶
func (l *ObservableList) Observe(callbackID any) int
Observe registers the Ruby callback callbackID and returns an Int observer id for Unobserve. On each mutation the list queues a Ruby-shaped collection-changed Hash — {"callback_id"=>callbackID, "kind"=> "collection_changed", "action"=>..., "index"=>..., "to"=>..., "count"=>..., "items"=>...} — that DrainEvents later yields. "action" is one of "insert", "remove", "replace", "move" or "reset"; "to" is meaningful for "move" and "items" for "insert"/"replace".
func (*ObservableList) RemoveAt ¶
func (l *ObservableList) RemoveAt(i int)
RemoveAt removes the item at i and queues a collection-changed event. An out-of-range index is ignored.
func (*ObservableList) Set ¶
func (l *ObservableList) Set(i int, v any)
Set replaces the item at i and queues a collection-changed event. An out-of-range index is ignored.
func (*ObservableList) Slice ¶
func (l *ObservableList) Slice() []any
Slice returns a defensive copy of the items as a Ruby Array.
func (*ObservableList) Unobserve ¶
func (l *ObservableList) Unobserve(subID int)
Unobserve detaches the observer created by Observe. An unknown id is ignored.