observer

package module
v0.0.0-...-cd0b681 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: BSD-3-Clause Imports: 0 Imported by: 0

README

go-ruby-observer/observer

go-ruby-observer/observer

Go Reference CI

A pure-Go (CGO=0) implementation of the core state behind Ruby's Observable mixin — the observer registry and the changed flag — faithful to MRI Ruby's lib/observer.rb and verified against the reference interpreter (Ruby 4.0.5) with ruby -robserver.

It is part of the go-ruby-* family of standalone front-end/runtime components (alongside go-ruby-parser, go-ruby-regexp, go-ruby-marshal and go-ruby-erb) that go-embedded-ruby builds on. It has no dependency on any interpreter: it owns the registry and answers whether and in what order observers should be notified, while the actual method dispatch onto each observer — update and friends — stays in the embedding interpreter (rbgo).

What it owns vs. what rbgo binds

A Registry backs one object that includes Observable:

MRI Observable this package
add_observer(obj, func=:update) Registry.AddObserver
delete_observer(obj) Registry.DeleteObserver
delete_observers Registry.DeleteObservers
count_observers Registry.CountObservers
changed(state=true) Registry.Changed
changed? Registry.ChangedQ
notify_observers(*args) Registry.NotifyObservers

NotifyObservers returns the ordered (observer, method, args) tuples to call; rbgo performs the dispatch (invoking each observer's method through the interpreter). Responsiveness (respond_to?) is delegated to a caller-supplied callback so the package stays interpreter-agnostic.

MRI-faithful semantics

  • Insertion order. Observers notify in the order they were added (MRI's Hash-keyed store). Re-adding an existing observer updates its method and keeps its position.
  • add_observer of a non-responding method raises. When the supplied respond_to? callback reports false, AddObserver returns *NotRespondingError with MRI's verbatim message observer does not respond to `update' (rbgo turns it into a NoMethodError).
  • Changed-flag lifecycle. changed? starts false; Changed(true) sets it, Changed(false) clears it.
  • Notify decision/reset. NotifyObservers is a no-op returning ok == false when changed? is false; when changed it returns the observers to call and then resets changed? to false.

Usage

import "github.com/go-ruby-observer/observer"

var r observer.Registry

// add_observer(w)   /   add_observer(w2, :special)
_ = r.AddObserver(w, observer.DefaultFunc, respondTo)
_ = r.AddObserver(w2, "special", respondTo)

r.Changed(true) // changed
entries, args, ok := r.NotifyObservers("event")
if ok {
    for _, e := range entries {
        // rbgo: invoke e.Func on e.Observer with args
        dispatch(e.Observer, e.Func, args)
    }
}
// r.ChangedQ() == false now

License

BSD-3-Clause.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package observer is a pure-Go (CGO=0) implementation of the core state behind Ruby's Observable mixin — the observer registry and the "changed" flag — as specified by MRI Ruby's lib/observer.rb (verified against the reference interpreter, Ruby 4.0.5).

A Registry backs one object that includes Observable. It owns the ordered set of observers (insertion order, as MRI's Hash-keyed store), the changed-flag lifecycle, and the notify decision/reset. It deliberately does not invoke observers: NotifyObservers returns the ordered (observer, method, args) tuples to call, and the actual method dispatch onto each observer stays in the caller (in go-embedded-ruby, the interpreter). Likewise responsiveness (Ruby's respond_to?) is delegated to a caller-supplied callback, so this package has no dependency on any interpreter.

The MRI mapping is:

add_observer(obj, func=:update)  ->  Registry.AddObserver
delete_observer(obj)             ->  Registry.DeleteObserver
delete_observers                 ->  Registry.DeleteObservers
count_observers                  ->  Registry.CountObservers
changed(state=true)              ->  Registry.Changed
changed?                         ->  Registry.ChangedQ
notify_observers(*args)          ->  Registry.NotifyObservers

add_observer raises NoMethodError when the observer does not respond to the method (reproduced as *NotRespondingError); notify_observers is a no-op returning nil when changed? is false, and otherwise notifies each observer in insertion order and then resets changed? to false.

Index

Constants

View Source
const DefaultFunc = "update"

DefaultFunc is the method name Observable#add_observer uses when the caller does not pass an explicit one, matching MRI's default (`:update`).

Variables

This section is empty.

Functions

This section is empty.

Types

type Entry

type Entry struct {
	// Observer is the registered observer object.
	Observer Observer
	// Func is the name of the method to call on Observer (the value passed to
	// AddObserver, or DefaultFunc).
	Func string
}

Entry is a registered (observer, method) pair as returned by Registry.NotifyObservers. The caller (rbgo) performs the actual dispatch by invoking Func on Observer with the notification arguments.

type NotRespondingError

type NotRespondingError struct {
	// Func is the method name the observer failed to respond to.
	Func string
}

NotRespondingError is returned by Registry.AddObserver when the observer does not respond to the requested method. It mirrors the NoMethodError MRI's Observable#add_observer raises; rbgo translates it back into a Ruby NoMethodError. Its Error string matches MRI's message verbatim:

observer does not respond to `update'

func (*NotRespondingError) Error

func (e *NotRespondingError) Error() string

type Observer

type Observer = any

Observer is one registered observer together with the name of the method to invoke on it. Observer is an opaque interface value supplied by the caller (in go-embedded-ruby it is the underlying Ruby object); this package never inspects it beyond identity, so it is used as a map key and must therefore be comparable.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the state backing one object that includes Ruby's Observable mixin: the ordered set of observers and the "changed" flag. A zero Registry is ready to use and has no observers with changed? == false.

Registry owns the observer set, insertion ordering, the changed-flag lifecycle, and the notify decision/reset; it does not invoke observers. The method dispatch onto each observer stays in the caller (rbgo), which acts on the Entry list [NotifyObservers] returns.

Registry is not safe for concurrent use; callers must synchronise, matching MRI's Observable (which is likewise not thread-safe).

func (*Registry) AddObserver

func (r *Registry) AddObserver(observer Observer, fn string, respondTo RespondTo) error

AddObserver registers observer, recording func name as the method to invoke on notification. fn is the method name (use DefaultFunc for MRI's default of :update). respondTo, when non-nil, is consulted to verify observer responds to fn; if it does not, observer is not registered and a *NotRespondingError is returned, mirroring MRI's NoMethodError. A nil respondTo skips the check (the caller is then responsible for responsiveness).

Re-adding an already-registered observer updates its method name and leaves its position in the order unchanged, matching MRI's Hash-keyed store.

func (*Registry) Changed

func (r *Registry) Changed(state bool)

Changed sets the changed flag to state, matching MRI's Observable#changed (whose argument defaults to true). Pass true to mark the state changed, false to clear it.

func (*Registry) ChangedQ

func (r *Registry) ChangedQ() bool

ChangedQ reports whether the changed flag is set, matching MRI's Observable#changed?.

func (*Registry) CountObservers

func (r *Registry) CountObservers() int

CountObservers returns the number of registered observers.

func (*Registry) DeleteObserver

func (r *Registry) DeleteObserver(observer Observer)

DeleteObserver removes observer from the set. Removing an observer that is not registered is a no-op, matching MRI's Hash#delete.

func (*Registry) DeleteObservers

func (r *Registry) DeleteObservers()

DeleteObservers removes every observer from the set.

func (*Registry) NotifyObservers

func (r *Registry) NotifyObservers(args ...any) (entries []Entry, notifyArgs []any, ok bool)

NotifyObservers decides whether observers should be notified, following MRI's Observable#notify_observers exactly:

  • When changed? is false it is a no-op: it returns a nil Entry list and leaves the changed flag false. (ok reports false in this case.)
  • When changed? is true it returns the registered observers in insertion order, each paired with its method name, then clears the changed flag. (ok reports true.)

args are the notification arguments; they are not used by this package and are returned to the caller unchanged so it can forward them to each observer's method. The caller (rbgo) performs the dispatch — it invokes each Entry.Func on each Entry.Observer with args — which is why the args are echoed here rather than acted upon.

ok mirrors MRI's truthiness: MRI returns nil when not changed and the result of changed(false) (i.e. false) when it did notify; callers that need the notify decision should use ok rather than len(entries).

type RespondTo

type RespondTo func(observer Observer, name string) bool

RespondTo reports whether observer responds to the method named name. The caller supplies this because responsiveness is an interpreter concept (Ruby's respond_to?); this package is interpreter-agnostic. AddObserver consults it to reproduce MRI's behaviour of raising NoMethodError for an observer that does not respond to the requested method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL