aasm 2.4.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 3.0.0
|
4
|
+
|
5
|
+
* switched documentation to the new DSL
|
6
|
+
* whiny transactions: by default, raise an exception if an event transition is not possible
|
7
|
+
* you may disable whiny transactions
|
8
|
+
|
3
9
|
## 2.4.0
|
4
10
|
|
5
11
|
* supporting new DSL (which is much shorter)
|
data/README.md
CHANGED
@@ -59,26 +59,24 @@ gem 'aasm'
|
|
59
59
|
Here's a quick example highlighting some of the features.
|
60
60
|
|
61
61
|
```ruby
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
aasm_column :current_state # defaults to aasm_state
|
66
|
-
|
67
|
-
aasm_initial_state :unread
|
62
|
+
class Conversation
|
63
|
+
include AASM
|
68
64
|
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
aasm :column => :current_state do # defaults to aasm_state
|
66
|
+
state :unread, :initial => true
|
67
|
+
state :read
|
68
|
+
state :closed
|
72
69
|
|
73
|
-
|
74
|
-
aasm_event :view do
|
70
|
+
event :view do
|
75
71
|
transitions :to => :read, :from => [:unread]
|
76
72
|
end
|
77
73
|
|
78
|
-
|
74
|
+
event :close do
|
79
75
|
transitions :to => :closed, :from => [:read, :unread]
|
80
76
|
end
|
81
77
|
end
|
78
|
+
|
79
|
+
end
|
82
80
|
```
|
83
81
|
|
84
82
|
## A Slightly More Complex Example ##
|
@@ -89,22 +87,21 @@ This example uses a few of the more complex features available.
|
|
89
87
|
class Relationship
|
90
88
|
include AASM
|
91
89
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
aasm_event :get_married do
|
105
|
-
transitions :to => :married, :from => [:dating, :intimate], :guard => :willing_to_give_up_manhood?
|
90
|
+
aasm :column => :status
|
91
|
+
state :dating, :enter => :make_happy, :exit => :make_depressed
|
92
|
+
state :intimate, :enter => :make_very_happy, :exit => :never_speak_again
|
93
|
+
state :married, :enter => :give_up_intimacy, :exit => :buy_exotic_car_and_wear_a_combover
|
94
|
+
|
95
|
+
event :get_intimate do
|
96
|
+
transitions :to => :intimate, :from => [:dating], :guard => :drunk?
|
97
|
+
end
|
98
|
+
|
99
|
+
event :get_married do
|
100
|
+
transitions :to => :married, :from => [:dating, :intimate], :guard => :willing_to_give_up_manhood?
|
101
|
+
end
|
106
102
|
end
|
107
|
-
|
103
|
+
aasm_initial_state Proc.new { |relationship| relationship.strictly_for_fun? ? :intimate : :dating }
|
104
|
+
|
108
105
|
def strictly_for_fun?; end
|
109
106
|
def drunk?; end
|
110
107
|
def willing_to_give_up_manhood?; end
|
@@ -122,14 +119,15 @@ This example uses a few of the more complex features available.
|
|
122
119
|
class Relationship
|
123
120
|
include AASM
|
124
121
|
|
125
|
-
|
126
|
-
|
122
|
+
aasm do
|
123
|
+
state :dating
|
124
|
+
state :married
|
127
125
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
126
|
+
event :get_married,
|
127
|
+
:before => :make_vows,
|
128
|
+
:after => :eat_wedding_cake do
|
129
|
+
transitions :to => :married, :from => [:dating]
|
130
|
+
end
|
133
131
|
end
|
134
132
|
```
|
135
133
|
|
@@ -142,7 +140,7 @@ Look at the [CHANGELOG](https://github.com/rubyist/aasm/blob/master/CHANGELOG.md
|
|
142
140
|
|
143
141
|
* [Scott Barron](https://github.com/rubyist)
|
144
142
|
* [Travis Tilley](https://github.com/ttilley)
|
145
|
-
* [Thorsten Böttger](http://github.com/alto)
|
143
|
+
* [Thorsten Böttger](http://github.com/alto)
|
146
144
|
|
147
145
|
|
148
146
|
## Warranty ##
|
data/lib/aasm.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
module AASM
|
2
|
-
end
|
3
|
-
|
4
1
|
require 'ostruct'
|
5
2
|
|
6
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
3
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'version')
|
4
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'errors')
|
5
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'base')
|
6
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'state_transition')
|
7
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'event')
|
8
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'state')
|
9
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'localizer')
|
7
10
|
require File.join(File.dirname(__FILE__), 'aasm', 'state_machine')
|
8
11
|
require File.join(File.dirname(__FILE__), 'aasm', 'persistence')
|
9
12
|
require File.join(File.dirname(__FILE__), 'aasm', 'aasm')
|
10
|
-
|
13
|
+
|
14
|
+
# load the deprecated methods and modules
|
15
|
+
Dir[File.join(File.dirname(__FILE__), 'aasm', 'deprecated', '*.rb')].sort.each { |f| require File.expand_path(f) }
|
data/lib/aasm/aasm.rb
CHANGED
@@ -1,9 +1,4 @@
|
|
1
1
|
module AASM
|
2
|
-
class InvalidTransition < RuntimeError
|
3
|
-
end
|
4
|
-
|
5
|
-
class UndefinedState < RuntimeError
|
6
|
-
end
|
7
2
|
|
8
3
|
def self.included(base) #:nodoc:
|
9
4
|
base.extend AASM::ClassMethods
|
@@ -45,7 +40,7 @@ module AASM
|
|
45
40
|
def aasm_state(name, options={})
|
46
41
|
aasm.state(name, options)
|
47
42
|
end
|
48
|
-
|
43
|
+
|
49
44
|
# deprecated
|
50
45
|
def aasm_event(name, options = {}, &block)
|
51
46
|
aasm.event(name, options, &block)
|
@@ -66,35 +61,31 @@ module AASM
|
|
66
61
|
aasm.states_for_select
|
67
62
|
end
|
68
63
|
|
69
|
-
def
|
70
|
-
AASM::Localizer.new.human_event_name(self, event)
|
64
|
+
def aasm_human_event_name(event)
|
65
|
+
AASM::SupportingClasses::Localizer.new.human_event_name(self, event)
|
71
66
|
end
|
72
67
|
end
|
73
|
-
|
74
|
-
# Instance methods
|
75
|
-
def aasm_current_state
|
76
|
-
return @aasm_current_state if @aasm_current_state
|
77
68
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
aasm_enter_initial_state
|
69
|
+
# this method does what? does it deliver the current state?
|
70
|
+
def aasm_current_state
|
71
|
+
@aasm_current_state ||=
|
72
|
+
aasm_persistable? ? aasm_read_state : aasm_enter_initial_state
|
84
73
|
end
|
85
74
|
|
75
|
+
# private?
|
86
76
|
def aasm_enter_initial_state
|
87
77
|
state_name = aasm_determine_state_name(self.class.aasm_initial_state)
|
88
78
|
state = aasm_state_object_for_state(state_name)
|
89
79
|
|
90
|
-
state.
|
91
|
-
state.
|
80
|
+
state.fire_callbacks(:before_enter, self)
|
81
|
+
state.fire_callbacks(:enter, self)
|
92
82
|
self.aasm_current_state = state_name
|
93
|
-
state.
|
83
|
+
state.fire_callbacks(:after_enter, self)
|
94
84
|
|
95
85
|
state_name
|
96
86
|
end
|
97
87
|
|
88
|
+
# private?
|
98
89
|
def aasm_events_for_current_state
|
99
90
|
aasm_events_for_state(aasm_current_state)
|
100
91
|
end
|
@@ -104,19 +95,23 @@ module AASM
|
|
104
95
|
def aasm_permissible_events_for_current_state
|
105
96
|
aasm_events_for_current_state.select{ |e| self.send(("may_" + e.to_s + "?").to_sym) }
|
106
97
|
end
|
107
|
-
|
98
|
+
|
108
99
|
def aasm_events_for_state(state)
|
109
100
|
events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
|
110
101
|
events.map {|event| event.name}
|
111
102
|
end
|
112
103
|
|
113
|
-
def
|
114
|
-
AASM::Localizer.new.human_state(self)
|
104
|
+
def aasm_human_state
|
105
|
+
AASM::SupportingClasses::Localizer.new.human_state(self)
|
115
106
|
end
|
116
107
|
|
117
108
|
private
|
118
109
|
|
119
|
-
def
|
110
|
+
def aasm_persistable?
|
111
|
+
self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
|
112
|
+
end
|
113
|
+
|
114
|
+
def aasm_set_current_state_with_persistence(state)
|
120
115
|
save_success = true
|
121
116
|
if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
|
122
117
|
save_success = aasm_write_state(state)
|
@@ -150,45 +145,45 @@ private
|
|
150
145
|
obj
|
151
146
|
end
|
152
147
|
|
153
|
-
def
|
148
|
+
def aasm_may_fire_event?(name, *args)
|
154
149
|
event = self.class.aasm_events[name]
|
155
150
|
event.may_fire?(self, *args)
|
156
151
|
end
|
157
|
-
|
158
|
-
def aasm_fire_event(name,
|
152
|
+
|
153
|
+
def aasm_fire_event(name, options, *args)
|
154
|
+
persist = options[:persist]
|
155
|
+
|
159
156
|
event = self.class.aasm_events[name]
|
160
157
|
begin
|
161
158
|
old_state = aasm_state_object_for_state(aasm_current_state)
|
162
159
|
|
163
160
|
|
164
|
-
old_state.
|
161
|
+
old_state.fire_callbacks(:exit, self)
|
165
162
|
|
166
163
|
# new event before callback
|
167
|
-
event.
|
164
|
+
event.fire_callbacks(:before, self)
|
168
165
|
|
169
|
-
new_state_name = event.fire(self, *args)
|
170
|
-
|
171
|
-
unless new_state_name.nil?
|
166
|
+
if new_state_name = event.fire(self, *args)
|
172
167
|
new_state = aasm_state_object_for_state(new_state_name)
|
173
168
|
|
174
169
|
# new before_ callbacks
|
175
|
-
old_state.
|
176
|
-
new_state.
|
170
|
+
old_state.fire_callbacks(:before_exit, self)
|
171
|
+
new_state.fire_callbacks(:before_enter, self)
|
177
172
|
|
178
|
-
new_state.
|
173
|
+
new_state.fire_callbacks(:enter, self)
|
179
174
|
|
180
175
|
persist_successful = true
|
181
176
|
if persist
|
182
|
-
persist_successful =
|
177
|
+
persist_successful = aasm_set_current_state_with_persistence(new_state_name)
|
183
178
|
event.execute_success_callback(self) if persist_successful
|
184
179
|
else
|
185
180
|
self.aasm_current_state = new_state_name
|
186
181
|
end
|
187
182
|
|
188
183
|
if persist_successful
|
189
|
-
old_state.
|
190
|
-
new_state.
|
191
|
-
event.
|
184
|
+
old_state.fire_callbacks(:after_exit, self)
|
185
|
+
new_state.fire_callbacks(:after_enter, self)
|
186
|
+
event.fire_callbacks(:after, self)
|
192
187
|
|
193
188
|
self.aasm_event_fired(name, old_state.name, self.aasm_current_state) if self.respond_to?(:aasm_event_fired)
|
194
189
|
else
|
@@ -196,12 +191,17 @@ private
|
|
196
191
|
end
|
197
192
|
|
198
193
|
persist_successful
|
194
|
+
|
199
195
|
else
|
200
196
|
if self.respond_to?(:aasm_event_failed)
|
201
197
|
self.aasm_event_failed(name, old_state.name)
|
202
198
|
end
|
203
199
|
|
204
|
-
|
200
|
+
if AASM::StateMachine[self.class].config.whiny_transitions
|
201
|
+
raise AASM::InvalidTransition, "Event '#{event.name}' cannot transition from '#{self.aasm_current_state}'"
|
202
|
+
else
|
203
|
+
false
|
204
|
+
end
|
205
205
|
end
|
206
206
|
rescue StandardError => e
|
207
207
|
event.execute_error_callback(self, e)
|
data/lib/aasm/base.rb
CHANGED
@@ -4,6 +4,11 @@ module AASM
|
|
4
4
|
@clazz = clazz
|
5
5
|
sm = AASM::StateMachine[@clazz]
|
6
6
|
sm.config.column = options[:column].to_sym if options[:column]
|
7
|
+
if options.key?(:whiny_transitions)
|
8
|
+
sm.config.whiny_transitions = options[:whiny_transitions]
|
9
|
+
else
|
10
|
+
sm.config.whiny_transitions = true # this is the default, so let's cry
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
def state(name, options={})
|
@@ -29,15 +34,15 @@ module AASM
|
|
29
34
|
# may_event? and get back a boolean that tells you whether the guard method
|
30
35
|
# on the transition will let this happen.
|
31
36
|
@clazz.send(:define_method, "may_#{name.to_s}?") do |*args|
|
32
|
-
|
37
|
+
aasm_may_fire_event?(name, *args)
|
33
38
|
end
|
34
39
|
|
35
40
|
@clazz.send(:define_method, "#{name.to_s}!") do |*args|
|
36
|
-
aasm_fire_event(name, true, *args)
|
41
|
+
aasm_fire_event(name, {:persist => true}, *args)
|
37
42
|
end
|
38
43
|
|
39
44
|
@clazz.send(:define_method, "#{name.to_s}") do |*args|
|
40
|
-
aasm_fire_event(name, false, *args)
|
45
|
+
aasm_fire_event(name, {:persist => false}, *args)
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
@@ -52,5 +57,6 @@ module AASM
|
|
52
57
|
def states_for_select
|
53
58
|
states.map { |state| state.for_select }
|
54
59
|
end
|
60
|
+
|
55
61
|
end
|
56
62
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module AASM
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
def human_event_name(*args)
|
5
|
+
warn "AASM.human_event_name is deprecated and will be removed in version 3.1.0; please use AASM.aasm_human_event_name instead!"
|
6
|
+
aasm_human_event_name(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def human_state
|
11
|
+
warn "AASM#human_state is deprecated and will be removed in version 3.1.0; please use AASM#aasm_human_state instead!"
|
12
|
+
aasm_human_state
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/lib/aasm/errors.rb
ADDED
data/lib/aasm/persistence.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
module AASM
|
1
|
+
module AASM
|
2
|
+
module Persistence
|
3
|
+
# Checks to see this class or any of it's superclasses inherit from
|
4
|
+
# ActiveRecord::Base and if so includes ActiveRecordPersistence
|
5
|
+
def self.set_persistence(base)
|
6
|
+
# Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
|
7
|
+
hierarchy = base.ancestors.map {|klass| klass.to_s}
|
2
8
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
hierarchy = base.ancestors.map {|klass| klass.to_s}
|
8
|
-
|
9
|
-
if hierarchy.include?("ActiveRecord::Base")
|
10
|
-
require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
|
11
|
-
base.send(:include, AASM::Persistence::ActiveRecordPersistence)
|
9
|
+
if hierarchy.include?("ActiveRecord::Base")
|
10
|
+
require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
|
11
|
+
base.send(:include, AASM::Persistence::ActiveRecordPersistence)
|
12
|
+
end
|
12
13
|
end
|
13
14
|
end
|
14
|
-
end
|
15
|
+
end # AASM
|
data/lib/aasm/state_machine.rb
CHANGED
@@ -1,31 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module AASM
|
2
|
+
class StateMachine
|
3
|
+
def self.[](clazz)
|
4
|
+
(@machines ||= {})[clazz.to_s]
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def self.[]=(clazz, machine)
|
8
|
+
(@machines ||= {})[clazz.to_s] = machine
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
attr_accessor :states, :events, :initial_state, :config
|
12
|
+
attr_reader :name
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def initialize(name)
|
15
|
+
@name = name
|
16
|
+
@initial_state = nil
|
17
|
+
@states = []
|
18
|
+
@events = {}
|
19
|
+
@config = OpenStruct.new
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
def clone
|
23
|
+
klone = super
|
24
|
+
klone.states = states.clone
|
25
|
+
klone.events = events.clone
|
26
|
+
klone
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
def create_state(name, options)
|
30
|
+
@states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
|
31
|
+
end
|
30
32
|
end
|
31
|
-
end
|
33
|
+
end # AASM
|