openhood-simple_state_machine 1.0.1 → 2.0.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 +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +53 -0
- data/MIT-LICENSE +22 -0
- data/README.textile +50 -0
- data/Rakefile +8 -0
- data/init.rb +1 -0
- data/lib/simple_state_machine/active_record.rb +61 -0
- data/lib/simple_state_machine/mongo_mapper.rb +55 -0
- data/lib/simple_state_machine/version.rb +3 -0
- data/lib/simple_state_machine.rb +3 -61
- data/simple_state_machine.gemspec +25 -0
- data/spec/db/schema.rb +8 -0
- data/spec/simple_state_machine/active_record_spec.rb +133 -0
- data/spec/simple_state_machine/mongo_mapper_spec.rb +119 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/mongo_mapper_helper.rb +268 -0
- metadata +105 -52
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
openhood-simple_state_machine (2.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (3.0.9)
|
10
|
+
activesupport (= 3.0.9)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
i18n (~> 0.5.0)
|
13
|
+
activerecord (3.0.9)
|
14
|
+
activemodel (= 3.0.9)
|
15
|
+
activesupport (= 3.0.9)
|
16
|
+
arel (~> 2.0.10)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activesupport (3.0.9)
|
19
|
+
arel (2.0.10)
|
20
|
+
bson (1.3.1)
|
21
|
+
builder (2.1.2)
|
22
|
+
diff-lcs (1.1.2)
|
23
|
+
i18n (0.5.0)
|
24
|
+
mongo (1.3.1)
|
25
|
+
bson (>= 1.3.1)
|
26
|
+
mongo_mapper (0.9.1)
|
27
|
+
activemodel (~> 3.0)
|
28
|
+
activesupport (~> 3.0)
|
29
|
+
plucky (~> 0.3.8)
|
30
|
+
plucky (0.3.8)
|
31
|
+
mongo (~> 1.3)
|
32
|
+
rake (0.8.7)
|
33
|
+
rspec (2.6.0)
|
34
|
+
rspec-core (~> 2.6.0)
|
35
|
+
rspec-expectations (~> 2.6.0)
|
36
|
+
rspec-mocks (~> 2.6.0)
|
37
|
+
rspec-core (2.6.4)
|
38
|
+
rspec-expectations (2.6.0)
|
39
|
+
diff-lcs (~> 1.1.2)
|
40
|
+
rspec-mocks (2.6.0)
|
41
|
+
sqlite3 (1.3.3)
|
42
|
+
tzinfo (0.3.28)
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
ruby
|
46
|
+
|
47
|
+
DEPENDENCIES
|
48
|
+
activerecord (>= 3.0.0)
|
49
|
+
mongo_mapper (~> 0.9.0)
|
50
|
+
openhood-simple_state_machine!
|
51
|
+
rake (~> 0.8.7)
|
52
|
+
rspec (~> 2.6.0)
|
53
|
+
sqlite3
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Joseph HALTER
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
h2. State Machine
|
2
|
+
|
3
|
+
Allow an Active Record or MongoMapper model to act as a finite state machine just as the popular plugin acts_as_state_machine but with many enhancements such as the possibility to have multiple states per object.
|
4
|
+
|
5
|
+
It can work together with the classic act_as_state_machine on the same project but both cannot be used at the same time for one given Active Record model. Moreover, simple_state_machine work standalone and doesn't require acts_as_state_machine.
|
6
|
+
|
7
|
+
You also get magic methods to check current state and a way to revert to previous state in database (or initial value for a new record) if something went wrong.
|
8
|
+
|
9
|
+
h3. Example for ActiveRecord
|
10
|
+
|
11
|
+
require "simple_state_machine/active_record"
|
12
|
+
|
13
|
+
class Chicken < ActiveRecord::Base
|
14
|
+
state_machine :user_state, [:pending, :active, :removed, :on_hold]
|
15
|
+
state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
|
16
|
+
def user_activate!
|
17
|
+
return false if !user_state_pending?
|
18
|
+
self.user_state = :active
|
19
|
+
save! rescue user_state_revert
|
20
|
+
user_state_active?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
And then you can do:
|
25
|
+
|
26
|
+
c = Chicken.create # c.user_state = :pending
|
27
|
+
c.user_activate! # c.user_state = :active
|
28
|
+
|
29
|
+
h3. Example for MongoMapper
|
30
|
+
|
31
|
+
require "simple_state_machine/mongo_mapper"
|
32
|
+
|
33
|
+
class Chicken
|
34
|
+
include MongoMapper::Document
|
35
|
+
plugin SimpleStateMachine::MongoMapper
|
36
|
+
|
37
|
+
state_machine :user_state, [:pending, :active, :removed, :on_hold]
|
38
|
+
state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
|
39
|
+
def user_activate!
|
40
|
+
return false if !user_state_pending?
|
41
|
+
self.user_state = :active
|
42
|
+
save! rescue user_state_revert
|
43
|
+
user_state_active?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
And then you can do:
|
48
|
+
|
49
|
+
c = Chicken.create # c.user_state = :pending
|
50
|
+
c.user_activate! # c.user_state = :active
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "simple_state_machine/active_record"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module SimpleStateMachine
|
4
|
+
module ActiveRecord
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do |base|
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def state_machine(column, states)
|
13
|
+
create_empty_state_machine unless inheritable_attributes.key? :states
|
14
|
+
inheritable_attributes[:states][column.to_sym] = states
|
15
|
+
validates_inclusion_of column, :in => states
|
16
|
+
# should also override getter/setter to convert to strings
|
17
|
+
self.class_eval <<-eos
|
18
|
+
def #{column.to_s}=(value)
|
19
|
+
self[:#{column.to_s}] = value.to_s
|
20
|
+
end
|
21
|
+
def #{column.to_s}
|
22
|
+
self[:#{column.to_s}].to_sym
|
23
|
+
end
|
24
|
+
def #{column.to_s}_revert
|
25
|
+
self[:#{column.to_s}] = self.new_record? ? states[:#{column.to_s}].first.to_s : self.#{column.to_s}_was
|
26
|
+
end
|
27
|
+
eos
|
28
|
+
|
29
|
+
# define a method {state_column}_{state}? for each state
|
30
|
+
states.each do |state|
|
31
|
+
self.class_eval <<-eos
|
32
|
+
def #{column.to_s}_#{state.to_s}?
|
33
|
+
self[:#{column.to_s}] === "#{state.to_s}"
|
34
|
+
end
|
35
|
+
eos
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def create_empty_state_machine
|
43
|
+
write_inheritable_attribute :states, {} # add a class variable
|
44
|
+
class_inheritable_reader :states # make it read-only
|
45
|
+
|
46
|
+
after_initialize :set_initial_states
|
47
|
+
self.class_eval do
|
48
|
+
def set_initial_states
|
49
|
+
states.each {|column, states|
|
50
|
+
self[column] = states.first.to_s
|
51
|
+
} if new_record?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
ActiveRecord::Base.class_eval do
|
60
|
+
include SimpleStateMachine::ActiveRecord
|
61
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module MongoMapper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_eval do
|
7
|
+
write_inheritable_attribute :states, {}
|
8
|
+
class_inheritable_reader :states
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def state_machine(column, column_states)
|
14
|
+
inheritable_attributes[:states][column.to_sym] = column_states
|
15
|
+
|
16
|
+
key column, String
|
17
|
+
validates_inclusion_of column, :in => column_states
|
18
|
+
|
19
|
+
define_method :"#{column}_revert" do
|
20
|
+
write_key column, new? ? states[column].first : send(:"#{column}_was")
|
21
|
+
end
|
22
|
+
|
23
|
+
# define a method {state_column}_{state}? for each state
|
24
|
+
column_states.each do |state|
|
25
|
+
define_method :"#{column}_#{state}?" do
|
26
|
+
send(column) === state
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
def initialize(*args)
|
34
|
+
super(*args)
|
35
|
+
set_initial_state
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def write_key(key, value)
|
40
|
+
super(key, states.keys.include?(key) ? value.to_s : value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_key(key)
|
44
|
+
value = super(key)
|
45
|
+
value && states.keys.include?(key) ? value.to_sym : value
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_initial_state
|
49
|
+
states.each do |column, states|
|
50
|
+
write_key column, states.first
|
51
|
+
end if new?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/simple_state_machine.rb
CHANGED
@@ -1,63 +1,5 @@
|
|
1
1
|
module SimpleStateMachine
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
module Macro
|
8
|
-
def state_machine(column, states)
|
9
|
-
create_empty_state_machine unless inheritable_attributes.key? :states
|
10
|
-
inheritable_attributes[:states][column.to_sym] = states
|
11
|
-
validates_inclusion_of column, :in => states
|
12
|
-
# should also override getter/setter to convert to strings
|
13
|
-
self.class_eval <<-eos
|
14
|
-
def #{column.to_s}=(value)
|
15
|
-
self[:#{column.to_s}] = value.to_s
|
16
|
-
end
|
17
|
-
def #{column.to_s}
|
18
|
-
self[:#{column.to_s}].to_sym
|
19
|
-
end
|
20
|
-
def #{column.to_s}_revert
|
21
|
-
self[:#{column.to_s}] = self.new_record? ? states[:#{column.to_s}].first.to_s : self.#{column.to_s}_was
|
22
|
-
end
|
23
|
-
eos
|
24
|
-
|
25
|
-
# define a method {state_column}_{state}? for each state
|
26
|
-
states.each do |state|
|
27
|
-
self.class_eval <<-eos
|
28
|
-
def #{column.to_s}_#{state.to_s}?
|
29
|
-
self[:#{column.to_s}] === "#{state.to_s}"
|
30
|
-
end
|
31
|
-
eos
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def create_empty_state_machine
|
39
|
-
write_inheritable_attribute :states, {} # add a class variable
|
40
|
-
class_inheritable_reader :states # make it read-only
|
41
|
-
|
42
|
-
# set initial states on new objects
|
43
|
-
if(!instance_methods.include?("after_initialize") && ActiveRecord::VERSION::MAJOR < 3)
|
44
|
-
self.class_eval do
|
45
|
-
def after_initialize # ActiveRecord::Base requires explicit definition of this function to use the callback
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
after_initialize :set_initial_states
|
50
|
-
self.class_eval do
|
51
|
-
def set_initial_states
|
52
|
-
states.each {|column, states|
|
53
|
-
self[column] = states.first.to_s
|
54
|
-
} if new_record?
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
ActiveRecord::Base.class_eval do
|
62
|
-
include SimpleStateMachine
|
2
|
+
autoload :ActiveRecord, "simple_state_machine/active_record"
|
3
|
+
autoload :MongoMapper, "simple_state_machine/mongo_mapper"
|
4
|
+
autoload :Version, "simple_state_machine/version"
|
63
5
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "simple_state_machine/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "openhood-simple_state_machine"
|
7
|
+
s.version = SimpleStateMachine::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Joseph HALTER", "Jonathan TRON"]
|
10
|
+
s.email = "team@openhood.com"
|
11
|
+
s.homepage = "http://github.com/openhood/simple_state_machine"
|
12
|
+
s.summary = %q{Same as acts_as_state_machine but on multiple columns and with more strict validation, allow creation of complex events with parameters, used successfully on critical financial applications for quite a long time}
|
13
|
+
s.description = s.summary
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency("rake", ["~> 0.8.7"])
|
21
|
+
s.add_development_dependency("rspec", ["~> 2.6.0"])
|
22
|
+
s.add_development_dependency("sqlite3")
|
23
|
+
s.add_development_dependency("activerecord", [">= 3.0.0"])
|
24
|
+
s.add_development_dependency("mongo_mapper", ["~> 0.9.0"])
|
25
|
+
end
|
data/spec/db/schema.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class Chicken < ActiveRecord::Base
|
4
|
+
state_machine :user_state, [:pending, :active, :removed, :on_hold]
|
5
|
+
state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
|
6
|
+
def user_activate!
|
7
|
+
return false if !user_state_pending?
|
8
|
+
self.user_state = :active
|
9
|
+
save! rescue user_state_revert
|
10
|
+
user_state_active?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Chicken do
|
15
|
+
|
16
|
+
it "should set initial user_state" do
|
17
|
+
c = Chicken.new
|
18
|
+
c.user_state.should === :pending
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set initial validation_state" do
|
22
|
+
c = Chicken.new
|
23
|
+
c.validation_state.should === :waiting
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should store state value as string" do
|
27
|
+
c = Chicken.new
|
28
|
+
c[:user_state].should === 'pending'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should also accept strings as state values" do
|
32
|
+
c = Chicken.new
|
33
|
+
c.user_state = 'active'
|
34
|
+
c.user_state.should === :active
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should validate user_state stay inside possible states" do
|
38
|
+
c = Chicken.new
|
39
|
+
c.valid?.should === true
|
40
|
+
c.user_state = 'waiting'
|
41
|
+
c.valid?.should === false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should show all possible states in .states method" do
|
45
|
+
Chicken.states.should === {
|
46
|
+
:user_state => [:pending, :active, :removed, :on_hold],
|
47
|
+
:validation_state => [:waiting, :reviewed, :validated, :invalid]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return be able to return to previous state" do
|
52
|
+
c = Chicken.new
|
53
|
+
c.user_state.should === :pending
|
54
|
+
c.user_state = 'active'
|
55
|
+
c.user_state.should === :active
|
56
|
+
c.user_state_revert
|
57
|
+
c.user_state.should === :pending
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "custom event user_activate!" do
|
61
|
+
|
62
|
+
it "should return true if on pending state" do
|
63
|
+
c = Chicken.new
|
64
|
+
c.user_activate!.should === true
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should effectively change user_state" do
|
68
|
+
c = Chicken.new
|
69
|
+
c.user_activate!
|
70
|
+
c.user_state.should === :active
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not affect validation_state" do
|
74
|
+
c = Chicken.new
|
75
|
+
c.user_activate!
|
76
|
+
c.validation_state.should === :waiting
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return false if user_state is not pending" do
|
80
|
+
c = Chicken.new
|
81
|
+
c.user_state = :on_hold
|
82
|
+
c.user_activate!.should === false
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "magic methods to test current state" do
|
88
|
+
|
89
|
+
it "should handle the user_state_pending? method" do
|
90
|
+
c = Chicken.new
|
91
|
+
lambda {c.user_state_pending?}.should_not raise_error(NoMethodError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should evaluate user_state_pending? to true initially" do
|
95
|
+
c = Chicken.new
|
96
|
+
c.user_state_pending?.should === true
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should evaluate user_state_pending? to false after activation" do
|
100
|
+
c = Chicken.new
|
101
|
+
c.user_activate!
|
102
|
+
c.user_state_pending?.should === false
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should evaluate user_state_active? to false initially" do
|
106
|
+
c = Chicken.new
|
107
|
+
c.user_state_active?.should === false
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should evaluate user_state_active? to true after activation" do
|
111
|
+
c = Chicken.new
|
112
|
+
c.user_activate!
|
113
|
+
c.user_state_active?.should === true
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should not catch user_state_invalid?" do
|
117
|
+
c = Chicken.new
|
118
|
+
lambda {c.user_state_invalid?}.should raise_error(NoMethodError)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should handle validation_state_waiting?" do
|
122
|
+
c = Chicken.new
|
123
|
+
lambda {c.validation_state_waiting?}.should_not raise_error(NoMethodError)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should evaluate validation_state_waiting? to true initially" do
|
127
|
+
c = Chicken.new
|
128
|
+
c.validation_state_waiting?.should === true
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SimpleStateMachine::MongoMapper do
|
4
|
+
before do
|
5
|
+
@doc = Doc("Order") do
|
6
|
+
plugin SimpleStateMachine::MongoMapper
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".state_machine" do
|
11
|
+
it "should be defined" do
|
12
|
+
@doc.should respond_to(:state_machine)
|
13
|
+
end
|
14
|
+
it "should take 2 arguments" do
|
15
|
+
lambda do
|
16
|
+
@doc.state_machine
|
17
|
+
end.should raise_error(ArgumentError)
|
18
|
+
lambda do
|
19
|
+
@doc.state_machine :state, [:initial]
|
20
|
+
end.should_not raise_error(ArgumentError)
|
21
|
+
lambda do
|
22
|
+
@doc.state_machine 1, 1, 1
|
23
|
+
end.should raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when defining a state machine with :state, [:initial, :final]" do
|
28
|
+
before do
|
29
|
+
@doc.class_eval do
|
30
|
+
state_machine :state, [:initial, :final]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
subject { @doc.new }
|
34
|
+
it "should allow accessing states via states[:state]" do
|
35
|
+
subject.states[:state].should == [:initial, :final]
|
36
|
+
end
|
37
|
+
it "should add a :state key of type String on model" do
|
38
|
+
subject.key_names.should include("state")
|
39
|
+
subject.keys["state"].type.should == String
|
40
|
+
end
|
41
|
+
it "should set state on object initialization with first state" do
|
42
|
+
subject.state.should == :initial
|
43
|
+
end
|
44
|
+
it do
|
45
|
+
subject.should be_state_initial
|
46
|
+
end
|
47
|
+
it do
|
48
|
+
subject.should_not be_state_final
|
49
|
+
end
|
50
|
+
it "should allow reverting to previous state" do
|
51
|
+
subject.state = :final
|
52
|
+
subject.should be_state_final
|
53
|
+
subject.state_revert
|
54
|
+
subject.should be_state_initial
|
55
|
+
end
|
56
|
+
it "should allow setting state via a String" do
|
57
|
+
subject.state = "final"
|
58
|
+
subject.should be_state_final
|
59
|
+
end
|
60
|
+
it "should allow setting state via a Symbol" do
|
61
|
+
subject.state = :final
|
62
|
+
subject.should be_state_final
|
63
|
+
end
|
64
|
+
it "should return a symbol for state" do
|
65
|
+
subject.state = "final"
|
66
|
+
subject.state.should == :final
|
67
|
+
subject.state = :final
|
68
|
+
subject.state.should == :final
|
69
|
+
end
|
70
|
+
it "should validate state is included in allowed state" do
|
71
|
+
subject.state = :unknown_state
|
72
|
+
subject.valid?
|
73
|
+
subject.errors[:state].should == ["is not included in the list"]
|
74
|
+
subject.state = :final
|
75
|
+
subject.valid?
|
76
|
+
subject.errors[:state].should == []
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "when defining a second state machine with :delivery_state, [:initial, :final]" do
|
80
|
+
before do
|
81
|
+
@doc.class_eval do
|
82
|
+
state_machine :delivery_state, [:initial, :final]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
subject { @doc.new }
|
86
|
+
it "should allow accessing states via states[:delivery_state]" do
|
87
|
+
subject.states[:delivery_state].should == [:initial, :final]
|
88
|
+
end
|
89
|
+
it "should add a :delivery_state key of type String on model" do
|
90
|
+
subject.key_names.should include("delivery_state")
|
91
|
+
subject.keys["delivery_state"].type.should == String
|
92
|
+
end
|
93
|
+
it "should set delivery_state on object initialization with first delivery_state" do
|
94
|
+
subject.delivery_state.should == :initial
|
95
|
+
end
|
96
|
+
it do
|
97
|
+
subject.should be_delivery_state_initial
|
98
|
+
end
|
99
|
+
it do
|
100
|
+
subject.should_not be_delivery_state_final
|
101
|
+
end
|
102
|
+
it "should allow reverting to previous state" do
|
103
|
+
subject.delivery_state = :final
|
104
|
+
subject.should be_delivery_state_final
|
105
|
+
subject.delivery_state_revert
|
106
|
+
subject.should be_delivery_state_initial
|
107
|
+
end
|
108
|
+
it "should not revert other state machine on reverting to previous state" do
|
109
|
+
subject.state = :final
|
110
|
+
subject.delivery_state = :final
|
111
|
+
subject.should be_state_final
|
112
|
+
subject.should be_delivery_state_final
|
113
|
+
subject.delivery_state_revert
|
114
|
+
subject.should be_state_final
|
115
|
+
subject.should be_delivery_state_initial
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "mongo_mapper"
|
3
|
+
require "simple_state_machine"
|
4
|
+
require "simple_state_machine/active_record"
|
5
|
+
require "simple_state_machine/mongo_mapper"
|
6
|
+
|
7
|
+
Dir[File.expand_path("../support/*.rb", __FILE__)].each{|f| require f}
|
8
|
+
|
9
|
+
log_dir = File.expand_path('../../log', __FILE__)
|
10
|
+
FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
|
11
|
+
logger = Logger.new(log_dir + '/test.log')
|
12
|
+
|
13
|
+
ActiveRecord::Base.logger = logger
|
14
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
15
|
+
load File.expand_path("../db/schema.rb", __FILE__)
|
16
|
+
|
17
|
+
|
18
|
+
MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => logger)
|
19
|
+
MongoMapper.database = "simple_state_machine_test"
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.before(:each) do
|
23
|
+
MongoMapper.database.collections.each { |c| c.drop_indexes }
|
24
|
+
end
|
25
|
+
config.include SimpleStateMachine::RSpec::Helpers
|
26
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module RSpec
|
3
|
+
module Helpers
|
4
|
+
def Doc(name=nil, &block)
|
5
|
+
klass = Class.new do
|
6
|
+
include ::MongoMapper::Document
|
7
|
+
set_collection_name "test#{rand(20)}"
|
8
|
+
|
9
|
+
if name
|
10
|
+
class_eval "def self.name; '#{name}' end"
|
11
|
+
class_eval "def self.to_s; '#{name}' end"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
klass.class_eval(&block) if block_given?
|
16
|
+
klass.collection.remove
|
17
|
+
klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def EDoc(name=nil, &block)
|
21
|
+
klass = Class.new do
|
22
|
+
include ::MongoMapper::EmbeddedDocument
|
23
|
+
|
24
|
+
if name
|
25
|
+
class_eval "def self.name; '#{name}' end"
|
26
|
+
class_eval "def self.to_s; '#{name}' end"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
klass.class_eval(&block) if block_given?
|
31
|
+
klass
|
32
|
+
end
|
33
|
+
|
34
|
+
::RSpec::Matchers.define :have_timestamps do |name, states = []|
|
35
|
+
description { "have keys created_at/updated_at of type Time" }
|
36
|
+
match do |actual|
|
37
|
+
keys = actual.keys.select{|key_name, key| ["created_at", "updated_at"].include?(key_name) && key.type==Time}
|
38
|
+
keys.size == 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
::RSpec::Matchers.define :have_simple_state_machine do |name, states = []|
|
43
|
+
description { "define a state machine \"#{name}\" with #{states.join(",")} states" }
|
44
|
+
match do |actual|
|
45
|
+
actual.states[name].eql?(states)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
::RSpec::Matchers.define :have_scope do |name, options = {}|
|
50
|
+
query = Plucky::Query.new(actual.class, options)
|
51
|
+
description do
|
52
|
+
s = "have a scope #{name} with"
|
53
|
+
s << " #{query.criteria.source} criterias" if query.criteria.source.present?
|
54
|
+
s << " and " if query.criteria.source.present? && query.options.source.present?
|
55
|
+
s << " #{query.options.source.inspect} options" if query.options.source.present?
|
56
|
+
s
|
57
|
+
end
|
58
|
+
match do |actual|
|
59
|
+
actual.class.scopes[name] &&
|
60
|
+
actual.class.scopes[name].call.criteria == query.criteria &&
|
61
|
+
actual.class.scopes[name].call.options == query.options
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
::RSpec::Matchers.define :have_key do |name, klass, options = {}|
|
66
|
+
description do
|
67
|
+
s = "have key #{name} of class #{klass.name}"
|
68
|
+
s << " with #{options[:default]} as default value" if options.has_key?(:default)
|
69
|
+
s
|
70
|
+
end
|
71
|
+
match do |actual|
|
72
|
+
key = actual.keys.detect{|key_name, key| key_name==name.to_s && key.type==klass}
|
73
|
+
valid = !!key
|
74
|
+
valid &&= key.last.default_value.eql?(options[:default]) if options.has_key?(:default)
|
75
|
+
valid
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
::RSpec::Matchers.define :have_index do |*keys|
|
80
|
+
description{ "have an index on #{keys.join(", ")}" }
|
81
|
+
match do |actual|
|
82
|
+
indexes = actual.collection.index_information.values.collect{|h| h["key"].keys}
|
83
|
+
indexes.include? keys.collect(&:to_s)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
::RSpec::Matchers.define :have_many do |name, options = {}|
|
88
|
+
description do
|
89
|
+
s = "have many "
|
90
|
+
s << "embedded " if options[:embedded]
|
91
|
+
s << "#{name}"
|
92
|
+
s << " of class #{options[:class].name}" if options[:class]
|
93
|
+
s << " ordered by '#{options[:order]}'" if options[:order]
|
94
|
+
s
|
95
|
+
end
|
96
|
+
match do |actual, matcher|
|
97
|
+
actual.associations.one? do |association_name, assocation|
|
98
|
+
valid = association_name==name.to_s && assocation.type==:many
|
99
|
+
valid &&= assocation.klass==options[:class] if options[:class]
|
100
|
+
valid &&= !!options[:embedded]==actual.embedded_associations.include?(assocation)
|
101
|
+
valid &&= !!options[:order]==assocation.query_options[:order] if options[:order]
|
102
|
+
valid
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
::RSpec::Matchers.define :have_one do |name, options = {}|
|
108
|
+
description do
|
109
|
+
s = "have one "
|
110
|
+
s << "embedded " if options[:embedded]
|
111
|
+
s << "#{name}"
|
112
|
+
s << " of class #{options[:class].name}" if options[:class]
|
113
|
+
s
|
114
|
+
end
|
115
|
+
match do |actual, matcher|
|
116
|
+
actual.associations.one? do |association_name, assocation|
|
117
|
+
valid = association_name==name.to_s && assocation.type==:one
|
118
|
+
valid &&= assocation.klass==options[:class] if options[:class]
|
119
|
+
valid &&= !!options[:embedded]==actual.embedded_associations.include?(assocation)
|
120
|
+
valid
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
::RSpec::Matchers.define :belong_to do |name, options = {}|
|
126
|
+
description do
|
127
|
+
s = "belong to #{name}"
|
128
|
+
s << " of class #{options[:class].name}" if options[:class]
|
129
|
+
s
|
130
|
+
end
|
131
|
+
match do |actual, matcher|
|
132
|
+
actual.associations.one? do |association_name, assocation|
|
133
|
+
valid = association_name==name.to_s && assocation.type==:belongs_to
|
134
|
+
valid &&= assocation.klass==options[:class] if options[:class]
|
135
|
+
valid
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
::RSpec::Matchers.define :validate_presence_of do |field, options = {}|
|
141
|
+
description do
|
142
|
+
s = "validate presence of #{field}"
|
143
|
+
s << " while still allowing nil" if options[:allow_nil]
|
144
|
+
s << " for #{options[:groups]} validations" if options[:groups]
|
145
|
+
s
|
146
|
+
end
|
147
|
+
match do |actual, matcher|
|
148
|
+
validation_method = options[:groups] ? :"valid_for_#{options[:groups]}?" : :valid?
|
149
|
+
actual.send "#{field}=", ""
|
150
|
+
actual.send validation_method
|
151
|
+
valid = !actual.errors[field].empty?
|
152
|
+
if actual.keys[field] && actual.keys[field].default_value.nil?
|
153
|
+
actual.send "#{field}=", nil
|
154
|
+
actual.send validation_method
|
155
|
+
valid && !!options[:allow_nil]==actual.errors[field].empty?
|
156
|
+
end
|
157
|
+
valid
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
::RSpec::Matchers.define :validate_length_of do |field, options = {}|
|
162
|
+
min, max = options[:within].first, options[:within].last
|
163
|
+
description do
|
164
|
+
"validate length of #{field} within #{min}..#{max}"
|
165
|
+
end
|
166
|
+
match do |actual, matcher|
|
167
|
+
actual.send "#{field}=", min.times.collect{"a"}.join
|
168
|
+
actual.valid?
|
169
|
+
valid = !actual.errors[field].include?("is invalid")
|
170
|
+
actual.send "#{field}=", (min-1).times.collect{"a"}.join
|
171
|
+
actual.valid?
|
172
|
+
valid &&= actual.errors[field].include?("is invalid")
|
173
|
+
actual.send "#{field}=", (max+1).times.collect{"a"}.join
|
174
|
+
actual.valid?
|
175
|
+
valid &&= actual.errors[field].include?("is invalid")
|
176
|
+
valid
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
::RSpec::Matchers.define :validate_inclusion_of do |field, options = {}|
|
181
|
+
min, max = options[:within].first, options[:within].last
|
182
|
+
description do
|
183
|
+
"validate inclusion of #{field} within #{min}..#{max}"
|
184
|
+
end
|
185
|
+
match do |actual, matcher|
|
186
|
+
actual.send "#{field}=", min
|
187
|
+
actual.valid?
|
188
|
+
valid = !actual.errors[field].include?("is not in the list")
|
189
|
+
actual.send "#{field}=", min.pred
|
190
|
+
actual.valid?
|
191
|
+
valid &&= actual.errors[field].include?("is not in the list")
|
192
|
+
actual.send "#{field}=", max.succ
|
193
|
+
actual.valid?
|
194
|
+
valid && actual.errors[field].include?("is not in the list")
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
::RSpec::Matchers.define :validate_confirmation_of do |field|
|
199
|
+
description { "validate confirmation of #{field}" }
|
200
|
+
match do |actual, matcher|
|
201
|
+
field_confirmation = "#{field}_confirmation"
|
202
|
+
actual.send "#{field}=", "aaaa"
|
203
|
+
actual.send "#{field_confirmation}=", "aaaa"
|
204
|
+
actual.valid?
|
205
|
+
valid = !actual.errors[field].include?("doesn't match confirmation")
|
206
|
+
actual.send "#{field}=", "aaaa"
|
207
|
+
actual.send "#{field}=", "bbbb"
|
208
|
+
actual.valid?
|
209
|
+
valid && actual.errors[field].include?("doesn't match confirmation")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
::RSpec::Matchers.define :validate_uniqueness_of do |field, options = {}|
|
214
|
+
description do
|
215
|
+
s = "validate uniqueness of #{field}"
|
216
|
+
s << " case sensitive" if options[:case_sensitive]
|
217
|
+
s << " case insensitive" unless options[:case_sensitive]
|
218
|
+
s << " allowing blank value" if options[:allow_blank]
|
219
|
+
s << " allowing nil value" if options[:allow_nil]
|
220
|
+
s
|
221
|
+
end
|
222
|
+
match do |actual, matcher|
|
223
|
+
existing = actual.class.first
|
224
|
+
raise "you need to create at least one record before we can check for uniqueness" unless existing
|
225
|
+
actual.attributes = existing.attributes.reject{|k,v| k == "_id" }
|
226
|
+
actual.valid?
|
227
|
+
valid = actual.errors[field].include?("has already been taken")
|
228
|
+
actual.send "#{field}=", actual.send(field).swapcase
|
229
|
+
actual.valid?
|
230
|
+
if options[:case_sensitive]
|
231
|
+
valid &&= !actual.errors[field].include?("has already been taken")
|
232
|
+
else
|
233
|
+
valid &&= actual.errors[field].include?("has already been taken")
|
234
|
+
end
|
235
|
+
if options[:allow_blank]
|
236
|
+
actual.send "#{field}=", ""
|
237
|
+
actual.valid?
|
238
|
+
valid &&= !actual.errors[field].include?("has already been taken")
|
239
|
+
end
|
240
|
+
if options[:allow_nil]
|
241
|
+
actual.send "#{field}=", nil
|
242
|
+
actual.valid?
|
243
|
+
valid &&= !actual.errors[field].include?("has already been taken")
|
244
|
+
end
|
245
|
+
valid
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
::RSpec::Matchers.define :validate_config_with do |key, klass|
|
250
|
+
description{ "validate presence of #{key} of class #{klass} in config" }
|
251
|
+
match do |actual, matcher|
|
252
|
+
actual.config.delete key
|
253
|
+
actual.valid?
|
254
|
+
valid = actual.errors[:config].include? "#{key} is required"
|
255
|
+
actual.config[key] = Class.new
|
256
|
+
actual.valid?
|
257
|
+
valid &&= actual.errors[:config].include? "#{key} should be #{klass}"
|
258
|
+
value = mock
|
259
|
+
value.stub(:kind_of?).with(klass).and_return true
|
260
|
+
actual.config[key] = value
|
261
|
+
actual.valid?
|
262
|
+
valid &&= actual.errors[:config].none?{|error| error.starts_with?("#{key} ")}
|
263
|
+
valid
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
metadata
CHANGED
@@ -1,75 +1,128 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: openhood-simple_state_machine
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 1
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
version: 1.0.1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
12
|
-
- Joseph
|
7
|
+
authors:
|
8
|
+
- Joseph HALTER
|
9
|
+
- Jonathan TRON
|
13
10
|
autorequire:
|
14
11
|
bindir: bin
|
15
12
|
cert_chain: []
|
16
|
-
|
17
|
-
date: 2010-11-24 00:00:00 +01:00
|
13
|
+
date: 2011-06-22 00:00:00.000000000 +02:00
|
18
14
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rake
|
18
|
+
requirement: &2161201240 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.7
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *2161201240
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: &2161198760 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.6.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *2161198760
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: sqlite3
|
40
|
+
requirement: &2161197360 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *2161197360
|
49
|
+
- !ruby/object:Gem::Dependency
|
21
50
|
name: activerecord
|
51
|
+
requirement: &2161195840 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 3.0.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *2161195840
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: mongo_mapper
|
62
|
+
requirement: &2161188280 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.9.0
|
68
|
+
type: :development
|
22
69
|
prerelease: false
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
segments:
|
28
|
-
- 2
|
29
|
-
- 2
|
30
|
-
- 2
|
31
|
-
version: 2.2.2
|
32
|
-
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
description: Same as acts_as_state_machine but on multiple columns and with more strict validation, allow creation of complex events with parameters, used successfully on critical financial applications for quite a long time
|
70
|
+
version_requirements: *2161188280
|
71
|
+
description: Same as acts_as_state_machine but on multiple columns and with more strict
|
72
|
+
validation, allow creation of complex events with parameters, used successfully
|
73
|
+
on critical financial applications for quite a long time
|
35
74
|
email: team@openhood.com
|
36
75
|
executables: []
|
37
|
-
|
38
76
|
extensions: []
|
39
|
-
|
40
77
|
extra_rdoc_files: []
|
41
|
-
|
42
|
-
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- MIT-LICENSE
|
83
|
+
- README.textile
|
84
|
+
- Rakefile
|
85
|
+
- init.rb
|
43
86
|
- lib/simple_state_machine.rb
|
87
|
+
- lib/simple_state_machine/active_record.rb
|
88
|
+
- lib/simple_state_machine/mongo_mapper.rb
|
89
|
+
- lib/simple_state_machine/version.rb
|
90
|
+
- simple_state_machine.gemspec
|
91
|
+
- spec/db/schema.rb
|
92
|
+
- spec/simple_state_machine/active_record_spec.rb
|
93
|
+
- spec/simple_state_machine/mongo_mapper_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
- spec/support/mongo_mapper_helper.rb
|
44
96
|
has_rdoc: true
|
45
97
|
homepage: http://github.com/openhood/simple_state_machine
|
46
98
|
licenses: []
|
47
|
-
|
48
99
|
post_install_message:
|
49
100
|
rdoc_options: []
|
50
|
-
|
51
|
-
require_paths:
|
101
|
+
require_paths:
|
52
102
|
- lib
|
53
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
requirements:
|
62
|
-
- -
|
63
|
-
- !ruby/object:Gem::Version
|
64
|
-
|
65
|
-
- 0
|
66
|
-
version: "0"
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
67
115
|
requirements: []
|
68
|
-
|
69
116
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.
|
117
|
+
rubygems_version: 1.6.2
|
71
118
|
signing_key:
|
72
119
|
specification_version: 3
|
73
|
-
summary: Same as acts_as_state_machine but on multiple columns and with more strict
|
74
|
-
|
75
|
-
|
120
|
+
summary: Same as acts_as_state_machine but on multiple columns and with more strict
|
121
|
+
validation, allow creation of complex events with parameters, used successfully
|
122
|
+
on critical financial applications for quite a long time
|
123
|
+
test_files:
|
124
|
+
- spec/db/schema.rb
|
125
|
+
- spec/simple_state_machine/active_record_spec.rb
|
126
|
+
- spec/simple_state_machine/mongo_mapper_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
- spec/support/mongo_mapper_helper.rb
|