multiflow 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/CHANGELOG.rdoc +38 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +46 -0
- data/LICENCE +20 -0
- data/README.rdoc +131 -0
- data/Rakefile +6 -0
- data/benchmark/compare_state_machines.rb +88 -0
- data/examples/robot.rb +18 -0
- data/examples/test.rb +48 -0
- data/init.rb +1 -0
- data/lib/multiflow/event.rb +38 -0
- data/lib/multiflow/exception.rb +5 -0
- data/lib/multiflow/machine.rb +40 -0
- data/lib/multiflow/persistence/active_record.rb +35 -0
- data/lib/multiflow/persistence/none.rb +21 -0
- data/lib/multiflow/persistence.rb +29 -0
- data/lib/multiflow/railtie.rb +12 -0
- data/lib/multiflow/state.rb +31 -0
- data/lib/multiflow/transition.rb +39 -0
- data/lib/multiflow.rb +121 -0
- data/multiflow.gemspec +24 -0
- data/spec/mongoid.yml +6 -0
- data/spec/orm/activerecord_spec.rb +209 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/stateflow_spec.rb +347 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 27023220462c60ad9bf0cbdee297069da3c6db61
|
4
|
+
data.tar.gz: cfd4c4c3c6b624e4377d375ea56aad5369579035
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c7835b010f45c295513917d015e2d5c59789d3e790945a40217894f337139854cd10f7c1ee3a4ece2dae05201a933c41730f5e1c1bd5c1ae6c933baf1ef60c7
|
7
|
+
data.tar.gz: 62c8b45de48b83647a7c402479b052889fa98dfbbd285dbdd3d9733bd2f496affabc42c45ad3fe1d90a3f17076748e916d57dde6b8776d8fdb3a3d979bc8ebb1
|
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
= Version 0.5.0.beta
|
2
|
+
* Added a Railtie
|
3
|
+
* Changed up the detection for persistence in Rails
|
4
|
+
|
5
|
+
= Version 0.4.2
|
6
|
+
* Some code neatening by pedromenezes - Thanks!
|
7
|
+
* Added simple benchmark which is probably not accurate :)
|
8
|
+
|
9
|
+
= Version 0.4.1
|
10
|
+
* Removed the depreciation warnings - FINALLY :)
|
11
|
+
|
12
|
+
= Version 0.4.0
|
13
|
+
* Changed the order of the hooks, now runs in the following order:
|
14
|
+
- Exit on previous state
|
15
|
+
- Enter on new state
|
16
|
+
- Save to persistence
|
17
|
+
|
18
|
+
= Version 0.3.0
|
19
|
+
* Added a non bang method for events (Check README)
|
20
|
+
* Added tests to persistence layers (About time!)
|
21
|
+
* Changed the way the persistence layers save to model
|
22
|
+
* Protected state column is now supported!
|
23
|
+
|
24
|
+
= Version 0.2.3
|
25
|
+
* Silence depreciation warnings (Fixing irritating issue)
|
26
|
+
|
27
|
+
= Version 0.2.2
|
28
|
+
* Previous state can be accessed with `_previous_state` instance method after transitioning states
|
29
|
+
|
30
|
+
= Version 0.2.1
|
31
|
+
* Reverted the writes back to update_attributes
|
32
|
+
|
33
|
+
= Version 0.2
|
34
|
+
* Added a transition to any event, please look at the tests to understand how it works - Thanks to nu7hatch for the patch!
|
35
|
+
* Changed the persistence layers to use write_attribute, instead of update_attribute - Thanks to achirkunov
|
36
|
+
|
37
|
+
= Version 0.1.2
|
38
|
+
* Fixed Mongoid support - Thanks bmartin for pointing that out
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
multiflow (1.0.0)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (3.2.8)
|
11
|
+
activesupport (= 3.2.8)
|
12
|
+
builder (~> 3.0.0)
|
13
|
+
activerecord (3.2.8)
|
14
|
+
activemodel (= 3.2.8)
|
15
|
+
activesupport (= 3.2.8)
|
16
|
+
arel (~> 3.0.2)
|
17
|
+
tzinfo (~> 0.3.29)
|
18
|
+
activesupport (3.2.8)
|
19
|
+
i18n (~> 0.6)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
arel (3.0.2)
|
22
|
+
builder (3.0.0)
|
23
|
+
diff-lcs (1.1.3)
|
24
|
+
i18n (0.6.0)
|
25
|
+
multi_json (1.3.6)
|
26
|
+
rspec (2.11.0)
|
27
|
+
rspec-core (~> 2.11.0)
|
28
|
+
rspec-expectations (~> 2.11.0)
|
29
|
+
rspec-mocks (~> 2.11.0)
|
30
|
+
rspec-core (2.11.1)
|
31
|
+
rspec-expectations (2.11.2)
|
32
|
+
diff-lcs (~> 1.1.3)
|
33
|
+
rspec-mocks (2.11.2)
|
34
|
+
sqlite3 (1.3.6)
|
35
|
+
sqlite3-ruby (1.3.3)
|
36
|
+
sqlite3 (>= 1.3.3)
|
37
|
+
tzinfo (0.3.33)
|
38
|
+
|
39
|
+
PLATFORMS
|
40
|
+
ruby
|
41
|
+
|
42
|
+
DEPENDENCIES
|
43
|
+
activerecord
|
44
|
+
multiflow!
|
45
|
+
rspec (>= 2.0.0)
|
46
|
+
sqlite3-ruby
|
data/LICENCE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Ryan Oberholzer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
= Stateflow
|
2
|
+
|
3
|
+
== PLEASE NOTE!!!
|
4
|
+
|
5
|
+
*Version 0.5.x > will only support Rails 3 / ActiveModel persistences. If you are using Rails 2 Please make sure you use the 0.4.x releases. There is a branch dedicated to that.*
|
6
|
+
|
7
|
+
This is the basics of the gem. Please check out the examples directory or tests for usage until this README gets fleshed out. Feel free to fork and modify as you please.
|
8
|
+
|
9
|
+
== INSTALL
|
10
|
+
|
11
|
+
gem install stateflow
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
As you can see below, Stateflow's API is very similar to AASM, but allows for a more dynamic state transition flow. Stateflow supports persistence/storage with Mongoid, MongoMapper, and ActiveRecord. Request any others or push them to me.
|
16
|
+
|
17
|
+
Stateflow defaults to ActiveRecord but you can set the persistence layer with:
|
18
|
+
|
19
|
+
Stateflow.persistence = :mongo_mapper
|
20
|
+
OR
|
21
|
+
Stateflow.persistence = :active_record
|
22
|
+
OR
|
23
|
+
Stateflow.persistence = :mongoid
|
24
|
+
|
25
|
+
Stateflow allows dynamic :to transitions with :decide. The result :decide returns needs to be one of the states listed in the :to array, otherwise it wont allow the transition. Please view the advanced example below for usage.
|
26
|
+
|
27
|
+
You can set the default column with the state_column function in the stateflow block. The default state column is "state".
|
28
|
+
|
29
|
+
state_column :state
|
30
|
+
|
31
|
+
== Rails 3
|
32
|
+
Stateflow now automatically tries to detect your persistence from your applications default ORM config. If the ORM you are using does not have a persistence layer it will default to ActiveRecord.
|
33
|
+
|
34
|
+
== Basic Example
|
35
|
+
|
36
|
+
require 'rubygems'
|
37
|
+
require 'stateflow'
|
38
|
+
|
39
|
+
# No persistence
|
40
|
+
Stateflow.persistence = :none
|
41
|
+
|
42
|
+
class Stoplight
|
43
|
+
include Stateflow
|
44
|
+
|
45
|
+
stateflow do
|
46
|
+
initial :green
|
47
|
+
|
48
|
+
state :green, :yellow, :red
|
49
|
+
|
50
|
+
event :change_color do
|
51
|
+
transitions :from => :green, :to => :yellow
|
52
|
+
transitions :from => :yellow, :to => :red
|
53
|
+
transitions :from => :red, :to => :green
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
== Advanced Example
|
59
|
+
|
60
|
+
require 'rubygems'
|
61
|
+
require 'stateflow'
|
62
|
+
|
63
|
+
# No persistence
|
64
|
+
Stateflow.persistence = :none
|
65
|
+
|
66
|
+
class Test
|
67
|
+
include Stateflow
|
68
|
+
|
69
|
+
stateflow do
|
70
|
+
|
71
|
+
initial :love
|
72
|
+
|
73
|
+
state :love do
|
74
|
+
enter lambda { |t| p "Entering love" }
|
75
|
+
exit :exit_love
|
76
|
+
end
|
77
|
+
|
78
|
+
state :hate do
|
79
|
+
enter lambda { |t| p "Entering hate" }
|
80
|
+
exit lambda { |t| p "Exiting hate" }
|
81
|
+
end
|
82
|
+
|
83
|
+
state :mixed do
|
84
|
+
enter lambda { |t| p "Entering mixed" }
|
85
|
+
exit lambda { |t| p "Exiting mixed" }
|
86
|
+
end
|
87
|
+
|
88
|
+
event :b do
|
89
|
+
transitions :from => :love, :to => :hate, :if => :no_ice_cream
|
90
|
+
transitions :from => :hate, :to => :love
|
91
|
+
end
|
92
|
+
|
93
|
+
event :a do
|
94
|
+
transitions :from => :love, :to => [:hate, :mixed], :decide => :likes_ice_cream?
|
95
|
+
transitions :from => [:hate, :mixed], :to => :love
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def likes_ice_cream?
|
100
|
+
rand(10) > 5 ? :mixed : :hate
|
101
|
+
end
|
102
|
+
|
103
|
+
def exit_love
|
104
|
+
p "Exiting love"
|
105
|
+
end
|
106
|
+
|
107
|
+
def no_ice_cream
|
108
|
+
rand(4) > 2 ? true : false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
== Bang event vs non-bang event
|
113
|
+
|
114
|
+
Bang events will save the model after call, where the non bang event will just update the state and call the transitions. (ie. model.change! vs model.change)
|
115
|
+
|
116
|
+
== Extras
|
117
|
+
|
118
|
+
* When transitioning states, the previous state from which you have transitioned to can be accessed via `_previous_state`. See tests for more information.
|
119
|
+
|
120
|
+
== Note on Patches/Pull Requests
|
121
|
+
|
122
|
+
* Fork the project.
|
123
|
+
* Make your feature addition or bug fix.
|
124
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
125
|
+
* Commit, do not mess with Rakefile, version, or history.
|
126
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
127
|
+
* Send me a pull request. Bonus points for topic branches.
|
128
|
+
|
129
|
+
== Copyright
|
130
|
+
|
131
|
+
Copyright (c) 2010 Ryan Oberholzer. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'active_record'
|
3
|
+
require 'stateflow'
|
4
|
+
require 'aasm'
|
5
|
+
|
6
|
+
# change this if sqlite is unavailable
|
7
|
+
dbconfig = {
|
8
|
+
:adapter => 'sqlite3',
|
9
|
+
:database => ':memory:'
|
10
|
+
}
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(dbconfig)
|
13
|
+
ActiveRecord::Migration.verbose = false
|
14
|
+
|
15
|
+
|
16
|
+
class TestMigration < ActiveRecord::Migration
|
17
|
+
STATE_MACHINES = ['stateflow', 'aasm']
|
18
|
+
|
19
|
+
def self.up
|
20
|
+
STATE_MACHINES.each do |name|
|
21
|
+
create_table "#{name}_tests", :force => true do |t|
|
22
|
+
t.column :state, :string
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.down
|
28
|
+
STATE_MACHINES.each do |name|
|
29
|
+
drop_table "#{name}_tests"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Stateflow.persistence = :active_record
|
35
|
+
|
36
|
+
class StateflowTest < ActiveRecord::Base
|
37
|
+
include Stateflow
|
38
|
+
|
39
|
+
stateflow do
|
40
|
+
initial :green
|
41
|
+
|
42
|
+
state :green, :yellow, :red
|
43
|
+
|
44
|
+
event :change_color do
|
45
|
+
transitions :from => :green, :to => :yellow
|
46
|
+
transitions :from => :yellow, :to => :red
|
47
|
+
transitions :from => :red, :to => :green
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class AasmTest < ActiveRecord::Base
|
53
|
+
include AASM
|
54
|
+
|
55
|
+
aasm_column :state # defaults to aasm_state
|
56
|
+
|
57
|
+
aasm_initial_state :green
|
58
|
+
|
59
|
+
aasm_state :green
|
60
|
+
aasm_state :yellow
|
61
|
+
aasm_state :red
|
62
|
+
|
63
|
+
aasm_event :change_color do
|
64
|
+
transitions :from => :green, :to => :yellow
|
65
|
+
transitions :from => :yellow, :to => :red
|
66
|
+
transitions :from => :red, :to => :green
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
n = 1000
|
71
|
+
TestMigration.up
|
72
|
+
|
73
|
+
Benchmark.bm(7) do |x|
|
74
|
+
x.report('stateflow') do
|
75
|
+
n.times do
|
76
|
+
stateflow = StateflowTest.new
|
77
|
+
3.times { stateflow.change_color! }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
x.report('aasm') do
|
81
|
+
n.times do
|
82
|
+
aasm = AasmTest.new
|
83
|
+
3.times { aasm.change_color! }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
TestMigration.down
|
data/examples/robot.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'stateflow'
|
3
|
+
|
4
|
+
class Robot
|
5
|
+
include Stateflow
|
6
|
+
|
7
|
+
stateflow do
|
8
|
+
initial :green
|
9
|
+
|
10
|
+
state :green, :yellow, :red
|
11
|
+
|
12
|
+
event :change_color do
|
13
|
+
transitions :from => :green, :to => :yellow
|
14
|
+
transitions :from => :yellow, :to => :red
|
15
|
+
transitions :from => :red, :to => :green
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/examples/test.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'stateflow'
|
3
|
+
|
4
|
+
class Test
|
5
|
+
include Stateflow
|
6
|
+
|
7
|
+
stateflow do
|
8
|
+
|
9
|
+
initial :love
|
10
|
+
|
11
|
+
state :love do
|
12
|
+
enter lambda { |t| p "Entering love" }
|
13
|
+
exit :exit_love
|
14
|
+
end
|
15
|
+
|
16
|
+
state :hate do
|
17
|
+
enter lambda { |t| p "Entering hate" }
|
18
|
+
exit lambda { |t| p "Exiting hate" }
|
19
|
+
end
|
20
|
+
|
21
|
+
state :mixed do
|
22
|
+
enter lambda { |t| p "Entering mixed" }
|
23
|
+
exit lambda { |t| p "Exiting mixed" }
|
24
|
+
end
|
25
|
+
|
26
|
+
event :b do
|
27
|
+
transitions :from => :love, :to => :hate, :if => :no_ice_cream
|
28
|
+
transitions :from => :hate, :to => :love
|
29
|
+
end
|
30
|
+
|
31
|
+
event :a do
|
32
|
+
transitions :from => :love, :to => [:hate, :mixed], :decide => :likes_ice_cream?
|
33
|
+
transitions :from => [:hate, :mixed], :to => :love
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def likes_ice_cream?
|
38
|
+
rand(10) > 5 ? :mixeds : :hate
|
39
|
+
end
|
40
|
+
|
41
|
+
def exit_love
|
42
|
+
p "Exiting love"
|
43
|
+
end
|
44
|
+
|
45
|
+
def no_ice_cream
|
46
|
+
rand(4) > 2 ? true : false
|
47
|
+
end
|
48
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'stateflow'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Multiflow
|
2
|
+
class Event
|
3
|
+
attr_accessor :name, :transitions, :machine
|
4
|
+
|
5
|
+
def initialize(name, machine=nil, &transitions)
|
6
|
+
@name = name
|
7
|
+
@machine = machine
|
8
|
+
@transitions = Array.new
|
9
|
+
|
10
|
+
instance_eval(&transitions)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fire(machine, current_state, klass, options)
|
14
|
+
transition = @transitions.select{ |t| t.from.include? current_state.name }.first
|
15
|
+
raise NoTransitionFound.new("No transition found for event #{@name}") if transition.nil?
|
16
|
+
|
17
|
+
return nil unless transition.can_transition?(klass)
|
18
|
+
|
19
|
+
new_state = machine.states[transition.find_to_state(klass)]
|
20
|
+
raise NoStateFound.new("Invalid state #{transition.to.to_s} for transition.") if new_state.nil?
|
21
|
+
|
22
|
+
current_state.execute_action(:exit, klass)
|
23
|
+
new_state.execute_action(:enter, klass)
|
24
|
+
|
25
|
+
new_state
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def transitions(args = {})
|
30
|
+
transition = Multiflow::Transition.new(args)
|
31
|
+
@transitions << transition
|
32
|
+
end
|
33
|
+
|
34
|
+
def any
|
35
|
+
@machine.states.keys
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Multiflow
|
2
|
+
class Machine
|
3
|
+
attr_accessor :states, :initial_state, :events
|
4
|
+
|
5
|
+
def initialize(&machine)
|
6
|
+
@states, @events, @create_scopes = Hash.new, Hash.new, true
|
7
|
+
instance_eval(&machine)
|
8
|
+
end
|
9
|
+
|
10
|
+
def state_column(name = :state)
|
11
|
+
@state_column ||= name
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_scopes(bool = false)
|
15
|
+
@create_scopes = bool
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_scopes?
|
19
|
+
@create_scopes
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def initial(name)
|
24
|
+
@initial_state_name = name
|
25
|
+
end
|
26
|
+
|
27
|
+
def state(*names, &options)
|
28
|
+
names.each do |name|
|
29
|
+
state = Multiflow::State.new(name, &options)
|
30
|
+
@initial_state = state if @states.empty? || @initial_state_name == name
|
31
|
+
@states[name.to_sym] = state
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def event(name, &transitions)
|
36
|
+
event = Multiflow::Event.new(name, self, &transitions)
|
37
|
+
@events[name.to_sym] = event
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Multiflow
|
2
|
+
module Persistence
|
3
|
+
module ActiveRecord
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_validation(:ensure_initial_state, :on => :create)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def add_scope(machine, state)
|
12
|
+
scope state.name, -> { where("#{machine.state_column}".to_sym => state.name.to_s) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_from_persistence(machine)
|
17
|
+
send machine.state_column.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def save_to_persistence(machine, new_state, options = {})
|
21
|
+
send("#{machine.state_column}=".to_sym, new_state)
|
22
|
+
save! if options[:save]
|
23
|
+
end
|
24
|
+
|
25
|
+
def ensure_initial_state
|
26
|
+
machines.each do |machine|
|
27
|
+
if send(machine.state_column.to_s).blank?
|
28
|
+
current_state = send("current_#{machine.state_column}")
|
29
|
+
send("#{machine.state_column.to_s}=", current_state.name.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Multiflow
|
2
|
+
module Persistence
|
3
|
+
module None
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def add_scope(machine, state)
|
8
|
+
# do nothing
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_from_persistence(machine)
|
13
|
+
instance_variable_get :"@state_#{machine.state_column}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def save_to_persistence(machine, new_state, options)
|
17
|
+
instance_variable_set :"@state_#{machine.state_column}", new_state
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Multiflow
|
2
|
+
module Persistence
|
3
|
+
def self.active
|
4
|
+
persistences = Array.new
|
5
|
+
|
6
|
+
Dir[File.dirname(__FILE__) + '/persistence/*.rb'].each do |file|
|
7
|
+
persistences << File.basename(file, File.extname(file)).underscore.to_sym
|
8
|
+
end
|
9
|
+
|
10
|
+
persistences
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load!(base)
|
14
|
+
begin
|
15
|
+
base.send :include, "Multiflow::Persistence::#{Multiflow.persistence.to_s.camelize}".constantize
|
16
|
+
rescue NameError
|
17
|
+
puts "[Multiflow] The ORM you are using does not have a Persistence layer. Defaulting to ActiveRecord."
|
18
|
+
puts "[Multiflow] You can overwrite the persistence with Multiflow.persistence = :new_persistence_layer"
|
19
|
+
|
20
|
+
Multiflow.persistence = :active_record
|
21
|
+
base.send :include, "Multiflow::Persistence::ActiveRecord".constantize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Multiflow::Persistence.active.each do |p|
|
26
|
+
autoload p.to_s.camelize.to_sym, "multiflow/persistence/#{p.to_s}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Multiflow
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
def default_orm
|
4
|
+
generators = config.respond_to?(:app_generators) ? :app_generators : :generators
|
5
|
+
config.send(generators).options[:rails][:orm]
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer "multiflow.set_persistence" do
|
9
|
+
Multiflow.persistence = default_orm if Multiflow.persistence.blank?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Multiflow
|
2
|
+
class State
|
3
|
+
attr_accessor :name, :options
|
4
|
+
|
5
|
+
def initialize(name, &options)
|
6
|
+
@name = name
|
7
|
+
@options = Hash.new
|
8
|
+
|
9
|
+
instance_eval(&options) if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def enter(method = nil, &block)
|
13
|
+
@options[:enter] = method.nil? ? block : method
|
14
|
+
end
|
15
|
+
|
16
|
+
def exit(method = nil, &block)
|
17
|
+
@options[:exit] = method.nil? ? block : method
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute_action(action, base)
|
21
|
+
action = @options[action.to_sym]
|
22
|
+
|
23
|
+
case action
|
24
|
+
when Symbol, String
|
25
|
+
base.send(action)
|
26
|
+
when Proc
|
27
|
+
action.call(base)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Multiflow
|
2
|
+
class IncorrectTransition < Exception; end
|
3
|
+
|
4
|
+
class Transition
|
5
|
+
attr_reader :from, :to, :if, :decide
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@from = [args[:from]].flatten
|
9
|
+
@to = args[:to]
|
10
|
+
@if = args[:if]
|
11
|
+
@decide = args[:decide]
|
12
|
+
end
|
13
|
+
|
14
|
+
def can_transition?(base)
|
15
|
+
return true unless @if
|
16
|
+
execute_action(@if, base)
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_to_state(base)
|
20
|
+
raise IncorrectTransition.new("Array of destinations and no decision") if @to.is_a?(Array) && @decide.nil?
|
21
|
+
return @to unless @to.is_a?(Array)
|
22
|
+
|
23
|
+
to = execute_action(@decide, base)
|
24
|
+
|
25
|
+
raise NoStateFound.new("Decision did not return a state that was set in the 'to' argument") unless @to.include?(to)
|
26
|
+
to
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def execute_action(action, base)
|
31
|
+
case action
|
32
|
+
when Symbol, String
|
33
|
+
base.send(action)
|
34
|
+
when Proc
|
35
|
+
action.call(base)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|