-
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 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
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 because 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.
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, components and promises is largely automatic.
- A store is a Ruby class and you are encouraged to use good data abstraction to hide the internal representation of your store.
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.
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 methods that return promises that will resolve in the future. The one reasonable exception to this is periodic events like the WorldClock uses.
This may not be true... Probably the only consideration is keeping the store simple.