You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<imgalign="left"width="100"height="100"style="margin-right: 20px"src="https://github.com/hyperstack-org/hyperstack/blob/edge/docs/wip.png?raw=true" /> The `Hyperstack::State::Observable` module allows you to build classes that share their state with Hyperstack Components, and have those components update when objects in those classes change state.
3
-
4
-
## This Page Under Construction
5
-
6
-
The `Hyperstack::State::Observable` module allows you to build classes that share their state with Hyperstack Components and have those components update when objects in those classes change state.
7
2
8
3
### Revisiting the Tic Tac Toe Game
9
4
10
5
The easiest way to understand HyperState is by example. If you you did not see the Tic-Tac-Toe example, then **[please review it now](client-dsl/interlude-tic-tac-toe.md)**, as we are going to use this to demonstrate how to use the `Hyperstack::State::Observable` module.
11
6
12
-
In our original TicTacToe implementation the state of the game was stored in the `DisplayGame` component. State was updated by
13
-
"bubbling up" events from lower level components up to `DisplayGame` where the event handers updated the state.
7
+
In our original Tic-Tac-Toe implementation the state of the game was stored in the `DisplayGame` component. State was updated by
8
+
"bubbling up" events from lower level components up to `DisplayGame` where the event hander updated the state.
14
9
15
10
This is a nice simple approach but suffers from two issues:
16
11
+ Each level of lower level components must be responsible for bubbling up the events to the higher component.
@@ -19,11 +14,11 @@ This is a nice simple approach but suffers from two issues:
19
14
As our applications become larger we will want a way to keep each component's interface isolated and not dependent on the overall
20
15
architecture, and to insure good separation of concerns.
21
16
22
-
The `Hyperstack::State::Observable` module allows us to put the game's state into a separate class, which can be accessed from any
23
-
component: No more need to bubble up events, and no more cluttering up our `DisplayGame` component with state management stuff
17
+
The `Hyperstack::State::Observable` module allows us to put the game's state into a separate class which can be accessed from any
18
+
component: No more need to bubble up events, and no more cluttering up our `DisplayGame` component with state management
24
19
and details of the game's data structure.
25
20
26
-
Here is the game state moved out of the `DisplayGame` component into its own class:
21
+
Here is the game state and associated methods moved out of the `DisplayGame` component into its own class:
27
22
28
23
```ruby
29
24
classGame
@@ -73,9 +68,8 @@ class Game
73
68
includeHyperstack::State::Observable
74
69
```
75
70
76
-
Including `Hyperstack::State::Observable` gives us access to a number of methods that allows our class to become
77
-
a *reactive store*, and interact with other stores and components so that when the `Game` state updates so will
78
-
any components that directly or indirectly depend on its state.
71
+
`Game` is now in its own class and includes `Hyperstack::State::Observable`. This adds a number of methods to `Game` that allows our class to become
72
+
a *reactive store*. When `Game` interacts with other stores and components they will be updated as the state of `Game` changes.
79
73
80
74
```ruby
81
75
definitialize
@@ -84,9 +78,9 @@ any components that directly or indirectly depend on its state.
84
78
end
85
79
```
86
80
87
-
In the original implementation we initialized the two state variables `@history` and `@step` in the `before_mount` callback.
88
-
There is no "mounting" of a store, so instead we will initialize our game by creating a new instance in the `DisplayGame` before
89
-
mount callback (see below.)
81
+
In the original implementation we initialized the two state variables `@history` and `@step` in the `before_mount` callback. The same initialization
82
+
is now in the `initialize` method which will be called when a new instance of the game is created. This will still be done in the `DisplayGame`
83
+
`before_mount` callback (see below.)
90
84
91
85
```ruby
92
86
observer :playerdo
@@ -108,8 +102,7 @@ been changed `observe` and `observer` indicate that state has been accessed outs
108
102
attr_reader:history
109
103
```
110
104
111
-
Just as we have `mutate`, `mutator`, and `state_writer`, we have `observe`, `observer`, and `state_reader`. The `state_accessor`
112
-
method just combines the two together.
105
+
Just as we have `mutate`, `mutator`, and `state_writer`, we have `observe`, `observer`, and `state_reader`.
The `DisplayGame``before_mount` callback is still responsible for initializing the game, but it no longer needs to be aware of
180
173
the internals of the game's state. It simply calls `Game.new` and stores the result in the `@game` instance variable. For the rest
181
-
of the component call the appropriate method on `@game`.
174
+
of the component's code we call the appropriate method on `@game`.
182
175
183
176
We will need to pass the entire game to `DisplayBoard` (we will see why shortly) so we will rename it to `DisplayCurrentBoard`.
184
177
@@ -216,19 +209,20 @@ communicate back upwards via events. Instead we communicate through the central
216
209
217
210
Rather than sending params down to lower level components, and having the components bubble up events, we have created a *Flux Loop*.
218
211
The `Game` store holds the state, the top level component reads the state and sends it down to lower level components, those
219
-
components update the state, causing the top level component to re-rerender.
212
+
components update the `Game`state causing the top level component to re-rerender.
220
213
221
214
This structure greatly simplifies the structure and understanding of our components, and keeps each component functionally isolated.
222
215
223
216
Furthermore algorithms such as `current_winner?` now are neatly abstracted out into their own class.
224
217
225
218
### Classes and Instances
226
219
227
-
If we are sure we will only want one Game board, we could define Game with class methods like this:
220
+
If we are sure we will only want one game board, we could define `Game` with class methods like this:
228
221
229
222
```ruby
230
223
classGame
231
224
includeHyperstack::State::Observable
225
+
232
226
class << self
233
227
definitialize
234
228
@history= [[]]
@@ -286,7 +280,6 @@ class DisplayBoard < HyperComponent
286
280
end
287
281
288
282
classDisplayGame < HyperComponent
289
-
before_mount { Game.initialize }
290
283
defmoves
291
284
returnunlessGame.history.length >1
292
285
@@ -316,33 +309,14 @@ class DisplayGame < HyperComponent
316
309
end
317
310
```
318
311
319
-
Note that with this approach we can go back to passing just the current board to `DisplayBoard` as `DisplayBoard` can
320
-
directly access `Game.handle_click!` since there is only one game.
321
-
322
-
### The Boot Broadcast
323
-
324
-
Observable classes can also receive information from *broadcasts*. Hyperstack comes with one predefined broadcast: `Hyperstack::Application::Boot` which is sent when the application has finished loading but before the first component is mounted.
312
+
Now instead of creating an instance and passing it around we
313
+
call the class level methods on `Game` throughout.
325
314
326
-
We can hook into the Boot broadcast and replace the need for our top level component to call initialize:
315
+
The `Hyperstack::State::Observable` module will call any class level `initialize` methods in the class or subclasses
316
+
before the first component mounts.
327
317
328
-
```ruby
329
-
classGame
330
-
includeHyperstack::State::Observable
331
-
332
-
receives Hyperstack::Application::Bootdo
333
-
@history= [[]]
334
-
@step=0
335
-
end
336
-
337
-
class << self
338
-
...
339
-
end
340
-
end
341
-
...
342
-
```
343
-
> Why is `receives` outside the class definitions? The receives method can be used to attach broadcasts to either class level or
344
-
instances. In this case we want to attach the receiver to Game, not to Game's singleton class. More on receivers and broadcasting
345
-
in the chapter on Operations.
318
+
Note that with this approach we can go back to passing just the current board to `DisplayBoard` as `DisplayBoard` can
319
+
directly access `Game.handle_click!` since there is only one game.
346
320
347
321
### Thinking About Stores
348
322
@@ -359,11 +333,9 @@ If your store's methods access other stores, you do not need worry about their s
359
333
that the built in Ruby Array and Hash classes are **not** stores, so when you modify or read an Array or a Hash its up to you to use
360
334
the appropriate `mutate` or `observe` method.
361
335
362
-
> Why not make Arrays and Hashes stores? For efficiency. Its a comprimise solution.
363
-
364
336
### Stores and Parameters
365
337
366
-
Typically in a large system you will have one or more central stores, and what you end up passing as parameters are either instances of those stores, or some other kind of index into the store. Or if (as in the case of our Game) there is only one store, you
338
+
Typically in a large system you will have one or more central stores, and what you end up passing as parameters are either instances of those stores, or some other kind of index into the store. If there is only one store (as in the case of our Game), you
367
339
need not pass any parameters at all.
368
340
369
341
We can rewrite the last iteration of DisplayBoard to demonstrate this:
@@ -387,22 +359,22 @@ class DisplayBoard < HyperComponent
387
359
end
388
360
```
389
361
390
-
Here `DisplayBoard` no longer takes any parameter (and should be renamed again to `DisplayCurrentBoard`) and now
391
-
`DisplaySquare` takes the id of the square to display, but the game or the current board are never passed as parameters;
362
+
Here `DisplayBoard` no longer takes any parameter (and could be renamed again to `DisplayCurrentBoard`) and now a new component -
363
+
`DisplaySquare`- takes the id of the square to display, but the game or the current board are never passed as parameters;
392
364
there is no need to as they are implicit.
393
365
394
-
Whether to not pass store objects, an instance of a store, and index into the store is a design decision that depends on
366
+
Whether to not pass a store class, an instance of a store, or some other index into the store is a design decision that depends on
395
367
lots of factors, mainly how you see your application evolving over time.
396
368
397
369
### Summary of Methods
398
370
399
371
All the observable methods can be used either at the class or instance level.
0 commit comments