aasm 2.2.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.
@@ -2,20 +2,18 @@ begin
2
2
  require 'rubygems'
3
3
  require 'active_record'
4
4
  require 'logger'
5
-
6
- ActiveRecord::Base.logger = Logger.new(STDERR)
7
5
 
8
- # A dummy class for mocking the activerecord connection class
9
- class Connection
10
- end
6
+ load_schema
11
7
 
12
- class FooBar < ActiveRecord::Base
8
+ ActiveRecord::Base.logger = Logger.new(STDERR)
9
+
10
+ class Gate < ActiveRecord::Base
13
11
  include AASM
14
12
 
15
13
  # Fake this column for testing purposes
16
14
  attr_accessor :aasm_state
17
15
 
18
- aasm_state :open
16
+ aasm_state :opened
19
17
  aasm_state :closed
20
18
 
21
19
  aasm_event :view do
@@ -23,36 +21,37 @@ begin
23
21
  end
24
22
  end
25
23
 
26
- class Fi < ActiveRecord::Base
24
+ class Reader < ActiveRecord::Base
27
25
  def aasm_read_state
28
26
  "fi"
29
27
  end
30
28
  include AASM
31
29
  end
32
30
 
33
- class Fo < ActiveRecord::Base
31
+ class Writer < ActiveRecord::Base
34
32
  def aasm_write_state(state)
35
33
  "fo"
36
34
  end
37
35
  include AASM
38
36
  end
39
37
 
40
- class Fum < ActiveRecord::Base
38
+ class Transient < ActiveRecord::Base
41
39
  def aasm_write_state_without_persistence(state)
42
40
  "fum"
43
41
  end
44
42
  include AASM
45
43
  end
46
44
 
47
- class June < ActiveRecord::Base
45
+ class Simple < ActiveRecord::Base
48
46
  include AASM
49
47
  aasm_column :status
50
48
  end
51
49
 
52
- class Beaver < June
50
+ class Derivate < Simple
53
51
  end
54
52
 
55
53
  class Thief < ActiveRecord::Base
54
+ set_table_name "thieves"
56
55
  include AASM
57
56
  aasm_initial_state Proc.new { |thief| thief.skilled ? :rich : :jailed }
58
57
  aasm_state :rich
@@ -60,7 +59,7 @@ begin
60
59
  attr_accessor :skilled, :aasm_state
61
60
  end
62
61
 
63
- describe "aasm model", :shared => true do
62
+ shared_examples_for "aasm model" do
64
63
  it "should include AASM::Persistence::ActiveRecordPersistence" do
65
64
  @klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence)
66
65
  end
@@ -69,9 +68,9 @@ begin
69
68
  end
70
69
  end
71
70
 
72
- describe FooBar, "class methods" do
71
+ describe Gate, "class methods" do
73
72
  before(:each) do
74
- @klass = FooBar
73
+ @klass = Gate
75
74
  end
76
75
  it_should_behave_like "aasm model"
77
76
  it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
@@ -85,9 +84,9 @@ begin
85
84
  end
86
85
  end
87
86
 
88
- describe Fi, "class methods" do
87
+ describe Reader, "class methods" do
89
88
  before(:each) do
90
- @klass = Fi
89
+ @klass = Reader
91
90
  end
92
91
  it_should_behave_like "aasm model"
93
92
  it "should not include AASM::Persistence::ActiveRecordPersistence::ReadState" do
@@ -101,9 +100,9 @@ begin
101
100
  end
102
101
  end
103
102
 
104
- describe Fo, "class methods" do
103
+ describe Writer, "class methods" do
105
104
  before(:each) do
106
- @klass = Fo
105
+ @klass = Writer
107
106
  end
108
107
  it_should_behave_like "aasm model"
109
108
  it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
@@ -117,9 +116,9 @@ begin
117
116
  end
118
117
  end
119
118
 
120
- describe Fum, "class methods" do
119
+ describe Transient, "class methods" do
121
120
  before(:each) do
122
- @klass = Fum
121
+ @klass = Transient
123
122
  end
124
123
  it_should_behave_like "aasm model"
125
124
  it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
@@ -133,61 +132,57 @@ begin
133
132
  end
134
133
  end
135
134
 
136
- describe FooBar, "instance methods" do
137
- before(:each) do
138
- connection = mock(Connection, :columns => [])
139
- FooBar.stub!(:connection).and_return(connection)
140
- end
135
+ describe Gate, "instance methods" do
141
136
 
142
137
  it "should respond to aasm read state when not previously defined" do
143
- FooBar.new.should respond_to(:aasm_read_state)
138
+ Gate.new.should respond_to(:aasm_read_state)
144
139
  end
145
140
 
146
141
  it "should respond to aasm write state when not previously defined" do
147
- FooBar.new.should respond_to(:aasm_write_state)
142
+ Gate.new.should respond_to(:aasm_write_state)
148
143
  end
149
144
 
150
145
  it "should respond to aasm write state without persistence when not previously defined" do
151
- FooBar.new.should respond_to(:aasm_write_state_without_persistence)
146
+ Gate.new.should respond_to(:aasm_write_state_without_persistence)
152
147
  end
153
148
 
154
149
  it "should return the initial state when new and the aasm field is nil" do
155
- FooBar.new.aasm_current_state.should == :open
150
+ Gate.new.aasm_current_state.should == :opened
156
151
  end
157
152
 
158
153
  it "should return the aasm column when new and the aasm field is not nil" do
159
- foo = FooBar.new
154
+ foo = Gate.new
160
155
  foo.aasm_state = "closed"
161
156
  foo.aasm_current_state.should == :closed
162
157
  end
163
158
 
164
159
  it "should return the aasm column when not new and the aasm_column is not nil" do
165
- foo = FooBar.new
160
+ foo = Gate.new
166
161
  foo.stub!(:new_record?).and_return(false)
167
162
  foo.aasm_state = "state"
168
163
  foo.aasm_current_state.should == :state
169
164
  end
170
165
 
171
166
  it "should allow a nil state" do
172
- foo = FooBar.new
167
+ foo = Gate.new
173
168
  foo.stub!(:new_record?).and_return(false)
174
169
  foo.aasm_state = nil
175
170
  foo.aasm_current_state.should be_nil
176
171
  end
177
172
 
178
173
  it "should have aasm_ensure_initial_state" do
179
- foo = FooBar.new
174
+ foo = Gate.new
180
175
  foo.send :aasm_ensure_initial_state
181
176
  end
182
177
 
183
178
  it "should call aasm_ensure_initial_state on validation before create" do
184
- foo = FooBar.new
179
+ foo = Gate.new
185
180
  foo.should_receive(:aasm_ensure_initial_state).and_return(true)
186
181
  foo.valid?
187
182
  end
188
183
 
189
184
  it "should call aasm_ensure_initial_state on validation before create" do
190
- foo = FooBar.new
185
+ foo = Gate.new
191
186
  foo.stub!(:new_record?).and_return(false)
192
187
  foo.should_not_receive(:aasm_ensure_initial_state)
193
188
  foo.valid?
@@ -195,45 +190,41 @@ begin
195
190
 
196
191
  end
197
192
 
198
- describe 'Beavers' do
193
+ describe 'Derivates' do
199
194
  it "should have the same states as it's parent" do
200
- Beaver.aasm_states.should == June.aasm_states
195
+ Derivate.aasm_states.should == Simple.aasm_states
201
196
  end
202
197
 
203
198
  it "should have the same events as it's parent" do
204
- Beaver.aasm_events.should == June.aasm_events
199
+ Derivate.aasm_events.should == Simple.aasm_events
205
200
  end
206
201
 
207
202
  it "should have the same column as it's parent" do
208
- Beaver.aasm_column.should == :status
203
+ Derivate.aasm_column.should == :status
209
204
  end
210
205
  end
211
206
 
212
207
  describe AASM::Persistence::ActiveRecordPersistence::NamedScopeMethods do
213
- class NamedScopeExample < ActiveRecord::Base
214
- include AASM
215
- end
216
208
 
217
209
  context "Does not already respond_to? the scope name" do
218
210
  it "should add a scope" do
219
- NamedScopeExample.aasm_state :unknown_scope
220
- NamedScopeExample.scopes.keys.should include(:unknown_scope)
211
+ Simple.should_not respond_to(:unknown_scope)
212
+ Simple.aasm_state :unknown_scope
213
+ Simple.should respond_to(:unknown_scope)
214
+ Simple.unknown_scope.class.should == ActiveRecord::Relation
221
215
  end
222
216
  end
223
217
 
224
218
  context "Already respond_to? the scope name" do
225
219
  it "should not add a scope" do
226
- NamedScopeExample.aasm_state :new
227
- NamedScopeExample.scopes.keys.should_not include(:new)
220
+ Simple.aasm_state :new
221
+ Simple.should respond_to(:new)
222
+ Simple.new.class.should == Simple
228
223
  end
229
224
  end
230
225
  end
231
226
 
232
227
  describe 'Thieves' do
233
- before(:each) do
234
- connection = mock(Connection, :columns => [])
235
- Thief.stub!(:connection).and_return(connection)
236
- end
237
228
 
238
229
  it 'should be rich if they\'re skilled' do
239
230
  Thief.new(:skilled => true).aasm_current_state.should == :rich
@@ -48,6 +48,20 @@ describe AASM::SupportingClasses::Event, 'when firing an event' do
48
48
 
49
49
  event.fire(obj).should == :closed
50
50
  end
51
+
52
+
53
+ it 'should call the guard with the params passed in' do
54
+ event = AASM::SupportingClasses::Event.new(:event) do
55
+ transitions :to => :closed, :from => [:open, :received], :guard => :guard_fn
56
+ end
57
+
58
+ obj = mock('object')
59
+ obj.stub!(:aasm_current_state).and_return(:open)
60
+ obj.should_receive(:guard_fn).with('arg1', 'arg2').and_return(true)
61
+
62
+ event.fire(obj, nil, 'arg1', 'arg2').should == :closed
63
+ end
64
+
51
65
  end
52
66
 
53
67
  describe AASM::SupportingClasses::Event, 'when executing the success callback' do
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+ require 'logger'
4
+ require 'i18n'
5
+
6
+ ActiveRecord::Base.logger = Logger.new(STDERR)
7
+
8
+ class LocalizerTestModel < ActiveRecord::Base
9
+ include AASM
10
+
11
+ attr_accessor :aasm_state
12
+
13
+ aasm_initial_state :open
14
+ aasm_state :opened
15
+ aasm_state :closed
16
+
17
+ aasm_event :close
18
+ aasm_event :open
19
+ end
20
+
21
+ describe AASM::Localizer do
22
+ before(:all) do
23
+ I18n.load_path << 'spec/en.yml'
24
+ I18n.default_locale = :en
25
+ end
26
+
27
+ after(:all) { I18n.load_path.clear }
28
+
29
+ let (:foo_opened) { LocalizerTestModel.new }
30
+ let (:foo_closed) { LocalizerTestModel.new.tap { |x| x.aasm_state = :closed } }
31
+
32
+ context '.human_state' do
33
+ it 'should return translated state value' do
34
+ foo_opened.human_state.should == "It's opened now!"
35
+ end
36
+
37
+ it 'should return humanized value if not localized' do
38
+ foo_closed.human_state.should == "Closed"
39
+ end
40
+ end
41
+
42
+ context '.human_event_name' do
43
+ it 'should return translated event name' do
44
+ LocalizerTestModel.human_event_name(:close).should == "Let's close it!"
45
+ end
46
+
47
+ it 'should return humanized event name' do
48
+ LocalizerTestModel.human_event_name(:open).should == "Open"
49
+ end
50
+ end
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!
@@ -0,0 +1,18 @@
1
+ module Models
2
+ class Process
3
+ include AASM
4
+
5
+ aasm_state :sleeping
6
+ aasm_state :running
7
+ aasm_state :suspended
8
+
9
+ aasm_event :start do
10
+ transitions :from => :sleeping, :to => :running
11
+ end
12
+
13
+ aasm_event :stop do
14
+ transitions :from => :running, :to => :suspended
15
+ end
16
+
17
+ end
18
+ end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+
1
11
  require 'ostruct'
2
12
  require 'rubygems'
3
13
 
@@ -23,8 +33,8 @@ class Test::Unit::TestCase
23
33
  end
24
34
 
25
35
  begin
26
- require 'ruby-debug'
27
- Debugger.start
36
+ require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = 'annoying'
37
+ require 'ruby-debug/completion'
28
38
  rescue LoadError
29
39
  end
30
40
 
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class StateMachineTest < Test::Unit::TestCase
4
+
5
+ context "state machines" do
6
+
7
+ should "be created without memory leak" do
8
+ assert_equal 1, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine
9
+ assert_number_of_objects AASM::SupportingClasses::State, 5 # AuthMachine
10
+ assert_number_of_objects AASM::SupportingClasses::Event, 11 # AuthMachine
11
+ assert_number_of_objects AASM::SupportingClasses::StateTransition, 19 # AuthMachine
12
+
13
+ load File.expand_path(File.dirname(__FILE__) + '/../models/process.rb')
14
+ assert_equal 2, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine + Process
15
+ assert_number_of_objects Models::Process, 0
16
+ assert_number_of_objects AASM::SupportingClasses::State, 8 # 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
+
20
+ Models.send(:remove_const, "Process") if Models.const_defined?("Process")
21
+ load File.expand_path(File.dirname(__FILE__) + '/../models/process.rb')
22
+ assert_equal 2, AASM::StateMachine.instance_variable_get("@machines").size # AuthMachine + Process
23
+ assert_number_of_objects AASM::SupportingClasses::State, 8 # 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
+ end
27
+
28
+ end
29
+
30
+ private
31
+
32
+ def assert_number_of_objects(clazz, num)
33
+ count = ObjectSpace.each_object(clazz) {}
34
+ assert_equal num, count, "#{num} expected, but we had #{count} #{clazz}"
35
+ end
36
+
37
+ end