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 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