Skip to content

Commit 8c17e11

Browse files
committed
merged
2 parents ee7f4f0 + a77e5f3 commit 8c17e11

File tree

6 files changed

+93
-102
lines changed

6 files changed

+93
-102
lines changed

docs/hyper-state/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
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.
66

77
In our original Tic-Tac-Toe implementation the state of the game was stored in the `DisplayGame` component. State was updated by
8-
<<<<<<< Updated upstream
98
"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-
>>>>>>> Stashed changes
139

1410
This is a nice simple approach but suffers from two issues:
1511
+ Each level of lower level components must be responsible for bubbling up the events to the higher component.

docs/specs/spec/hyper-state/tic_tac_toe_spec.rb

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def history
7171
end
7272

7373
def run_the_spec(initial_history)
74-
binding.pry
7574
expect(page).to have_content "Next player: X", wait: 0
7675
expect(history).to eq initial_history
7776
expect(squares.count).to eq 9
@@ -707,102 +706,6 @@ def draw_square(id)
707706
end
708707

709708
class DisplayGame < HyperComponent
710-
before_mount { Game.initialize }
711-
def moves
712-
return unless Game.history.length > 1
713-
714-
Game.history.length.times do |move|
715-
LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" }
716-
.on(:click) { Game.jump_to!(move) }
717-
end
718-
end
719-
720-
def status
721-
if (winner = Game.current_winner?)
722-
"Winner: #{winner}"
723-
else
724-
"Next player: #{Game.player}"
725-
end
726-
end
727-
728-
render(DIV, class: :game) do
729-
DIV(class: :game_board) do
730-
DisplayBoard(board: Game.current)
731-
end
732-
DIV(class: :game_info) do
733-
DIV { status }
734-
OL { moves }
735-
end
736-
end
737-
end
738-
end
739-
run_the_spec([])
740-
end
741-
742-
it "using the Boot broadcast to initialize the store" do
743-
insert_html "<style>\n#{CSS}</style>"
744-
mount "DisplayGame" do
745-
class Game
746-
include Hyperstack::State::Observable
747-
748-
receives Hyperstack::Application::Boot do
749-
@history = [[]]
750-
@step = 0
751-
end
752-
753-
class << self
754-
observer :player do
755-
@step.even? ? :X : :O
756-
end
757-
758-
observer :current do
759-
@history[@step]
760-
end
761-
762-
state_reader :history
763-
764-
WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]
765-
766-
def current_winner?
767-
WINNING_COMBOS.each do |a, b, c|
768-
return current[a] if current[a] && current[a] == current[b] && current[a] == current[c]
769-
end
770-
false
771-
end
772-
773-
mutator :handle_click! do |id|
774-
board = history[@step]
775-
return if current_winner? || board[id]
776-
777-
board = board.dup
778-
board[id] = player
779-
@history = history[0..@step] + [board]
780-
@step += 1
781-
end
782-
783-
mutator(:jump_to!) { |step| @step = step }
784-
end
785-
end
786-
787-
class DisplayBoard < HyperComponent
788-
param :board
789-
790-
def draw_square(id)
791-
BUTTON(class: :square, id: id) { board[id] }
792-
.on(:click) { Game.handle_click!(id) }
793-
end
794-
795-
render(DIV) do
796-
(0..6).step(3) do |row|
797-
DIV(class: :board_row) do
798-
(row..row + 2).each { |id| draw_square(id) }
799-
end
800-
end
801-
end
802-
end
803-
804-
class DisplayGame < HyperComponent
805-
before_mount { Game.initialize }
806709
def moves
807710
return unless Game.history.length > 1
808711

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module ObjectSpace
2+
def self.each_object(target_klass, &block)
3+
klasses = [Object]
4+
i = 0
5+
loop do
6+
klass = klasses[i]
7+
yield klass
8+
names = `klass.$$const` && `Object.keys(klass.$$const)`
9+
names.each do |name|
10+
begin
11+
k = klass.const_get(name) rescue nil
12+
next unless `k.$$const`
13+
next unless k.respond_to?(:is_a?)
14+
next if klasses.include?(k)
15+
16+
klasses << k if k.is_a? target_klass
17+
rescue Exception => e
18+
next
19+
end
20+
end if names
21+
i += 1
22+
break if i >= klasses.length
23+
end
24+
end
25+
end

ruby/hyper-state/lib/hyper-state.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
require 'hyperstack/state/observer'
1313
require 'hyperstack/state/version'
1414

15-
if RUBY_ENGINE != 'opal'
15+
if RUBY_ENGINE == 'opal'
16+
require 'ext/object_space'
17+
else
1618
require 'opal'
1719
Opal.append_path(File.expand_path('../', __FILE__).untaint)
1820
end

ruby/hyper-state/lib/hyperstack/state/observable.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ def self.included(base)
107107
base.singleton_class.define_singleton_method(:receives) do |*args, &block|
108108
Internal::Receiver.mount(base, *args, &block)
109109
end
110+
111+
unless base.respond_to? :__hyperstack_state_observer_included
112+
base.receives Hyperstack::Application::Boot do
113+
ObjectSpace.each_object(Class) do |klass|
114+
next unless klass <= base
115+
next unless klass.respond_to?(:initialize)
116+
next unless klass.method(:initialize).arity.zero?
117+
klass.initialize
118+
end
119+
end
120+
base.singleton_class.attr_reader :__hyperstack_state_observer_included
121+
end
110122
end
111123
end
112124
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require 'spec_helper'
2+
3+
describe 'calling class initialize method', :js do
4+
before(:each) do
5+
isomorphic do
6+
class ClassStore
7+
include Hyperstack::State::Observable
8+
class << self
9+
attr_reader :initialized
10+
def initialize
11+
@initialized = !@initialized && self
12+
end
13+
end
14+
end
15+
16+
class ClassSubStore < ClassStore
17+
class << self
18+
attr_reader :initialized
19+
def initialize
20+
@initialized = !@initialized && self
21+
end
22+
end
23+
end
24+
25+
class ClassSubNoInit < ClassStore
26+
end
27+
28+
class SubGumStore < ClassStore
29+
include Hyperstack::State::Observable
30+
class << self
31+
attr_reader :initialized
32+
def initialize
33+
@initialized = !@initialized && self
34+
end
35+
end
36+
end
37+
end
38+
end
39+
40+
it "the store will receive a class level initialize (client)" do
41+
expect { ClassStore.initialized }.on_client_to eq(ClassStore.to_s)
42+
expect { ClassSubStore.initialized }.on_client_to eq(ClassSubStore.to_s)
43+
expect { SubGumStore.initialized }.on_client_to eq(SubGumStore.to_s)
44+
end
45+
46+
it "the store will receive a class level initialize (server)" do
47+
Hyperstack::Application::Boot.run
48+
49+
expect(ClassStore.initialized).to eq(ClassStore)
50+
expect(ClassSubStore.initialized).to eq(ClassSubStore)
51+
expect(SubGumStore.initialized).to eq(SubGumStore)
52+
end
53+
end

0 commit comments

Comments
 (0)