aasm 2.3.0 → 2.3.1
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/README.md +17 -0
- data/aasm.gemspec +1 -0
- data/lib/aasm.rb +1 -1
- data/lib/aasm/aasm.rb +20 -2
- data/lib/aasm/event.rb +18 -0
- data/lib/aasm/{i18n.rb → localizer.rb} +1 -1
- data/lib/aasm/state_transition.rb +4 -4
- data/lib/aasm/version.rb +1 -1
- data/spec/en.yml +2 -2
- data/spec/schema.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/unit/{i18n_spec.rb → localizer_spec.rb} +6 -6
- data/spec/unit/state_transition_spec.rb +79 -0
- data/test/functional/auth_machine_test.rb +28 -0
- data/test/test_helper.rb +3 -3
- data/test/unit/state_machine_test.rb +6 -6
- metadata +21 -7
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -117,6 +117,23 @@ This example uses a few of the more complex features available.
|
|
117
117
|
end
|
118
118
|
```
|
119
119
|
|
120
|
+
## Callbacks around events
|
121
|
+
```ruby
|
122
|
+
class Relationship
|
123
|
+
include AASM
|
124
|
+
|
125
|
+
aasm_state :dating
|
126
|
+
aasm_state :married
|
127
|
+
|
128
|
+
aasm_event :get_married,
|
129
|
+
:before => :make_vows,
|
130
|
+
:after => :eat_wedding_cake do
|
131
|
+
transitions :to => :married, :from => [:dating]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
|
120
137
|
# Other Stuff #
|
121
138
|
|
122
139
|
Author:: Scott Barron <scott at elitists dot net>
|
data/aasm.gemspec
CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency 'shoulda'
|
20
20
|
s.add_development_dependency 'sqlite3'
|
21
21
|
s.add_development_dependency 'minitest'
|
22
|
+
s.add_development_dependency 'ruby-debug-completion'
|
22
23
|
|
23
24
|
s.files = `git ls-files`.split("\n")
|
24
25
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/aasm.rb
CHANGED
@@ -7,4 +7,4 @@ require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes')
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'aasm', 'state_machine')
|
8
8
|
require File.join(File.dirname(__FILE__), 'aasm', 'persistence')
|
9
9
|
require File.join(File.dirname(__FILE__), 'aasm', 'aasm')
|
10
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
10
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'localizer')
|
data/lib/aasm/aasm.rb
CHANGED
@@ -50,6 +50,13 @@ module AASM
|
|
50
50
|
sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
|
51
51
|
end
|
52
52
|
|
53
|
+
# an addition over standard aasm so that, before firing an event, you can ask
|
54
|
+
# may_event? and get back a boolean that tells you whether the guard method
|
55
|
+
# on the transition will let this happen.
|
56
|
+
define_method("may_#{name.to_s}?") do |*args|
|
57
|
+
aasm_test_event(name, *args)
|
58
|
+
end
|
59
|
+
|
53
60
|
define_method("#{name.to_s}!") do |*args|
|
54
61
|
aasm_fire_event(name, true, *args)
|
55
62
|
end
|
@@ -72,7 +79,7 @@ module AASM
|
|
72
79
|
end
|
73
80
|
|
74
81
|
def human_event_name(event)
|
75
|
-
AASM::
|
82
|
+
AASM::Localizer.new.human_event_name(self, event)
|
76
83
|
end
|
77
84
|
end
|
78
85
|
|
@@ -104,13 +111,19 @@ module AASM
|
|
104
111
|
aasm_events_for_state(aasm_current_state)
|
105
112
|
end
|
106
113
|
|
114
|
+
# filters the results of events_for_current_state so that only those that
|
115
|
+
# are really currently possible (given transition guards) are shown.
|
116
|
+
def aasm_permissible_events_for_current_state
|
117
|
+
aasm_events_for_current_state.select{ |e| self.send(("may_" + e.to_s + "?").to_sym) }
|
118
|
+
end
|
119
|
+
|
107
120
|
def aasm_events_for_state(state)
|
108
121
|
events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
|
109
122
|
events.map {|event| event.name}
|
110
123
|
end
|
111
124
|
|
112
125
|
def human_state
|
113
|
-
AASM::
|
126
|
+
AASM::Localizer.new.human_state(self)
|
114
127
|
end
|
115
128
|
|
116
129
|
private
|
@@ -149,6 +162,11 @@ module AASM
|
|
149
162
|
obj
|
150
163
|
end
|
151
164
|
|
165
|
+
def aasm_test_event(name, *args)
|
166
|
+
event = self.class.aasm_events[name]
|
167
|
+
event.may_fire?(self, *args)
|
168
|
+
end
|
169
|
+
|
152
170
|
def aasm_fire_event(name, persist, *args)
|
153
171
|
event = self.class.aasm_events[name]
|
154
172
|
begin
|
data/lib/aasm/event.rb
CHANGED
@@ -7,6 +7,24 @@ class AASM::SupportingClasses::Event
|
|
7
7
|
update(options, &block)
|
8
8
|
end
|
9
9
|
|
10
|
+
# a neutered version of fire - it doesn't actually fir the event, it just
|
11
|
+
# executes the transition guards to determine if a transition is even
|
12
|
+
# an option given current conditions.
|
13
|
+
def may_fire?(obj, to_state=nil)
|
14
|
+
transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
|
15
|
+
return false if transitions.size == 0
|
16
|
+
|
17
|
+
result = false
|
18
|
+
transitions.each do |transition|
|
19
|
+
next if to_state and !Array(transition.to).include?(to_state)
|
20
|
+
if transition.perform(obj)
|
21
|
+
result = true
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
10
28
|
def fire(obj, to_state=nil, *args)
|
11
29
|
transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
|
12
30
|
raise AASM::InvalidTransition, "Event '#{name}' cannot transition from '#{obj.aasm_current_state}'" if transitions.size == 0
|
@@ -36,10 +36,10 @@ class AASM::SupportingClasses::StateTransition
|
|
36
36
|
|
37
37
|
def _execute(obj, on_transition, *args)
|
38
38
|
case on_transition
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
when Proc
|
40
|
+
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
|
41
|
+
when Symbol, String
|
42
|
+
obj.send(:method, on_transition.to_sym).arity == 0 ? obj.send(on_transition) : obj.send(on_transition, *args)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/aasm/version.rb
CHANGED
data/spec/en.yml
CHANGED
data/spec/schema.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
ActiveRecord::Schema.define(:version => 0) do
|
2
2
|
|
3
|
-
%w{gates readers writers transients simples thieves
|
3
|
+
%w{gates readers writers transients simples thieves localizer_test_models}.each do |table_name|
|
4
4
|
create_table table_name, :force => true
|
5
5
|
end
|
6
6
|
|
data/spec/spec_helper.rb
CHANGED
@@ -5,6 +5,9 @@ require 'aasm'
|
|
5
5
|
require 'rspec'
|
6
6
|
require 'rspec/autorun'
|
7
7
|
|
8
|
+
# require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = 'annoying'
|
9
|
+
# require 'ruby-debug/completion'
|
10
|
+
|
8
11
|
def load_schema
|
9
12
|
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
10
13
|
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
@@ -5,7 +5,7 @@ require 'i18n'
|
|
5
5
|
|
6
6
|
ActiveRecord::Base.logger = Logger.new(STDERR)
|
7
7
|
|
8
|
-
class
|
8
|
+
class LocalizerTestModel < ActiveRecord::Base
|
9
9
|
include AASM
|
10
10
|
|
11
11
|
attr_accessor :aasm_state
|
@@ -18,7 +18,7 @@ class I18nTestModel < ActiveRecord::Base
|
|
18
18
|
aasm_event :open
|
19
19
|
end
|
20
20
|
|
21
|
-
describe AASM::
|
21
|
+
describe AASM::Localizer do
|
22
22
|
before(:all) do
|
23
23
|
I18n.load_path << 'spec/en.yml'
|
24
24
|
I18n.default_locale = :en
|
@@ -26,8 +26,8 @@ describe AASM::I18n do
|
|
26
26
|
|
27
27
|
after(:all) { I18n.load_path.clear }
|
28
28
|
|
29
|
-
let (:foo_opened) {
|
30
|
-
let (:foo_closed) {
|
29
|
+
let (:foo_opened) { LocalizerTestModel.new }
|
30
|
+
let (:foo_closed) { LocalizerTestModel.new.tap { |x| x.aasm_state = :closed } }
|
31
31
|
|
32
32
|
context '.human_state' do
|
33
33
|
it 'should return translated state value' do
|
@@ -41,11 +41,11 @@ describe AASM::I18n do
|
|
41
41
|
|
42
42
|
context '.human_event_name' do
|
43
43
|
it 'should return translated event name' do
|
44
|
-
|
44
|
+
LocalizerTestModel.human_event_name(:close).should == "Let's close it!"
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'should return humanized event name' do
|
48
|
-
|
48
|
+
LocalizerTestModel.human_event_name(:open).should == "Open"
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -82,3 +82,82 @@ describe AASM::SupportingClasses::StateTransition, '- when performing guard chec
|
|
82
82
|
st.perform(obj)
|
83
83
|
end
|
84
84
|
end
|
85
|
+
|
86
|
+
describe AASM::SupportingClasses::StateTransition, '- when executing the transition with a Proc' do
|
87
|
+
it 'should call a Proc on the object with args' do
|
88
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {|o| o.test}}
|
89
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
90
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
91
|
+
obj = mock('object')
|
92
|
+
|
93
|
+
opts[:on_transition].should_receive(:call).with(any_args)
|
94
|
+
|
95
|
+
st.execute(obj, args)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should call a Proc on the object without args' do
|
99
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {||}}
|
100
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
101
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
102
|
+
obj = mock('object')
|
103
|
+
|
104
|
+
opts[:on_transition].should_receive(:call).with(no_args)
|
105
|
+
|
106
|
+
st.execute(obj, args)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe AASM::SupportingClasses::StateTransition, '- when executing the transition with an :on_transtion method call' do
|
111
|
+
it 'should accept a String for the method name' do
|
112
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => 'test'}
|
113
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
114
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
115
|
+
obj = mock('object')
|
116
|
+
|
117
|
+
obj.should_receive(:test)
|
118
|
+
|
119
|
+
st.execute(obj, args)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should accept a Symbol for the method name' do
|
123
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
124
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
125
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
126
|
+
obj = mock('object')
|
127
|
+
|
128
|
+
obj.should_receive(:test)
|
129
|
+
|
130
|
+
st.execute(obj, args)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should pass args if the target method accepts them' do
|
134
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
135
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
136
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
137
|
+
obj = mock('object')
|
138
|
+
|
139
|
+
obj.class.class_eval do
|
140
|
+
define_method(:test) {|*args| 'success'}
|
141
|
+
end
|
142
|
+
|
143
|
+
return_value = st.execute(obj, args)
|
144
|
+
|
145
|
+
return_value.should == 'success'
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should NOT pass args if the target method does NOT accept them' do
|
149
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
150
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
151
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
152
|
+
obj = mock('object')
|
153
|
+
|
154
|
+
obj.class.class_eval do
|
155
|
+
define_method(:test) {|*args| 'success'}
|
156
|
+
end
|
157
|
+
|
158
|
+
return_value = st.execute(obj, args)
|
159
|
+
|
160
|
+
return_value.should == 'success'
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -29,6 +29,11 @@ class AuthMachine
|
|
29
29
|
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
|
30
30
|
end
|
31
31
|
|
32
|
+
# a dummy event that can never happen
|
33
|
+
aasm_event :unpassify do
|
34
|
+
transitions :from => :passive, :to => :active, :guard => Proc.new {|u| false }
|
35
|
+
end
|
36
|
+
|
32
37
|
aasm_event :unsuspend do
|
33
38
|
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
34
39
|
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
@@ -89,6 +94,29 @@ class AuthMachineTest < Test::Unit::TestCase
|
|
89
94
|
end
|
90
95
|
|
91
96
|
context 'when being unsuspended' do
|
97
|
+
|
98
|
+
should 'be able to be unsuspended' do
|
99
|
+
@auth = AuthMachine.new
|
100
|
+
@auth.activate!
|
101
|
+
@auth.suspend!
|
102
|
+
assert @auth.may_unsuspend?
|
103
|
+
end
|
104
|
+
|
105
|
+
should 'not be able to be unsuspended into active' do
|
106
|
+
@auth = AuthMachine.new
|
107
|
+
@auth.suspend!
|
108
|
+
assert_equal false, @auth.may_unsuspend?(:active)
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'not be able to be unpassified' do
|
112
|
+
@auth = AuthMachine.new
|
113
|
+
@auth.activate!
|
114
|
+
@auth.suspend!
|
115
|
+
@auth.unsuspend!
|
116
|
+
|
117
|
+
assert_equal false, @auth.may_unpassify?
|
118
|
+
end
|
119
|
+
|
92
120
|
should 'be active if previously activated' do
|
93
121
|
@auth = AuthMachine.new
|
94
122
|
@auth.activate!
|
data/test/test_helper.rb
CHANGED
@@ -33,11 +33,11 @@ class Test::Unit::TestCase
|
|
33
33
|
end
|
34
34
|
|
35
35
|
begin
|
36
|
-
require 'ruby-debug'
|
37
|
-
|
36
|
+
require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = 'annoying'
|
37
|
+
require 'ruby-debug/completion'
|
38
38
|
rescue LoadError
|
39
39
|
end
|
40
40
|
|
41
41
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
42
42
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
43
|
-
require 'aasm'
|
43
|
+
require 'aasm'
|
@@ -7,22 +7,22 @@ class StateMachineTest < Test::Unit::TestCase
|
|
7
7
|
should "be created without memory leak" do
|
8
8
|
assert_equal 1, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine
|
9
9
|
assert_number_of_objects AASM::SupportingClasses::State, 5 # AuthMachine
|
10
|
-
assert_number_of_objects AASM::SupportingClasses::Event,
|
11
|
-
assert_number_of_objects AASM::SupportingClasses::StateTransition,
|
10
|
+
assert_number_of_objects AASM::SupportingClasses::Event, 11 # AuthMachine
|
11
|
+
assert_number_of_objects AASM::SupportingClasses::StateTransition, 19 # AuthMachine
|
12
12
|
|
13
13
|
load File.expand_path(File.dirname(__FILE__) + '/../models/process.rb')
|
14
14
|
assert_equal 2, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine + Process
|
15
15
|
assert_number_of_objects Models::Process, 0
|
16
16
|
assert_number_of_objects AASM::SupportingClasses::State, 8 # AuthMachine + Process
|
17
|
-
assert_number_of_objects AASM::SupportingClasses::Event,
|
18
|
-
assert_number_of_objects AASM::SupportingClasses::StateTransition,
|
17
|
+
assert_number_of_objects AASM::SupportingClasses::Event, 13 # AuthMachine + Process
|
18
|
+
assert_number_of_objects AASM::SupportingClasses::StateTransition, 21 # AuthMachine + Process
|
19
19
|
|
20
20
|
Models.send(:remove_const, "Process") if Models.const_defined?("Process")
|
21
21
|
load File.expand_path(File.dirname(__FILE__) + '/../models/process.rb')
|
22
22
|
assert_equal 2, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine + Process
|
23
23
|
assert_number_of_objects AASM::SupportingClasses::State, 8 # AuthMachine + Process
|
24
|
-
assert_number_of_objects AASM::SupportingClasses::Event,
|
25
|
-
assert_number_of_objects AASM::SupportingClasses::StateTransition,
|
24
|
+
assert_number_of_objects AASM::SupportingClasses::Event, 13 # AuthMachine + Process
|
25
|
+
assert_number_of_objects AASM::SupportingClasses::StateTransition, 21 # AuthMachine + Process
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aasm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 2.3.
|
9
|
+
- 1
|
10
|
+
version: 2.3.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Scott Barron
|
@@ -18,7 +18,7 @@ autorequire:
|
|
18
18
|
bindir: bin
|
19
19
|
cert_chain: []
|
20
20
|
|
21
|
-
date: 2011-09-
|
21
|
+
date: 2011-09-10 00:00:00 +02:00
|
22
22
|
default_executable:
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
@@ -134,6 +134,20 @@ dependencies:
|
|
134
134
|
version: "0"
|
135
135
|
type: :development
|
136
136
|
version_requirements: *id008
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: ruby-debug-completion
|
139
|
+
prerelease: false
|
140
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
hash: 3
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
version: "0"
|
149
|
+
type: :development
|
150
|
+
version_requirements: *id009
|
137
151
|
description: AASM is a continuation of the acts as state machine rails plugin, built for plain Ruby objects.
|
138
152
|
email: scott@elitists.net, ttilley@gmail.com
|
139
153
|
executables: []
|
@@ -153,7 +167,7 @@ files:
|
|
153
167
|
- lib/aasm.rb
|
154
168
|
- lib/aasm/aasm.rb
|
155
169
|
- lib/aasm/event.rb
|
156
|
-
- lib/aasm/
|
170
|
+
- lib/aasm/localizer.rb
|
157
171
|
- lib/aasm/persistence.rb
|
158
172
|
- lib/aasm/persistence/active_record_persistence.rb
|
159
173
|
- lib/aasm/state.rb
|
@@ -171,7 +185,7 @@ files:
|
|
171
185
|
- spec/unit/active_record_persistence_spec.rb
|
172
186
|
- spec/unit/before_after_callbacks_spec.rb
|
173
187
|
- spec/unit/event_spec.rb
|
174
|
-
- spec/unit/
|
188
|
+
- spec/unit/localizer_spec.rb
|
175
189
|
- spec/unit/state_spec.rb
|
176
190
|
- spec/unit/state_transition_spec.rb
|
177
191
|
- test/functional/auth_machine_test.rb
|
@@ -227,7 +241,7 @@ test_files:
|
|
227
241
|
- spec/unit/active_record_persistence_spec.rb
|
228
242
|
- spec/unit/before_after_callbacks_spec.rb
|
229
243
|
- spec/unit/event_spec.rb
|
230
|
-
- spec/unit/
|
244
|
+
- spec/unit/localizer_spec.rb
|
231
245
|
- spec/unit/state_spec.rb
|
232
246
|
- spec/unit/state_transition_spec.rb
|
233
247
|
- test/functional/auth_machine_test.rb
|