Observers
Observers are decoupled from the events they observe. This is because we're not always observing events! We may be observing a route like '/:id' or Route[':id'] or a status code like Status[404], and we can trigger events for these objects too. Think of events as the building blocks/structure, and observers as the wiring/glue that connects them together.
Nodes include observers by default, but you can include them into any class:
class MySubscriber
include Observers
end
Observing
Subscriber to Publisher
class MySubscriber < LowNode
observe MyPublisher
def self.handle
# This class method will be called upon trigger.
end
end
Publisher to Subscriber
Add observers from the object being observed with:
class MyPublisher < LowNode
observers << MySubscriber
end
Tip
Reference a publisher other than self with:
observers(OtherPublisher) << my_observer
Ordering
Observers are ordered, called in the order that they are defined but can be reordered via an array-like interface. You can add observers on either side; from the object being observed or the observer. When ordering is important, add observers from the observed side.
Tip
Observers should only be re-ordered once per observee so that you don't lose track.
Event Observers
MyEvent.define do |observers|
observers # Access existing observers array.
observers << MyObserver # Add an observer at the end.
observers = [MyObserver, *observers] # Add an observer at the start.
end
Object Observers [CANDIDATE]
For observing objects that aren't Event classes such as routes you can do:
Observers['/:user_id' => :get].define |observers|
observerse << my_observer
end
Actions
Note
Events define their own actions and observers CANNOT change the action. Events retain control over which actions are called. This allows for a predictable one-directional flow between events. Complex workflows can be reasoned about more easily without worrying that an observer somewhere is overriding every action on an event and executing unrelated behaviour and output.
Call the my_action method on all observers of MyPublisher with:
class MyPublisher < LowNode
trigger action: :my_action
end
Action Types
trigger- Calls all observers. Returns the last non-nil value.take- Calls all observers up until the first non-nil value. Returns the first non-nil value.
With Keys
Call actions on all observers of a differeent object/key with a key: keyword argument:
trigger key: OtherPublisher, action: :my_action
trigger key: OtherPublisher, action: :my_action
Action Accepting [UNRELEASED]
In this example we're observing the route for all types of HTTP requests, but only accepting the QUERY HTTP verb as our action/method:
observe Route['/:question'] => :query
Action Forwarding [CANDIDATE]
You can redirect an action to call a method of a different name:
observe Route[QUERY => '/:question'] => { query: :answer }
This forwards the query: action to the :answer action/method.
Events
Trigger events on observers with the event keyword argument:
class MyPublisher < LowNode
trigger event: MyEvent.new(my_data)
end
Events define their own actions, or you can override them when triggering:
class MyPublisher < LowNode
trigger event: MyEvent.new(my_data), action: :my_action
end
ℹ️ All observers to MyPublisher will have their my_action(event:) method called with the event passed in as a keyword argument.
With Keys
Call actions on all observers of a differeent object/key with a key: keyword argument:
trigger key: OtherPublisher, action: :my_action, event: MyEvent.new(event_data)
Ordered Actions
Events can define multiple actions and control the order in which they're executed.
Inside your event subclass in super(key: route.path, action:) supply action: with an array of actions:
Event.triggerwill trigger every observer and every action on each observer in the order they're definedEvent.takewill return the first observer with the first action that returns a non-nil value