-
Notifications
You must be signed in to change notification settings - Fork 18
Store Anatomy
Anything but a completely static web page will have dynamic states that change because of user inputs, the passage of time, or other external events.
Stores are Ruby classes that keep the dynamic parts of the state in special state variables
For example here is Store that keeps track of time at a given location:
class WorldClock < HyperStore
# Keep track of the time at multiple locations
attr_reader :name
attr_reader :lattitude
attr_reader :longitude
attr_reader :time_zone_offset
def current_time
WorldClock.gmt+time_zone_offset
end
def WorldClock.all
state.all || []
end
def initialize(name, lattitude, longitude, time_zone_offset)
@name, @lattitude, @longitude, @time_zone_offset =
[name, lattitude, longitude, time_zone_offset]
end
def WorldClock.gmt
unless state.gmt
every(1) { state.gmt! = Time.now.gmt }
state.gmt! = Time.now
end
state.gmt
end
def WorldClock.new(*args)
super.tap do |new_clock|
state.all! = [] unless state.all
state.all! << new_clock
end
end
end
Now we can create a clock and post the time to the console every minute like this:
new_york = WorldClock.new('New York', 40.7128, -74.0059, 5.hours)
every(1.minute) { puts new_york.current_time }
But becuase it is a Reactive Store
we can also say this:
# assume we have a div with id='new-york' some place in our code
Element['div#new-york'].render do
"The time in #{new_york.name} is #{new_york.current_time}"
end
This will automatically rerender the contents of the 'new-york' DIV whenever the store changes!
Notice that the current_time
method uses the gmt
method, where we update and access a state variable named state.gmt
. State variables work the same as Ruby instance variables like @name
, except that when they change, all the parts of the display that are depending on the current value will be updated.
So when we first render our 'new-york' DIV, current_time
calls gmt
, which reads the value of state.gmt
. Our store now "knows" that when state.gmt
changes that DIV will have to be re-rendered.
To alert the reader of the code that we are changing the state, the name of the state variable has the exclamation mark (!) appended to it when it is changed or modified.
We have a second state variable called all
that keeps the list of all clocks created. We will use this in another example.
If you have heard of or used Flux, then this is the same idea but extended to keep things easy.
- A store can be a singleton class (as in Flux)
- or it can have many instances (like our example above.)
- The wiring up between stores, actions, dispatchers, and components is automatic.
Stores are Ruby Classes, so you may be tempted to add more intelligence than needed to the Store. Hyperloop suggests (but does not force) you keep the logic in a Store to the minimum needed to keep the data structures intact.
In the WordClock Store we delegate the computation of the name, latitude, longitude and time zone, upwards, to the caller, and avoid the temptation to automatically figure this all out based on say the name. Elsewhere we will see that Operations are the best place to put that logic.
Although we make an exception in our WorldClock example, in general Hyperloop would recommend that you keep all Store code synchronous. This mainly means don't try to make HTTP requests or call other functions that return promises that will resolve in the future. In the section on "Operations" we will return to this example and remove the call to every
to demonstrate the advantage of this approach (even in a small case like this.)