aasm 3.0.16 → 3.0.17
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/.travis.yml +2 -1
- data/API +34 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -1
- data/HOWTO +12 -0
- data/README.md +57 -4
- data/aasm.gemspec +2 -0
- data/lib/aasm.rb +5 -4
- data/lib/aasm/aasm.rb +50 -75
- data/lib/aasm/base.rb +22 -18
- data/lib/aasm/event.rb +130 -0
- data/lib/aasm/instance_base.rb +87 -0
- data/lib/aasm/localizer.rb +54 -0
- data/lib/aasm/persistence.rb +22 -14
- data/lib/aasm/persistence/active_record_persistence.rb +38 -69
- data/lib/aasm/persistence/base.rb +42 -2
- data/lib/aasm/persistence/mongoid_persistence.rb +33 -64
- data/lib/aasm/state.rb +78 -0
- data/lib/aasm/state_machine.rb +2 -2
- data/lib/aasm/transition.rb +49 -0
- data/lib/aasm/version.rb +1 -1
- data/spec/models/active_record/api.rb +75 -0
- data/spec/models/auth_machine.rb +1 -1
- data/spec/models/bar.rb +15 -0
- data/spec/models/foo.rb +34 -0
- data/spec/models/mongoid/simple_mongoid.rb +10 -0
- data/spec/models/mongoid/{mongoid_models.rb → simple_new_dsl_mongoid.rb} +1 -12
- data/spec/models/persistence.rb +2 -1
- data/spec/models/this_name_better_not_be_in_use.rb +11 -0
- data/spec/schema.rb +1 -1
- data/spec/spec_helper.rb +8 -1
- data/spec/unit/api_spec.rb +72 -0
- data/spec/unit/callbacks_spec.rb +2 -2
- data/spec/unit/event_spec.rb +269 -0
- data/spec/unit/inspection_spec.rb +43 -5
- data/spec/unit/{supporting_classes/localizer_spec.rb → localizer_spec.rb} +2 -2
- data/spec/unit/memory_leak_spec.rb +12 -12
- data/spec/unit/persistence/active_record_persistence_spec.rb +0 -40
- data/spec/unit/persistence/mongoid_persistance_spec.rb +3 -2
- data/spec/unit/simple_example_spec.rb +6 -0
- data/spec/unit/{supporting_classes/state_spec.rb → state_spec.rb} +2 -2
- data/spec/unit/{supporting_classes/state_transition_spec.rb → transition_spec.rb} +18 -18
- metadata +127 -38
- data/lib/aasm/persistence/read_state.rb +0 -40
- data/lib/aasm/supporting_classes/event.rb +0 -146
- data/lib/aasm/supporting_classes/localizer.rb +0 -56
- data/lib/aasm/supporting_classes/state.rb +0 -80
- data/lib/aasm/supporting_classes/state_transition.rb +0 -51
- data/spec/spec_helpers/models_spec_helper.rb +0 -64
- data/spec/unit/supporting_classes/event_spec.rb +0 -203
@@ -2,6 +2,44 @@ module AASM
|
|
2
2
|
module Persistence
|
3
3
|
module Base
|
4
4
|
|
5
|
+
def self.included(base) #:nodoc:
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the value of the aasm_column - called from <tt>aasm.current_state</tt>
|
10
|
+
#
|
11
|
+
# If it's a new record, and the aasm state column is blank it returns the initial state
|
12
|
+
# (example provided here for ActiveRecord, but it's true for Mongoid as well):
|
13
|
+
#
|
14
|
+
# class Foo < ActiveRecord::Base
|
15
|
+
# include AASM
|
16
|
+
# aasm :column => :status do
|
17
|
+
# state :opened
|
18
|
+
# state :closed
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# foo = Foo.new
|
23
|
+
# foo.current_state # => :opened
|
24
|
+
# foo.close
|
25
|
+
# foo.current_state # => :closed
|
26
|
+
#
|
27
|
+
# foo = Foo.find(1)
|
28
|
+
# foo.current_state # => :opened
|
29
|
+
# foo.aasm_state = nil
|
30
|
+
# foo.current_state # => nil
|
31
|
+
#
|
32
|
+
# NOTE: intended to be called from an event
|
33
|
+
#
|
34
|
+
# This allows for nil aasm states - be sure to add validation to your model
|
35
|
+
def aasm_read_state
|
36
|
+
if new_record?
|
37
|
+
send(self.class.aasm_column).blank? ? aasm.determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
|
38
|
+
else
|
39
|
+
send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
5
43
|
module ClassMethods
|
6
44
|
# Maps to the aasm_column in the database. Defaults to "aasm_state". You can write
|
7
45
|
# (example provided here for ActiveRecord, but it's true for Mongoid as well):
|
@@ -39,18 +77,20 @@ module AASM
|
|
39
77
|
# @aasm_column
|
40
78
|
AASM::StateMachine[self].config.column
|
41
79
|
end
|
42
|
-
end
|
80
|
+
end # ClassMethods
|
43
81
|
|
44
82
|
end # Base
|
45
83
|
end # Persistence
|
46
84
|
|
47
85
|
class Base
|
86
|
+
# make sure to create a (named) scope for each state
|
48
87
|
def state_with_scope(name, *args)
|
49
88
|
state_without_scope(name, *args)
|
50
89
|
unless @clazz.respond_to?(name)
|
51
90
|
if @clazz.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
|
52
|
-
|
91
|
+
scope_options_hash = {:conditions => { "#{@clazz.table_name}.#{@clazz.aasm_column}" => name.to_s}}
|
53
92
|
scope_method = ActiveRecord::VERSION::MAJOR >= 3 ? :scope : :named_scope
|
93
|
+
scope_options = ActiveRecord::VERSION::MAJOR >= 4 ? lambda { where(scope_options_hash[:conditions])} : scope_options_hash
|
54
94
|
@clazz.send(scope_method, name, scope_options)
|
55
95
|
elsif @clazz.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
|
56
96
|
scope_options = lambda { @clazz.send(:where, {@clazz.aasm_column.to_sym => name.to_s}) }
|
@@ -6,11 +6,6 @@ module AASM
|
|
6
6
|
# * extends the model with ClassMethods
|
7
7
|
# * includes InstanceMethods
|
8
8
|
#
|
9
|
-
# Unless the corresponding methods are already defined, it includes
|
10
|
-
# * ReadState
|
11
|
-
# * WriteState
|
12
|
-
# * WriteStateWithoutPersistence
|
13
|
-
#
|
14
9
|
# Adds
|
15
10
|
#
|
16
11
|
# before_validation :aasm_ensure_initial_state
|
@@ -34,12 +29,9 @@ module AASM
|
|
34
29
|
# end
|
35
30
|
#
|
36
31
|
def self.included(base)
|
37
|
-
base.
|
32
|
+
base.send(:include, AASM::Persistence::Base)
|
38
33
|
base.extend AASM::Persistence::MongoidPersistence::ClassMethods
|
39
34
|
base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
|
40
|
-
base.send(:include, AASM::Persistence::ReadState) unless base.method_defined?(:aasm_read_state)
|
41
|
-
base.send(:include, AASM::Persistence::MongoidPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
|
42
|
-
base.send(:include, AASM::Persistence::MongoidPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
|
43
35
|
|
44
36
|
# Mongoid's Validatable gem dependency goes not have a before_validation_on_xxx hook yet.
|
45
37
|
# base.before_validation_on_create :aasm_ensure_initial_state
|
@@ -70,48 +62,28 @@ module AASM
|
|
70
62
|
|
71
63
|
module InstanceMethods
|
72
64
|
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# Internally just calls <tt>aasm_read_state</tt>
|
65
|
+
# Writes <tt>state</tt> to the state column and persists it to the database
|
66
|
+
# using update_attribute (which bypasses validation)
|
77
67
|
#
|
78
68
|
# foo = Foo.find(1)
|
79
|
-
# foo.aasm_current_state # => :pending
|
80
|
-
# foo.aasm_state = "opened"
|
81
69
|
# foo.aasm_current_state # => :opened
|
82
|
-
# foo.close
|
70
|
+
# foo.close!
|
83
71
|
# foo.aasm_current_state # => :closed
|
84
|
-
#
|
85
|
-
# foo.aasm_current_state # => :pending
|
72
|
+
# Foo.find(1).aasm_current_state # => :closed
|
86
73
|
#
|
87
|
-
|
88
|
-
|
89
|
-
|
74
|
+
# NOTE: intended to be called from an event
|
75
|
+
def aasm_write_state(state)
|
76
|
+
old_value = read_attribute(self.class.aasm_column)
|
77
|
+
write_attribute(self.class.aasm_column, state.to_s)
|
90
78
|
|
91
|
-
|
79
|
+
unless self.save(:validate => false)
|
80
|
+
write_attribute(self.class.aasm_column, old_value)
|
81
|
+
return false
|
82
|
+
end
|
92
83
|
|
93
|
-
|
94
|
-
# that the initial state gets populated before validation on create
|
95
|
-
#
|
96
|
-
# foo = Foo.new
|
97
|
-
# foo.aasm_state # => nil
|
98
|
-
# foo.valid?
|
99
|
-
# foo.aasm_state # => "open" (where :open is the initial state)
|
100
|
-
#
|
101
|
-
#
|
102
|
-
# foo = Foo.find(:first)
|
103
|
-
# foo.aasm_state # => 1
|
104
|
-
# foo.aasm_state = nil
|
105
|
-
# foo.valid?
|
106
|
-
# foo.aasm_state # => nil
|
107
|
-
#
|
108
|
-
def aasm_ensure_initial_state
|
109
|
-
send("#{self.class.aasm_column}=", self.aasm_enter_initial_state.to_s) if send(self.class.aasm_column).blank?
|
84
|
+
true
|
110
85
|
end
|
111
86
|
|
112
|
-
end
|
113
|
-
|
114
|
-
module WriteStateWithoutPersistence
|
115
87
|
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
116
88
|
#
|
117
89
|
# foo = Foo.find(1)
|
@@ -127,31 +99,28 @@ module AASM
|
|
127
99
|
def aasm_write_state_without_persistence(state)
|
128
100
|
write_attribute(self.class.aasm_column, state.to_s)
|
129
101
|
end
|
130
|
-
end
|
131
102
|
|
132
|
-
|
133
|
-
|
134
|
-
#
|
103
|
+
private
|
104
|
+
|
105
|
+
# Ensures that if the aasm_state column is nil and the record is new
|
106
|
+
# that the initial state gets populated before validation on create
|
135
107
|
#
|
136
|
-
# foo = Foo.
|
137
|
-
# foo.
|
138
|
-
# foo.
|
139
|
-
# foo.
|
140
|
-
# Foo.find(1).aasm_current_state # => :closed
|
108
|
+
# foo = Foo.new
|
109
|
+
# foo.aasm_state # => nil
|
110
|
+
# foo.valid?
|
111
|
+
# foo.aasm_state # => "open" (where :open is the initial state)
|
141
112
|
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
true
|
113
|
+
#
|
114
|
+
# foo = Foo.find(:first)
|
115
|
+
# foo.aasm_state # => 1
|
116
|
+
# foo.aasm_state = nil
|
117
|
+
# foo.valid?
|
118
|
+
# foo.aasm_state # => nil
|
119
|
+
#
|
120
|
+
def aasm_ensure_initial_state
|
121
|
+
send("#{self.class.aasm_column}=", aasm.enter_initial_state.to_s) if send(self.class.aasm_column).blank?
|
153
122
|
end
|
154
|
-
end
|
123
|
+
end # InstanceMethods
|
155
124
|
|
156
125
|
module NamedScopeMethods
|
157
126
|
def aasm_state_with_named_scope name, options = {}
|
@@ -161,4 +130,4 @@ module AASM
|
|
161
130
|
end
|
162
131
|
end
|
163
132
|
end
|
164
|
-
end
|
133
|
+
end
|
data/lib/aasm/state.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module AASM
|
2
|
+
class State
|
3
|
+
attr_reader :name, :options
|
4
|
+
|
5
|
+
def initialize(name, clazz, options={})
|
6
|
+
@name = name
|
7
|
+
@clazz = clazz
|
8
|
+
update(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(state)
|
12
|
+
if state.is_a? Symbol
|
13
|
+
name == state
|
14
|
+
else
|
15
|
+
name == state.name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(state)
|
20
|
+
if state.is_a? Symbol
|
21
|
+
name <=> state
|
22
|
+
else
|
23
|
+
name <=> state.name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
name.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def fire_callbacks(action, record)
|
32
|
+
action = @options[action]
|
33
|
+
catch :halt_aasm_chain do
|
34
|
+
action.is_a?(Array) ?
|
35
|
+
action.each {|a| _fire_callbacks(a, record)} :
|
36
|
+
_fire_callbacks(action, record)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def display_name
|
41
|
+
@display_name ||= begin
|
42
|
+
if Module.const_defined?(:I18n)
|
43
|
+
localized_name
|
44
|
+
else
|
45
|
+
name.to_s.gsub(/_/, ' ').capitalize
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def localized_name
|
51
|
+
AASM::Localizer.new.human_state_name(@clazz, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def for_select
|
55
|
+
[display_name, name.to_s]
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def update(options = {})
|
61
|
+
if options.key?(:display) then
|
62
|
+
@display_name = options.delete(:display)
|
63
|
+
end
|
64
|
+
@options = options
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def _fire_callbacks(action, record)
|
69
|
+
case action
|
70
|
+
when Symbol, String
|
71
|
+
record.send(action)
|
72
|
+
when Proc
|
73
|
+
action.call(record)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end # AASM
|
data/lib/aasm/state_machine.rb
CHANGED
@@ -29,8 +29,8 @@ module AASM
|
|
29
29
|
@events = @events.dup
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
@states << AASM::
|
32
|
+
def add_state(name, clazz, options)
|
33
|
+
@states << AASM::State.new(name, clazz, options) unless @states.include?(name)
|
34
34
|
end
|
35
35
|
|
36
36
|
end # StateMachine
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module AASM
|
2
|
+
class Transition
|
3
|
+
attr_reader :from, :to, :opts
|
4
|
+
alias_method :options, :opts
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
8
|
+
@opts = opts
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: should be named allowed? or similar
|
12
|
+
def perform(obj, *args)
|
13
|
+
case @guard
|
14
|
+
when Symbol, String
|
15
|
+
obj.send(@guard, *args)
|
16
|
+
when Proc
|
17
|
+
@guard.call(obj, *args)
|
18
|
+
else
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(obj, *args)
|
24
|
+
@on_transition.is_a?(Array) ?
|
25
|
+
@on_transition.each {|ot| _execute(obj, ot, *args)} :
|
26
|
+
_execute(obj, @on_transition, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(obj)
|
30
|
+
@from == obj.from && @to == obj.to
|
31
|
+
end
|
32
|
+
|
33
|
+
def from?(value)
|
34
|
+
@from == value
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def _execute(obj, on_transition, *args)
|
40
|
+
case on_transition
|
41
|
+
when Proc
|
42
|
+
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
|
43
|
+
when Symbol, String
|
44
|
+
obj.send(:method, on_transition.to_sym).arity == 0 ? obj.send(on_transition) : obj.send(on_transition, *args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end # AASM
|
data/lib/aasm/version.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
class DefaultState
|
2
|
+
attr_accessor :transient_store, :persisted_store
|
3
|
+
include AASM
|
4
|
+
aasm do
|
5
|
+
state :alpha, :initial => true
|
6
|
+
state :beta
|
7
|
+
state :gamma
|
8
|
+
event :release do
|
9
|
+
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ProvidedState
|
15
|
+
attr_accessor :transient_store, :persisted_store
|
16
|
+
include AASM
|
17
|
+
aasm do
|
18
|
+
state :alpha, :initial => true
|
19
|
+
state :beta
|
20
|
+
state :gamma
|
21
|
+
event :release do
|
22
|
+
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def aasm_read_state
|
27
|
+
:beta
|
28
|
+
end
|
29
|
+
|
30
|
+
def aasm_write_state(new_state)
|
31
|
+
@persisted_store = new_state
|
32
|
+
end
|
33
|
+
|
34
|
+
def aasm_write_state_without_persistence(new_state)
|
35
|
+
@transient_store = new_state
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class PersistedState < ActiveRecord::Base
|
40
|
+
attr_accessor :transient_store, :persisted_store
|
41
|
+
include AASM
|
42
|
+
aasm do
|
43
|
+
state :alpha, :initial => true
|
44
|
+
state :beta
|
45
|
+
state :gamma
|
46
|
+
event :release do
|
47
|
+
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ProvidedAndPersistedState < ActiveRecord::Base
|
53
|
+
attr_accessor :transient_store, :persisted_store
|
54
|
+
include AASM
|
55
|
+
aasm do
|
56
|
+
state :alpha, :initial => true
|
57
|
+
state :beta
|
58
|
+
state :gamma
|
59
|
+
event :release do
|
60
|
+
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def aasm_read_state
|
65
|
+
:gamma
|
66
|
+
end
|
67
|
+
|
68
|
+
def aasm_write_state(new_state)
|
69
|
+
@persisted_store = new_state
|
70
|
+
end
|
71
|
+
|
72
|
+
def aasm_write_state_without_persistence(new_state)
|
73
|
+
@transient_store = new_state
|
74
|
+
end
|
75
|
+
end
|
data/spec/models/auth_machine.rb
CHANGED
@@ -46,7 +46,7 @@ class AuthMachine
|
|
46
46
|
def initialize
|
47
47
|
# the AR backend uses a before_validate_on_create :aasm_ensure_initial_state
|
48
48
|
# lets do something similar here for testing purposes.
|
49
|
-
|
49
|
+
aasm.enter_initial_state
|
50
50
|
end
|
51
51
|
|
52
52
|
def make_activation_code
|