aasm 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ rdoc
8
8
  Gemfile.lock
9
9
  spec/debug.log
10
10
  spec/*.db
11
+ TODO
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>
@@ -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")
@@ -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', 'i18n')
10
+ require File.join(File.dirname(__FILE__), 'aasm', 'localizer')
@@ -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::I18n.new.human_event_name(self, event)
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::I18n.new.human_state(self)
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
@@ -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
@@ -1,4 +1,4 @@
1
- class AASM::I18n
1
+ class AASM::Localizer
2
2
  def human_event_name(klass, event)
3
3
  defaults = ancestors_list(klass).map do |ancestor|
4
4
  :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
@@ -36,10 +36,10 @@ class AASM::SupportingClasses::StateTransition
36
36
 
37
37
  def _execute(obj, on_transition, *args)
38
38
  case on_transition
39
- when Symbol, String
40
- obj.send(on_transition, *args)
41
- when Proc
42
- on_transition.call(obj, *args)
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
 
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "2.3.0"
2
+ VERSION = "2.3.1"
3
3
  end
@@ -1,10 +1,10 @@
1
1
  en:
2
2
  activerecord:
3
3
  events:
4
- i18n_test_model:
4
+ localizer_test_model:
5
5
  close: "Let's close it!"
6
6
 
7
7
  attributes:
8
- i18n_test_model:
8
+ localizer_test_model:
9
9
  aasm_state:
10
10
  open: "It's opened now!"
@@ -1,6 +1,6 @@
1
1
  ActiveRecord::Schema.define(:version => 0) do
2
2
 
3
- %w{gates readers writers transients simples thieves i18n_test_models}.each do |table_name|
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
 
@@ -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 I18nTestModel < ActiveRecord::Base
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::I18n do
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) { I18nTestModel.new }
30
- let (:foo_closed) { I18nTestModel.new.tap { |x| x.aasm_state = :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
- I18nTestModel.human_event_name(:close).should == "Let's close it!"
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
- I18nTestModel.human_event_name(:open).should == "Open"
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!
@@ -33,11 +33,11 @@ class Test::Unit::TestCase
33
33
  end
34
34
 
35
35
  begin
36
- require 'ruby-debug'
37
- Debugger.start
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, 10 # AuthMachine
11
- assert_number_of_objects AASM::SupportingClasses::StateTransition, 18 # AuthMachine
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, 12 # AuthMachine + Process
18
- assert_number_of_objects AASM::SupportingClasses::StateTransition, 20 # AuthMachine + Process
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, 12 # AuthMachine + Process
25
- assert_number_of_objects AASM::SupportingClasses::StateTransition, 20 # AuthMachine + Process
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: 3
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
- - 0
10
- version: 2.3.0
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-02 00:00:00 +02:00
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/i18n.rb
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/i18n_spec.rb
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/i18n_spec.rb
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