switcher 0.0.1.beta → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +7 -0
- data/Guardfile +5 -0
- data/README.md +16 -2
- data/lib/switcher/adapters/active_record.rb +78 -0
- data/lib/switcher/adapters/object.rb +65 -0
- data/lib/switcher/facade.rb +10 -3
- data/lib/switcher/spec.rb +4 -21
- data/lib/switcher/statement.rb +64 -0
- data/lib/switcher/version.rb +1 -1
- data/lib/switcher.rb +16 -3
- data/spec/fixtures/airbag.rb +19 -0
- data/spec/fixtures/box.rb +17 -0
- data/spec/fixtures/car.rb +37 -0
- data/spec/fixtures/human.rb +1 -1
- data/spec/switcher_active_record_spec.rb +77 -0
- data/spec/switcher_spec.rb +123 -19
- metadata +21 -10
- data/lib/switcher/machine.rb +0 -43
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
-
|
1
|
+
Not for production use
|
2
2
|
|
3
|
-
See specs
|
3
|
+
Docs: See specs
|
4
|
+
|
5
|
+
## TODO:
|
6
|
+
|
7
|
+
* 1.8 compat
|
8
|
+
* Write README
|
9
|
+
* Refactoring
|
10
|
+
* Events without state
|
11
|
+
* Ability to define validations, associations etc. in state (ability to call static methods from object)
|
12
|
+
|
13
|
+
## Adapters TODO:
|
14
|
+
|
15
|
+
* ActiveModel
|
16
|
+
* Virtus
|
17
|
+
* Mongoid
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Switcher
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module Initializer
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def switcher_pre_initialize(inst, spec)
|
9
|
+
inst.class_eval do
|
10
|
+
spec_name = spec.name
|
11
|
+
|
12
|
+
define_method(:"#{spec_name}_spec") { spec }
|
13
|
+
define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
|
14
|
+
|
15
|
+
define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
|
16
|
+
|
17
|
+
events = []
|
18
|
+
|
19
|
+
spec.states.each_pair do |state_name, state|
|
20
|
+
define_method(:"#{spec_name}_#{state_name}?") do
|
21
|
+
state_name == self.send(:"#{spec_name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
events << state.event_names
|
25
|
+
end
|
26
|
+
|
27
|
+
events.flatten.each do |event_name|
|
28
|
+
define_method(:"can_#{event_name}?") do
|
29
|
+
self.class.class_variable_get(:@@__specs__).map { |spc|
|
30
|
+
spc.states[self.send(:"#{spc.name}")].event_names.include?(event_name)
|
31
|
+
}.include?(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
define_method(:"#{event_name}!") do |*args|
|
35
|
+
switch_from(nil, event_name, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.included(base)
|
44
|
+
base.extend Switcher::ClassMethods
|
45
|
+
base.extend Switcher::ActiveRecord::ClassMethods
|
46
|
+
|
47
|
+
base.class_eval do
|
48
|
+
after_initialize do
|
49
|
+
self.class.class_variable_get(:@@__specs__).each do |spec|
|
50
|
+
spec_name = spec.name
|
51
|
+
state_current = read_attribute(spec_name)
|
52
|
+
state_prev = (has_attribute?("#{spec_name}_prev") ? read_attribute("#{spec_name}_prev") : nil)
|
53
|
+
|
54
|
+
self.instance_variable_set(:"@#{spec_name}_statement", Statement.new(self, spec, state_current, state_prev))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def switch(event, *args)
|
61
|
+
switch_from(self.class, event, *args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def switch_from(orig, event, *args)
|
65
|
+
self.class.class_variable_get(:@@__specs__).each do |spc|
|
66
|
+
spc_name = spc.name
|
67
|
+
|
68
|
+
self.instance_variable_get(:"@#{spc_name}_statement").publish(event, orig, args)
|
69
|
+
|
70
|
+
write_attribute(spc_name, self.send(:"#{spc_name}"))
|
71
|
+
if has_attribute?("#{spc_name}_prev")
|
72
|
+
write_attribute("#{spc_name}_prev", self.send(:"#{spc_name}_prev"))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Switcher
|
2
|
+
module Object
|
3
|
+
module Initializer
|
4
|
+
def new(*args, &block)
|
5
|
+
instance = allocate
|
6
|
+
instance.instance_eval { initialize(*args, &block) }
|
7
|
+
instance.class.class_variable_get(:@@__specs__).each do |spc| # TODO - move to something like Switcher.initial_statement(instance)
|
8
|
+
instance.instance_variable_set(:"@#{spc.name}_statement", Statement.new(instance, spc))
|
9
|
+
end
|
10
|
+
instance
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def switcher_pre_initialize(inst, spec)
|
16
|
+
inst.class_eval do
|
17
|
+
spec_name = spec.name
|
18
|
+
|
19
|
+
define_method(:"#{spec_name}_spec") { spec }
|
20
|
+
define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
|
21
|
+
|
22
|
+
define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
|
23
|
+
|
24
|
+
events = []
|
25
|
+
|
26
|
+
spec.states.each_pair do |state_name, state|
|
27
|
+
define_method(:"#{spec_name}_#{state_name}?") do
|
28
|
+
state_name == self.send(:"#{spec_name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
events << state.event_names
|
32
|
+
end
|
33
|
+
|
34
|
+
events.flatten.each do |event_name|
|
35
|
+
define_method(:"can_#{event_name}?") do
|
36
|
+
self.class.class_variable_get(:@@__specs__).map { |spc|
|
37
|
+
spc.states[self.send(:"#{spc.name}")].event_names.include?(event_name)
|
38
|
+
}.include?(true)
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method(:"#{event_name}!") do |*args|
|
42
|
+
switch(event_name, *args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.included(base)
|
50
|
+
base.extend Switcher::ClassMethods
|
51
|
+
base.extend Switcher::Object::Initializer
|
52
|
+
base.extend Switcher::Object::ClassMethods
|
53
|
+
end
|
54
|
+
|
55
|
+
def switch(event, *args)
|
56
|
+
switch_from(self.class, event, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def switch_from(orig, event, *args)
|
60
|
+
self.class.class_variable_get(:@@__specs__).each do |spc|
|
61
|
+
self.instance_variable_get(:"@#{spc.name}_statement").publish(event, orig, args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/switcher/facade.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
module Switcher
|
2
2
|
class Facade
|
3
|
-
def initialize(
|
3
|
+
def initialize(target, data)
|
4
4
|
@args = data
|
5
5
|
@stopped = false
|
6
|
-
@
|
6
|
+
@bubble_cancelled = false
|
7
|
+
@target_state = nil
|
8
|
+
@target = target
|
7
9
|
end
|
8
10
|
|
9
|
-
attr_reader :args, :stopped, :target_state
|
11
|
+
attr_reader :args, :stopped, :target, :target_state, :bubble_cancelled
|
10
12
|
|
11
13
|
def stop
|
12
14
|
@stopped = true
|
15
|
+
@bubble_cancelled = true
|
13
16
|
end
|
14
17
|
|
15
18
|
alias :restrict :stop
|
@@ -17,5 +20,9 @@ module Switcher
|
|
17
20
|
def switch_to(state)
|
18
21
|
@target_state = state.to_sym
|
19
22
|
end
|
23
|
+
|
24
|
+
def cancel_bubble
|
25
|
+
@bubble_cancelled = true
|
26
|
+
end
|
20
27
|
end
|
21
28
|
end
|
data/lib/switcher/spec.rb
CHANGED
@@ -7,10 +7,10 @@ module Switcher
|
|
7
7
|
@name = name.to_sym
|
8
8
|
@states = {}
|
9
9
|
@states_list = []
|
10
|
-
@
|
10
|
+
@targets = []
|
11
11
|
end
|
12
12
|
|
13
|
-
attr_reader :name, :states, :
|
13
|
+
attr_reader :name, :states, :states_list, :targets
|
14
14
|
|
15
15
|
def state(name, &block)
|
16
16
|
listener = Listener.new(name)
|
@@ -20,26 +20,9 @@ module Switcher
|
|
20
20
|
@states[name.to_sym] = listener
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
@
|
23
|
+
def notify(*list)
|
24
|
+
@targets = list
|
25
25
|
end
|
26
26
|
|
27
|
-
def publish(current_state, event, instance, args)
|
28
|
-
facade = Facade.new(args)
|
29
|
-
["before_#{event}", event.to_s].each do |ev|
|
30
|
-
@states[current_state.to_sym].trigger(ev, facade, instance, args)
|
31
|
-
end
|
32
|
-
set_state(facade)
|
33
|
-
@states[current_state.to_sym].trigger("after_#{event}", facade, instance, args)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def set_state(facade)
|
39
|
-
unless facade.stopped && facade.target_state.nil?
|
40
|
-
@state_prev = @current_state
|
41
|
-
@current_state = facade.target_state.to_sym
|
42
|
-
end
|
43
|
-
end
|
44
27
|
end
|
45
28
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Switcher
|
2
|
+
class Statement
|
3
|
+
def initialize(instance, spec, state_current=nil, state_prev=nil)
|
4
|
+
@instance = instance
|
5
|
+
|
6
|
+
@spec = spec
|
7
|
+
|
8
|
+
@state_prev = state_prev
|
9
|
+
@state_current = state_current
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :name, :states
|
13
|
+
|
14
|
+
def state_current
|
15
|
+
(@state_current || @spec.states_list.first).to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def state_prev
|
19
|
+
@state_prev ? @state_prev.to_sym : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish(event, original, args)
|
23
|
+
states = @spec.states
|
24
|
+
state = state_current
|
25
|
+
|
26
|
+
return unless states.has_key?(state)
|
27
|
+
|
28
|
+
facade = Facade.new(original, args)
|
29
|
+
|
30
|
+
["before_#{event}", event.to_s].each do |ev|
|
31
|
+
states[state].trigger(ev, facade, @instance, args)
|
32
|
+
end
|
33
|
+
|
34
|
+
set_state(facade)
|
35
|
+
|
36
|
+
states[state].trigger("after_#{event}", facade, @instance, args)
|
37
|
+
|
38
|
+
return if facade.bubble_cancelled
|
39
|
+
|
40
|
+
if @spec.targets.length > 0
|
41
|
+
@spec.targets.each do |target|
|
42
|
+
target_instance = @instance.send(target.to_sym)
|
43
|
+
if target_instance.respond_to?(:each)
|
44
|
+
target_instance.each do |ti|
|
45
|
+
ti.switch_from(original, event, args) if ti.respond_to?(:switch_from)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
target_instance.switch_from(original, event, args) if target_instance.respond_to?(:switch_from)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def set_state(facade)
|
57
|
+
unless facade.stopped || facade.target_state.nil?
|
58
|
+
@state_prev = state_current
|
59
|
+
@state_current = facade.target_state.to_sym
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/lib/switcher/version.rb
CHANGED
data/lib/switcher.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
require "switcher/version"
|
2
|
+
require 'switcher/spec'
|
3
|
+
require 'switcher/statement'
|
4
|
+
require 'switcher/adapters/object'
|
5
|
+
require 'switcher/adapters/active_record'
|
2
6
|
|
3
7
|
module Switcher
|
4
|
-
|
5
|
-
|
8
|
+
module ClassMethods
|
9
|
+
def switcher(name, options={}, &block)
|
10
|
+
self.class_variable_defined?(:@@__specs__) or self.class_variable_set(:@@__specs__, [])
|
6
11
|
|
7
|
-
|
12
|
+
spec = Spec.new(name, options)
|
13
|
+
spec.instance_eval(&block)
|
14
|
+
|
15
|
+
self.class_variable_get(:@@__specs__) << spec
|
16
|
+
|
17
|
+
switcher_pre_initialize(self, spec)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Airbag
|
2
|
+
include Switcher::Object
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@crashed_from = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :crashed_from
|
9
|
+
|
10
|
+
switcher :state do
|
11
|
+
state :idle do
|
12
|
+
on :crash, switch_to: :deployed do |ev|
|
13
|
+
@crashed_from = ev.target
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
state :deployed
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
class Box < ActiveRecord::Base
|
4
|
+
include Switcher::ActiveRecord
|
5
|
+
|
6
|
+
switcher :state do
|
7
|
+
state :requested do
|
8
|
+
on :produce, switch_to: :produced
|
9
|
+
end
|
10
|
+
|
11
|
+
state :produced do
|
12
|
+
on :break, switch_to: :broken
|
13
|
+
end
|
14
|
+
|
15
|
+
state :broken
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Car
|
2
|
+
include Switcher::Object
|
3
|
+
|
4
|
+
attr_accessor :airbag, :airbags
|
5
|
+
|
6
|
+
switcher :state do
|
7
|
+
notify :airbag, :airbags
|
8
|
+
|
9
|
+
state :new do
|
10
|
+
on :buy, switch_to: :buyed
|
11
|
+
end
|
12
|
+
|
13
|
+
state :buyed do
|
14
|
+
on :start, switch_to: :used
|
15
|
+
end
|
16
|
+
|
17
|
+
state :used do
|
18
|
+
on :crash, switch_to: :damaged do |ev, fast=true|
|
19
|
+
ev.cancel_bubble unless fast
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
state :damaged do
|
24
|
+
on :repair, switch_to: :used
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
switcher :movement do
|
29
|
+
state :stands do
|
30
|
+
on :start, switch_to: :moves
|
31
|
+
end
|
32
|
+
|
33
|
+
state :moves do
|
34
|
+
on :stop, switch_to: :stands
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/fixtures/human.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
require 'fixtures/box'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
describe Switcher::ActiveRecord do
|
7
|
+
before :all do
|
8
|
+
FileUtils.rm("test.sqlite3") if File.exists?("test.sqlite3")
|
9
|
+
|
10
|
+
ActiveRecord::Migration.verbose = false
|
11
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "test.sqlite3")
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define do
|
14
|
+
create_table :boxes do |t|
|
15
|
+
t.string :type
|
16
|
+
t.string :state
|
17
|
+
t.string :state_prev
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
after :all do
|
23
|
+
FileUtils.rm("test.sqlite3")
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:box) do
|
27
|
+
Box.new
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should have initial state" do
|
31
|
+
box.state.should eq(:requested)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should switch states" do
|
35
|
+
box.produce!
|
36
|
+
|
37
|
+
box.state.should eq(:produced)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should save states" do
|
41
|
+
box.produce!
|
42
|
+
box.save
|
43
|
+
|
44
|
+
box.reload
|
45
|
+
|
46
|
+
box.state.should eq(:produced)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should save previous states if possible" do
|
50
|
+
box.produce!
|
51
|
+
box.save
|
52
|
+
|
53
|
+
box.reload
|
54
|
+
|
55
|
+
box.state_prev.should eq(:requested)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should save last defined state" do
|
59
|
+
box.produce!
|
60
|
+
box.break!
|
61
|
+
box.save
|
62
|
+
|
63
|
+
box.reload
|
64
|
+
|
65
|
+
box.state.should eq(:broken)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "can detect possibility to switch" do
|
69
|
+
box.can_produce?.should be_true
|
70
|
+
box.can_break?.should be_false
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have state predicate" do
|
74
|
+
box.state_requested?.should be_true
|
75
|
+
box.state_produced?.should be_false
|
76
|
+
end
|
77
|
+
end
|
data/spec/switcher_spec.rb
CHANGED
@@ -1,54 +1,158 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
require 'fixtures/human'
|
4
|
+
require 'fixtures/car'
|
5
|
+
require 'fixtures/airbag'
|
4
6
|
|
5
7
|
describe Switcher do
|
6
8
|
|
7
9
|
describe "Basic functionality" do
|
8
|
-
|
10
|
+
person = Human.new
|
11
|
+
|
12
|
+
it "should have initial state" do
|
13
|
+
person.state.should eq(:newborn)
|
14
|
+
end
|
9
15
|
|
10
16
|
it "should switch to state with option" do
|
11
|
-
|
12
|
-
|
17
|
+
person.go_to_school!
|
18
|
+
|
19
|
+
person.state.should eq(:scholar)
|
13
20
|
end
|
14
21
|
|
15
22
|
it "should switch to state by internal condition" do
|
16
|
-
|
17
|
-
|
23
|
+
person.finish_school!(2)
|
24
|
+
|
25
|
+
person.state.should eq(:worker)
|
18
26
|
end
|
19
27
|
|
20
28
|
it "should have before and after callbacks" do
|
21
|
-
|
22
|
-
|
29
|
+
person.full_age!(100)
|
30
|
+
person.fight!(true)
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
person.state.should eq(:civil)
|
33
|
+
person.easy_to_fight.should be_true
|
34
|
+
person.honor.should eq(100)
|
27
35
|
end
|
28
36
|
|
29
37
|
it "can prevent switch" do
|
30
|
-
|
31
|
-
|
38
|
+
person.find_job!(false)
|
39
|
+
|
40
|
+
person.state.should eq(:civil)
|
32
41
|
end
|
33
42
|
|
34
43
|
it "and can not" do
|
35
|
-
|
36
|
-
|
44
|
+
person.switch("find_job", true)
|
45
|
+
|
46
|
+
person.state.should eq(:manager)
|
37
47
|
end
|
38
48
|
|
39
49
|
it "can detect possibility to switch" do
|
40
|
-
|
41
|
-
|
50
|
+
person.can_fight?.should be_false
|
51
|
+
person.can_die?.should be_true
|
42
52
|
end
|
43
53
|
|
44
54
|
it "should remember previous state" do
|
45
|
-
|
55
|
+
person.state_prev.should eq(:civil)
|
46
56
|
end
|
47
57
|
|
48
58
|
it "should have state predicate" do
|
49
|
-
|
50
|
-
|
59
|
+
person.state_manager?.should be_true
|
60
|
+
person.state_civil?.should be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "Multiple states" do
|
65
|
+
car = Car.new
|
66
|
+
|
67
|
+
it "should have different states" do
|
68
|
+
car.state.should eq(:new)
|
69
|
+
car.movement.should eq(:stands)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should switch states" do
|
73
|
+
car.buy!
|
74
|
+
|
75
|
+
car.state.should eq(:buyed)
|
76
|
+
car.movement.should eq(:stands)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should have cross-stated events" do
|
80
|
+
car.start!
|
81
|
+
|
82
|
+
car.state.should eq(:used)
|
83
|
+
car.movement.should eq(:moves)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should remember previous states" do
|
87
|
+
car.state_prev.should eq(:buyed)
|
88
|
+
car.movement_prev.should eq(:stands)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have state predicates" do
|
92
|
+
car.state_used?.should be_true
|
93
|
+
car.state_new?.should be_false
|
94
|
+
|
95
|
+
car.movement_moves?.should be_true
|
96
|
+
car.movement_stands?.should be_false
|
51
97
|
end
|
52
98
|
end
|
53
99
|
|
100
|
+
describe "Multiple instances" do
|
101
|
+
car_one = Car.new
|
102
|
+
car_two = Car.new
|
103
|
+
|
104
|
+
it "should work independent" do
|
105
|
+
car_one.buy!
|
106
|
+
|
107
|
+
car_two.state.should eq(:new)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "Event bubbling" do
|
112
|
+
it "should bubble event" do
|
113
|
+
car = Car.new
|
114
|
+
car.airbag = Airbag.new
|
115
|
+
|
116
|
+
car.buy!
|
117
|
+
car.start!
|
118
|
+
car.crash!
|
119
|
+
|
120
|
+
car.airbag.state.should eq(:deployed)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should bubble event to collections" do
|
124
|
+
car = Car.new
|
125
|
+
car.airbags = [Airbag.new, Airbag.new]
|
126
|
+
|
127
|
+
car.buy!
|
128
|
+
car.start!
|
129
|
+
car.crash!
|
130
|
+
|
131
|
+
car.airbags.each do |ab|
|
132
|
+
ab.state.should eq(:deployed)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should define original target" do
|
137
|
+
car = Car.new
|
138
|
+
car.airbag = Airbag.new
|
139
|
+
|
140
|
+
car.buy!
|
141
|
+
car.start!
|
142
|
+
car.crash!
|
143
|
+
|
144
|
+
car.airbag.crashed_from.to_s.should eq("Car")
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should cancel bubble" do
|
148
|
+
car = Car.new
|
149
|
+
car.airbag = Airbag.new
|
150
|
+
|
151
|
+
car.buy!
|
152
|
+
car.start!
|
153
|
+
car.crash!(false)
|
154
|
+
|
155
|
+
car.airbag.state.should eq(:idle)
|
156
|
+
end
|
157
|
+
end
|
54
158
|
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: switcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Andrey Savchenko
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-04 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70251275731280 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70251275731280
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70251275730860 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70251275730860
|
36
36
|
description: Switcher is simple, event-driven state machine
|
37
37
|
email:
|
38
38
|
- andrey@aejis.eu
|
@@ -42,16 +42,23 @@ extra_rdoc_files: []
|
|
42
42
|
files:
|
43
43
|
- .gitignore
|
44
44
|
- Gemfile
|
45
|
+
- Guardfile
|
45
46
|
- README.md
|
46
47
|
- Rakefile
|
47
48
|
- lib/switcher.rb
|
49
|
+
- lib/switcher/adapters/active_record.rb
|
50
|
+
- lib/switcher/adapters/object.rb
|
48
51
|
- lib/switcher/facade.rb
|
49
52
|
- lib/switcher/listener.rb
|
50
|
-
- lib/switcher/machine.rb
|
51
53
|
- lib/switcher/spec.rb
|
54
|
+
- lib/switcher/statement.rb
|
52
55
|
- lib/switcher/version.rb
|
56
|
+
- spec/fixtures/airbag.rb
|
57
|
+
- spec/fixtures/box.rb
|
58
|
+
- spec/fixtures/car.rb
|
53
59
|
- spec/fixtures/human.rb
|
54
60
|
- spec/spec_helper.rb
|
61
|
+
- spec/switcher_active_record_spec.rb
|
55
62
|
- spec/switcher_spec.rb
|
56
63
|
- switcher.gemspec
|
57
64
|
homepage: https://github.com/Ptico/switcher
|
@@ -69,9 +76,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
77
|
none: false
|
71
78
|
requirements:
|
72
|
-
- - ! '
|
79
|
+
- - ! '>='
|
73
80
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
81
|
+
version: '0'
|
75
82
|
requirements: []
|
76
83
|
rubyforge_project: switcher
|
77
84
|
rubygems_version: 1.8.6
|
@@ -79,6 +86,10 @@ signing_key:
|
|
79
86
|
specification_version: 3
|
80
87
|
summary: Switcher is simple, event-driven state machine
|
81
88
|
test_files:
|
89
|
+
- spec/fixtures/airbag.rb
|
90
|
+
- spec/fixtures/box.rb
|
91
|
+
- spec/fixtures/car.rb
|
82
92
|
- spec/fixtures/human.rb
|
83
93
|
- spec/spec_helper.rb
|
94
|
+
- spec/switcher_active_record_spec.rb
|
84
95
|
- spec/switcher_spec.rb
|
data/lib/switcher/machine.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'switcher/spec'
|
2
|
-
|
3
|
-
module Switcher
|
4
|
-
module Machine
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def switcher(name, options={}, &block)
|
8
|
-
self.class_variable_defined?(:@@__specs__) or self.class_variable_set(:@@__specs__, [])
|
9
|
-
|
10
|
-
spec = Spec.new(name, options)
|
11
|
-
spec.instance_eval(&block)
|
12
|
-
|
13
|
-
self.class_variable_get(:@@__specs__) << spec # dup and destroy?
|
14
|
-
|
15
|
-
self.class_eval do
|
16
|
-
define_method(:"#{spec.name}_spec") { spec }
|
17
|
-
define_method(:"#{spec.name}_prev") { spec.state_prev }
|
18
|
-
|
19
|
-
define_method(:"#{spec.name}") { spec.current_state }
|
20
|
-
|
21
|
-
events = []
|
22
|
-
|
23
|
-
spec.states.each_pair do |state_name, state|
|
24
|
-
define_method(:"#{state_name}?") { state_name == self.send(:"#{spec.name}") }
|
25
|
-
|
26
|
-
events << state.event_names
|
27
|
-
end
|
28
|
-
|
29
|
-
events.flatten.each do |event_name|
|
30
|
-
define_method(:"can_#{event_name}?") { spec.states[self.send(:"#{spec.name}")].event_names.include?(event_name) }
|
31
|
-
define_method(:"#{event_name}!") do |*args|
|
32
|
-
spec.publish(self.send(:"#{spec.name}"), event_name, self, args)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.included(base)
|
40
|
-
base.extend ClassMethods
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|