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
7
In our original Tic-Tac-Toe implementation the state of the game was stored in the `DisplayGame` component. State was updated by
8
+
<<<<<<< HEAD
13
9
"bubbling up" events from lower level components up to `DisplayGame` where the event handler updated the state.
10
+
=======
11
+
"bubbling up" events from lower level components up to `DisplayGame` where the event hander updated the state.
12
+
>>>>>>> issue-402
14
13
15
14
This is a nice simple approach but suffers from two issues:
16
15
+ Each level of lower level components must be responsible for bubbling up the events to the higher component.
@@ -19,11 +18,11 @@ This is a nice simple approach but suffers from two issues:
19
18
As our applications become larger we will want a way to keep each component's interface isolated and not dependent on the overall
20
19
architecture, and to insure good separation of concerns.
21
20
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
21
+
The `Hyperstack::State::Observable` module allows us to put the game's state into a separate class which can be accessed from any
22
+
component: No more need to bubble up events, and no more cluttering up our `DisplayGame` component with state management
24
23
and details of the game's data structure.
25
24
26
-
Here is the game state moved out of the `DisplayGame` component into its own class:
25
+
Here is the game state and associated methods moved out of the `DisplayGame` component into its own class:
27
26
28
27
```ruby
29
28
classGame
@@ -73,9 +72,8 @@ class Game
73
72
includeHyperstack::State::Observable
74
73
```
75
74
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.
75
+
`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
76
+
a *reactive store*. When `Game` interacts with other stores and components they will be updated as the state of `Game` changes.
79
77
80
78
```ruby
81
79
definitialize
@@ -84,9 +82,9 @@ any components that directly or indirectly depend on its state.
84
82
end
85
83
```
86
84
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.)
85
+
In the original implementation we initialized the two state variables `@history` and `@step` in the `before_mount` callback. The same initialization
86
+
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`
87
+
`before_mount` callback (see below.)
90
88
91
89
```ruby
92
90
observer :playerdo
@@ -108,8 +106,7 @@ been changed `observe` and `observer` indicate that state has been accessed outs
108
106
attr_reader:history
109
107
```
110
108
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.
109
+
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
177
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`.
178
+
of the component's code we call the appropriate method on `@game`.
182
179
183
180
We will need to pass the entire game to `DisplayBoard` (we will see why shortly) so we will rename it to `DisplayCurrentBoard`.
184
181
@@ -216,19 +213,20 @@ communicate back upwards via events. Instead we communicate through the central
216
213
217
214
Rather than sending params down to lower level components, and having the components bubble up events, we have created a *Flux Loop*.
218
215
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.
216
+
components update the `Game`state causing the top level component to re-rerender.
220
217
221
218
This structure greatly simplifies the structure and understanding of our components, and keeps each component functionally isolated.
222
219
223
220
Furthermore algorithms such as `current_winner?` now are neatly abstracted out into their own class.
224
221
225
222
### Classes and Instances
226
223
227
-
If we are sure we will only want one Game board, we could define Game with class methods like this:
224
+
If we are sure we will only want one game board, we could define `Game` with class methods like this:
228
225
229
226
```ruby
230
227
classGame
231
228
includeHyperstack::State::Observable
229
+
232
230
class << self
233
231
definitialize
234
232
@history= [[]]
@@ -286,7 +284,6 @@ class DisplayBoard < HyperComponent
286
284
end
287
285
288
286
classDisplayGame < HyperComponent
289
-
before_mount { Game.initialize }
290
287
defmoves
291
288
returnunlessGame.history.length >1
292
289
@@ -316,33 +313,14 @@ class DisplayGame < HyperComponent
316
313
end
317
314
```
318
315
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.
316
+
Now instead of creating an instance and passing it around we
317
+
call the class level methods on `Game` throughout.
325
318
326
-
We can hook into the Boot broadcast and replace the need for our top level component to call initialize:
319
+
The `Hyperstack::State::Observable` module will call any class level `initialize` methods in the class or subclasses
320
+
before the first component mounts.
327
321
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.
322
+
Note that with this approach we can go back to passing just the current board to `DisplayBoard` as `DisplayBoard` can
323
+
directly access `Game.handle_click!` since there is only one game.
346
324
347
325
### Thinking About Stores
348
326
@@ -359,11 +337,9 @@ If your store's methods access other stores, you do not need worry about their s
359
337
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
338
the appropriate `mutate` or `observe` method.
361
339
362
-
> Why not make Arrays and Hashes stores? For efficiency. Its a comprimise solution.
363
-
364
340
### Stores and Parameters
365
341
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
342
+
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
343
need not pass any parameters at all.
368
344
369
345
We can rewrite the previous iteration of `DisplayBoard` to demonstrate this:
@@ -387,22 +363,26 @@ class DisplayBoard < HyperComponent
387
363
end
388
364
```
389
365
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;
366
+
Here `DisplayBoard` no longer takes any parameter (and could be renamed again to `DisplayCurrentBoard`) and now a new component -
367
+
`DisplaySquare`- takes the id of the square to display, but the game or the current board are never passed as parameters;
392
368
there is no need to as they are implicit.
393
369
370
+
<<<<<<< HEAD
394
371
Whether to pass (or not pass) a store class, an instance of a store, or some other index into the store is a design decision that depends on
372
+
=======
373
+
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
374
+
>>>>>>> issue-402
395
375
lots of factors, mainly how you see your application evolving over time.
396
376
397
377
### Summary of Methods
398
378
399
379
All the observable methods can be used either at the class or instance level.
0 commit comments