aasm 2.4.0 → 3.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/CHANGELOG.md +6 -0
- data/README.md +33 -35
- data/lib/aasm.rb +10 -5
- data/lib/aasm/aasm.rb +41 -41
- data/lib/aasm/base.rb +9 -3
- data/lib/aasm/deprecated/aasm.rb +15 -0
- data/lib/aasm/errors.rb +4 -0
- data/lib/aasm/persistence.rb +12 -11
- data/lib/aasm/state_machine.rb +27 -25
- data/lib/aasm/supporting_classes/event.rb +132 -0
- data/lib/aasm/supporting_classes/localizer.rb +40 -0
- data/lib/aasm/supporting_classes/state.rb +57 -0
- data/lib/aasm/supporting_classes/state_transition.rb +50 -0
- data/lib/aasm/version.rb +1 -1
- data/spec/models/conversation.rb +21 -21
- data/spec/models/silencer.rb +17 -0
- data/spec/spec_helpers/models_spec_helper.rb +83 -72
- data/spec/unit/aasm_spec.rb +4 -18
- data/spec/unit/active_record_persistence_spec.rb +6 -4
- data/spec/unit/event_spec.rb +2 -2
- data/spec/unit/localizer_spec.rb +7 -7
- data/spec/unit/state_spec.rb +5 -5
- data/spec/unit/state_transition_spec.rb +22 -6
- metadata +12 -9
- data/lib/aasm/event.rb +0 -127
- data/lib/aasm/localizer.rb +0 -36
- data/lib/aasm/state.rb +0 -53
- data/lib/aasm/state_transition.rb +0 -46
- data/lib/aasm/supporting_classes.rb +0 -7
@@ -0,0 +1,132 @@
|
|
1
|
+
module AASM
|
2
|
+
module SupportingClasses
|
3
|
+
class Event
|
4
|
+
attr_reader :name, :success, :options
|
5
|
+
|
6
|
+
def initialize(name, options = {}, &block)
|
7
|
+
@name = name
|
8
|
+
@transitions = []
|
9
|
+
update(options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# a neutered version of fire - it doesn't actually fire the event, it just
|
13
|
+
# executes the transition guards to determine if a transition is even
|
14
|
+
# an option given current conditions.
|
15
|
+
def may_fire?(obj, to_state=nil)
|
16
|
+
transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
|
17
|
+
return false if transitions.size == 0
|
18
|
+
|
19
|
+
result = false
|
20
|
+
transitions.each do |transition|
|
21
|
+
next if to_state and !Array(transition.to).include?(to_state)
|
22
|
+
if transition.perform(obj)
|
23
|
+
result = true
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def fire(obj, to_state=nil, *args)
|
31
|
+
transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
|
32
|
+
return nil if transitions.size == 0
|
33
|
+
|
34
|
+
next_state = nil
|
35
|
+
transitions.each do |transition|
|
36
|
+
next if to_state and !Array(transition.to).include?(to_state)
|
37
|
+
if transition.perform(obj, *args)
|
38
|
+
next_state = to_state || Array(transition.to).first
|
39
|
+
transition.execute(obj, *args)
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
next_state
|
44
|
+
end
|
45
|
+
|
46
|
+
def transitions_from_state?(state)
|
47
|
+
@transitions.any? { |t| t.from == state }
|
48
|
+
end
|
49
|
+
|
50
|
+
def transitions_from_state(state)
|
51
|
+
@transitions.select { |t| t.from == state }
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_transitions
|
55
|
+
@transitions
|
56
|
+
end
|
57
|
+
|
58
|
+
def fire_callbacks(action, record)
|
59
|
+
action = @options[action]
|
60
|
+
action.is_a?(Array) ?
|
61
|
+
action.each {|a| _fire_callbacks(a, record)} :
|
62
|
+
_fire_callbacks(action, record)
|
63
|
+
end
|
64
|
+
|
65
|
+
def ==(event)
|
66
|
+
if event.is_a? Symbol
|
67
|
+
name == event
|
68
|
+
else
|
69
|
+
name == event.name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute_success_callback(obj, success = nil)
|
74
|
+
callback = success || @success
|
75
|
+
case(callback)
|
76
|
+
when String, Symbol
|
77
|
+
obj.send(callback)
|
78
|
+
when Proc
|
79
|
+
callback.call(obj)
|
80
|
+
when Array
|
81
|
+
callback.each{|meth|self.execute_success_callback(obj, meth)}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def execute_error_callback(obj, error, error_callback=nil)
|
86
|
+
callback = error_callback || @error
|
87
|
+
raise error unless callback
|
88
|
+
case(callback)
|
89
|
+
when String, Symbol
|
90
|
+
raise NoMethodError unless obj.respond_to?(callback.to_sym)
|
91
|
+
obj.send(callback, error)
|
92
|
+
when Proc
|
93
|
+
callback.call(obj, error)
|
94
|
+
when Array
|
95
|
+
callback.each{|meth|self.execute_error_callback(obj, error, meth)}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def update(options = {}, &block)
|
102
|
+
if options.key?(:success) then
|
103
|
+
@success = options[:success]
|
104
|
+
end
|
105
|
+
if options.key?(:error) then
|
106
|
+
@error = options[:error]
|
107
|
+
end
|
108
|
+
if block then
|
109
|
+
instance_eval(&block)
|
110
|
+
end
|
111
|
+
@options = options
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def _fire_callbacks(action, record)
|
116
|
+
case action
|
117
|
+
when Symbol, String
|
118
|
+
record.send(action)
|
119
|
+
when Proc
|
120
|
+
action.call(record)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def transitions(trans_opts)
|
125
|
+
Array(trans_opts[:from]).each do |s|
|
126
|
+
@transitions << AASM::SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end # SupportingClasses
|
132
|
+
end # AASM
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module AASM
|
2
|
+
module SupportingClasses
|
3
|
+
class Localizer
|
4
|
+
def human_event_name(klass, event)
|
5
|
+
defaults = ancestors_list(klass).map do |ancestor|
|
6
|
+
:"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
|
7
|
+
end << event.to_s.humanize
|
8
|
+
|
9
|
+
I18n.translate(defaults.shift, :default => defaults, :raise => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def human_state(obj)
|
13
|
+
klass = obj.class
|
14
|
+
defaults = ancestors_list(klass).map do |ancestor|
|
15
|
+
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}.#{obj.aasm_current_state}"
|
16
|
+
end << obj.aasm_current_state.to_s.humanize
|
17
|
+
|
18
|
+
I18n.translate(defaults.shift, :default => defaults, :raise => true)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# added for rails 2.x compatibility
|
24
|
+
def i18n_scope(klass)
|
25
|
+
klass.respond_to?(:i18n_scope) ? klass.i18n_scope : :activerecord
|
26
|
+
end
|
27
|
+
|
28
|
+
# added for rails < 3.0.3 compatibility
|
29
|
+
def i18n_klass(klass)
|
30
|
+
klass.model_name.respond_to?(:i18n_key) ? klass.model_name.i18n_key : klass.name.underscore
|
31
|
+
end
|
32
|
+
|
33
|
+
def ancestors_list(klass)
|
34
|
+
klass.ancestors.select do |ancestor|
|
35
|
+
ancestor.respond_to?(:model_name) unless ancestor == ActiveRecord::Base
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end # SupportingClasses
|
40
|
+
end # AASM
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module AASM
|
2
|
+
module SupportingClasses
|
3
|
+
class State
|
4
|
+
attr_reader :name, :options
|
5
|
+
|
6
|
+
def initialize(name, options={})
|
7
|
+
@name = name
|
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 fire_callbacks(action, record)
|
20
|
+
action = @options[action]
|
21
|
+
catch :halt_aasm_chain do
|
22
|
+
action.is_a?(Array) ?
|
23
|
+
action.each {|a| _fire_callbacks(a, record)} :
|
24
|
+
_fire_callbacks(action, record)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def display_name
|
29
|
+
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
|
30
|
+
end
|
31
|
+
|
32
|
+
def for_select
|
33
|
+
[display_name, name.to_s]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def update(options = {})
|
39
|
+
if options.key?(:display) then
|
40
|
+
@display_name = options.delete(:display)
|
41
|
+
end
|
42
|
+
@options = options
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def _fire_callbacks(action, record)
|
47
|
+
case action
|
48
|
+
when Symbol, String
|
49
|
+
record.send(action)
|
50
|
+
when Proc
|
51
|
+
action.call(record)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end # SupportingClasses
|
57
|
+
end # AASM
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AASM
|
2
|
+
module SupportingClasses
|
3
|
+
class StateTransition
|
4
|
+
attr_reader :from, :to, :opts
|
5
|
+
alias_method :options, :opts
|
6
|
+
|
7
|
+
def initialize(opts)
|
8
|
+
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
9
|
+
@opts = opts
|
10
|
+
end
|
11
|
+
|
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 # SupportingClasses
|
50
|
+
end # AASM
|
data/lib/aasm/version.rb
CHANGED
data/spec/models/conversation.rb
CHANGED
@@ -1,33 +1,33 @@
|
|
1
1
|
class Conversation
|
2
2
|
include AASM
|
3
3
|
|
4
|
-
|
4
|
+
aasm do
|
5
|
+
state :needs_attention, :initial => true
|
6
|
+
state :read
|
7
|
+
state :closed
|
8
|
+
state :awaiting_response
|
9
|
+
state :junk
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
aasm_state :closed
|
9
|
-
aasm_state :awaiting_response
|
10
|
-
aasm_state :junk
|
11
|
+
event :new_message do
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
aasm_event :view do
|
16
|
-
transitions :to => :read, :from => [:needs_attention]
|
17
|
-
end
|
14
|
+
event :view do
|
15
|
+
transitions :to => :read, :from => [:needs_attention]
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
event :reply do
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
event :close do
|
22
|
+
transitions :to => :closed, :from => [:read, :awaiting_response]
|
23
|
+
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
event :junk do
|
26
|
+
transitions :to => :junk, :from => [:read]
|
27
|
+
end
|
29
28
|
|
30
|
-
|
29
|
+
event :unjunk do
|
30
|
+
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def initialize(persister)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Silencer
|
2
|
+
include AASM
|
3
|
+
|
4
|
+
aasm :whiny_transitions => false do
|
5
|
+
state :silent, :initial => true
|
6
|
+
state :crying
|
7
|
+
state :smiling
|
8
|
+
|
9
|
+
event :cry do
|
10
|
+
transitions :from => :silent, :to => :crying
|
11
|
+
end
|
12
|
+
event :smile do
|
13
|
+
transitions :from => :crying, :to => :smiling
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -2,16 +2,17 @@ Dir[File.dirname(__FILE__) + "/../models/*.rb"].each { |f| require File.expand_p
|
|
2
2
|
|
3
3
|
class Foo
|
4
4
|
include AASM
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
aasm do
|
6
|
+
state :open, :initial => true, :exit => :exit
|
7
|
+
state :closed, :enter => :enter
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
event :close, :success => :success_callback do
|
10
|
+
transitions :to => :closed, :from => [:open]
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
event :null do
|
14
|
+
transitions :to => :closed, :from => [:open], :guard => :always_false
|
15
|
+
end
|
15
16
|
end
|
16
17
|
|
17
18
|
def always_false
|
@@ -29,17 +30,21 @@ end
|
|
29
30
|
|
30
31
|
class FooTwo < Foo
|
31
32
|
include AASM
|
32
|
-
|
33
|
+
aasm do
|
34
|
+
state :foo
|
35
|
+
end
|
33
36
|
end
|
34
37
|
|
35
38
|
class Bar
|
36
39
|
include AASM
|
37
40
|
|
38
|
-
|
39
|
-
|
41
|
+
aasm do
|
42
|
+
state :read
|
43
|
+
state :ended
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
event :foo do
|
46
|
+
transitions :to => :ended, :from => [:read]
|
47
|
+
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
@@ -48,9 +53,11 @@ end
|
|
48
53
|
|
49
54
|
class Banker
|
50
55
|
include AASM
|
56
|
+
aasm do
|
57
|
+
state :retired
|
58
|
+
state :selling_bad_mortgages
|
59
|
+
end
|
51
60
|
aasm_initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
|
52
|
-
aasm_state :retired
|
53
|
-
aasm_state :selling_bad_mortgages
|
54
61
|
RICH = 1_000_000
|
55
62
|
attr_accessor :balance
|
56
63
|
def initialize(balance = 0); self.balance = balance; end
|
@@ -59,12 +66,13 @@ end
|
|
59
66
|
|
60
67
|
class Argument
|
61
68
|
include AASM
|
62
|
-
|
63
|
-
|
64
|
-
|
69
|
+
aasm do
|
70
|
+
state :invalid, :initial => true
|
71
|
+
state :valid
|
65
72
|
|
66
|
-
|
67
|
-
|
73
|
+
event :valid do
|
74
|
+
transitions :to => :valid, :from => [:invalid]
|
75
|
+
end
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
@@ -73,39 +81,39 @@ class AuthMachine
|
|
73
81
|
|
74
82
|
attr_accessor :activation_code, :activated_at, :deleted_at
|
75
83
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
84
|
+
aasm do
|
85
|
+
state :passive
|
86
|
+
state :pending, :initial => true, :enter => :make_activation_code
|
87
|
+
state :active, :enter => :do_activate
|
88
|
+
state :suspended
|
89
|
+
state :deleted, :enter => :do_delete, :exit => :do_undelete
|
90
|
+
|
91
|
+
event :register do
|
92
|
+
transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| u.can_register? }
|
93
|
+
end
|
94
|
+
|
95
|
+
event :activate do
|
96
|
+
transitions :from => :pending, :to => :active
|
97
|
+
end
|
98
|
+
|
99
|
+
event :suspend do
|
100
|
+
transitions :from => [:passive, :pending, :active], :to => :suspended
|
101
|
+
end
|
102
|
+
|
103
|
+
event :delete do
|
104
|
+
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
|
105
|
+
end
|
106
|
+
|
107
|
+
# a dummy event that can never happen
|
108
|
+
event :unpassify do
|
109
|
+
transitions :from => :passive, :to => :active, :guard => Proc.new {|u| false }
|
110
|
+
end
|
104
111
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
event :unsuspend do
|
113
|
+
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
114
|
+
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
115
|
+
transitions :from => :suspended, :to => :passive
|
116
|
+
end
|
109
117
|
end
|
110
118
|
|
111
119
|
def initialize
|
@@ -147,30 +155,33 @@ end
|
|
147
155
|
class ThisNameBetterNotBeInUse
|
148
156
|
include AASM
|
149
157
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
158
|
+
aasm do
|
159
|
+
state :initial
|
160
|
+
state :symbol
|
161
|
+
state :string
|
162
|
+
state :array
|
163
|
+
state :proc
|
164
|
+
end
|
155
165
|
end
|
156
166
|
|
157
167
|
class ChetanPatil
|
158
168
|
include AASM
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
169
|
+
aasm do
|
170
|
+
state :sleeping, :initial => true
|
171
|
+
state :showering
|
172
|
+
state :working
|
173
|
+
state :dating
|
174
|
+
state :prettying_up
|
175
|
+
|
176
|
+
event :wakeup do
|
177
|
+
transitions :from => :sleeping, :to => [:showering, :working]
|
178
|
+
end
|
179
|
+
|
180
|
+
event :dress do
|
181
|
+
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
|
182
|
+
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
|
183
|
+
transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
|
184
|
+
end
|
174
185
|
end
|
175
186
|
|
176
187
|
def wear_clothes(shirt_color, trouser_type)
|