transitions 0.1.5 → 0.1.6

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 1.0.6
2
+
3
+ * (troessner) Revert 'Fixing set_initial_state with Mongoid' because of https://github.com/troessner/transitions/issues/76
4
+ * (divins) Multiple success callbacks
5
+ * (cstrahan) Pass additional args to guard function
6
+ * (cmw) Support for configurable column names
7
+
1
8
  # 1.0.5
2
9
 
3
10
  * (troessner) Fix unhelpful error message when event can not be fired.
data/README.rdoc CHANGED
@@ -44,7 +44,7 @@ This goes into your Gemfile:
44
44
  end
45
45
  end
46
46
 
47
- In this example we assume that you are in a rails project using Bundler, which would automitcally require `transitions`.
47
+ In this example we assume that you are in a rails project using Bundler, which would automatically require `transitions`.
48
48
  If this is not the case for you you have to add
49
49
 
50
50
  require 'transitions'
@@ -98,6 +98,17 @@ you can use this feature a la:
98
98
  >> Order.pick_line_items
99
99
  => [#<Order id: 3, state: "pick_line_items", description: nil, created_at: "2011-08-23 15:48:46", updated_at: "2011-08-23 15:48:46">]
100
100
 
101
+ ==== Using <tt>guard</tt>
102
+
103
+ Each event definition takes an optional "guard" argument, which acts as a predicate for the transition.
104
+ You can pass in a Symbol, a String, or a Proc like this:
105
+
106
+ event :discontinue do
107
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => :can_discontinue
108
+ end
109
+
110
+ Any arguments passed to the event method will be passed on to the <tt>guard</tt> predicate.
111
+
101
112
  ==== Using <tt>on_transition</tt>
102
113
 
103
114
  Each event definition takes an optional "on_transition" argument, which allows you to execute methods on transition.
@@ -107,6 +118,8 @@ You can pass in a Symbol, a String, a Proc or an Array containing method names a
107
118
  transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk]
108
119
  end
109
120
 
121
+ Any arguments passed to the event method will be passed on to the <tt>on_transition</tt> callback.
122
+
110
123
  ==== Using <tt>success</tt>
111
124
 
112
125
  In case you need to trigger a method call after a successful transition you can use <tt>success</tt>:
@@ -122,6 +135,12 @@ perfom some more complex success callbacks:
122
135
  transitions :to => :discontinued, :from => [:available, :out_of_stock]
123
136
  end
124
137
 
138
+ If you need it, you can even call multiple methods or lambdas just passing an array:
139
+
140
+ event :discontinue, :success => [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do
141
+ transitions :to => :discontinued, :from => [:available, :out_of_stock]
142
+ end
143
+
125
144
  ==== Timestamps
126
145
 
127
146
  If you'd like to note the time of a state change, Transitions comes with timestamps free!
@@ -167,6 +186,20 @@ You can easily get a listing of all available states:
167
186
  state :closed
168
187
  end
169
188
 
189
+ === Configuring a different column name with ActiveRecord
190
+
191
+ To use a different column than <tt>state</tt> to track it's value simply do this:
192
+
193
+ class Product < ActiveRecord::Base
194
+ include Transitions
195
+
196
+ state_machine :attribute_name => :different_column do
197
+
198
+ ...
199
+
200
+ end
201
+ end
202
+
170
203
  === Documentation, Guides & Examples
171
204
 
172
205
  - {Online API Documentation}[http://rdoc.info/github/troessner/transitions/master/Transitions]
@@ -25,9 +25,31 @@ module ActiveModel
25
25
  extend ActiveSupport::Concern
26
26
 
27
27
  included do
28
+ class ::Transitions::Machine
29
+ unless instance_methods.include?(:new_initialize) || instance_methods.include?(:new_update)
30
+ attr_reader :attribute_name
31
+ alias :old_initialize :initialize
32
+ alias :old_update :update
33
+
34
+ def new_initialize(*args, &block)
35
+ @attribute_name = :state
36
+ old_initialize(*args, &block)
37
+ end
38
+
39
+ def new_update(options = {}, &block)
40
+ @attribute_name = options[:attribute_name] if options.key?(:attribute_name)
41
+ old_update(options, &block)
42
+ end
43
+
44
+ alias :initialize :new_initialize
45
+ alias :update :new_update
46
+ else
47
+ puts "WARNING: Transitions::Machine#new_update or Transitions::Machine#new_initialize already defined. This can possibly break ActiveModel::Transitions."
48
+ end
49
+ end
28
50
  include ::Transitions
29
51
  after_initialize :set_initial_state
30
- validates_presence_of :state
52
+ validate :state_presence
31
53
  validate :state_inclusion
32
54
  end
33
55
 
@@ -43,6 +65,10 @@ module ActiveModel
43
65
 
44
66
  protected
45
67
 
68
+ def transitions_state_column_name
69
+ self.class.state_machine.attribute_name
70
+ end
71
+
46
72
  def write_state(state)
47
73
  prev_state = current_state
48
74
  write_state_without_persistence(state)
@@ -55,22 +81,27 @@ module ActiveModel
55
81
  def write_state_without_persistence(state)
56
82
  ivar = self.class.get_state_machine.current_state_variable
57
83
  instance_variable_set(ivar, state)
58
- self.state = state.to_s
84
+ self[transitions_state_column_name] = state.to_s
59
85
  end
60
86
 
61
87
  def read_state
62
- self.state && self.state.to_sym
88
+ self[transitions_state_column_name] && self[transitions_state_column_name].to_sym
63
89
  end
64
90
 
65
91
  def set_initial_state
66
- self.state ||= self.class.get_state_machine.initial_state.to_s if self.respond_to?(:state=)
92
+ self[transitions_state_column_name] ||= self.class.get_state_machine.initial_state.to_s if self.has_attribute?(transitions_state_column_name)
93
+ end
94
+
95
+ def state_presence
96
+ unless self[transitions_state_column_name].present?
97
+ self.errors.add(transitions_state_column_name, :presence)
98
+ end
67
99
  end
68
100
 
69
101
  def state_inclusion
70
- unless self.class.get_state_machine.states.map{|s| s.name.to_s }.include?(self.state.to_s)
71
- self.errors.add(:state, :inclusion, :value => self.state)
102
+ unless self.class.get_state_machine.states.map{|s| s.name.to_s }.include?(self[transitions_state_column_name].to_s)
103
+ self.errors.add(transitions_state_column_name, :inclusion, :value => self[transitions_state_column_name])
72
104
  end
73
105
  end
74
106
  end
75
107
  end
76
-
@@ -49,7 +49,7 @@ module Transitions
49
49
  next_state = nil
50
50
  transitions.each do |transition|
51
51
  next if to_state && !Array(transition.to).include?(to_state)
52
- if transition.perform(obj)
52
+ if transition.perform(obj, *args)
53
53
  next_state = to_state || Array(transition.to).first
54
54
  transition.execute(obj, *args)
55
55
  break
@@ -78,7 +78,7 @@ module Transitions
78
78
  end
79
79
 
80
80
  def update(options = {}, &block)
81
- @success = build_sucess_callback(options[:success]) if options.key?(:success)
81
+ @success = build_success_callback(options[:success]) if options.key?(:success)
82
82
  self.timestamp = options[:timestamp] if options[:timestamp]
83
83
  instance_eval(&block) if block
84
84
  self
@@ -129,12 +129,18 @@ module Transitions
129
129
  end
130
130
  end
131
131
 
132
- def build_sucess_callback(callback_symbol_or_proc)
133
- case callback_symbol_or_proc
132
+ def build_success_callback(callback_names)
133
+ case callback_names
134
+ when Array
135
+ lambda do |record|
136
+ callback_names.each do |callback|
137
+ build_success_callback(callback).call(record)
138
+ end
139
+ end
134
140
  when Proc
135
- callback_symbol_or_proc
141
+ callback_names
136
142
  when Symbol
137
- lambda { |record| record.send(callback_symbol_or_proc) }
143
+ lambda { |record| record.send(callback_names) }
138
144
  end
139
145
  end
140
146
 
@@ -29,12 +29,12 @@ module Transitions
29
29
  @options = opts
30
30
  end
31
31
 
32
- def perform(obj)
32
+ def perform(obj, *args)
33
33
  case @guard
34
34
  when Symbol, String
35
- obj.send(@guard)
35
+ obj.send(@guard, *args)
36
36
  when Proc
37
- @guard.call(obj)
37
+ @guard.call(obj, *args)
38
38
  else
39
39
  true
40
40
  end
@@ -1,3 +1,3 @@
1
1
  module Transitions
2
- VERSION = '0.1.5'
2
+ VERSION = '0.1.6'
3
3
  end
@@ -11,6 +11,17 @@ end
11
11
 
12
12
  set_up_db CreateTrafficLights
13
13
 
14
+ class CreateDifferentTrafficLights < ActiveRecord::Migration
15
+ def self.up
16
+ create_table(:different_traffic_lights) do |t|
17
+ t.string :different_state
18
+ t.string :name
19
+ end
20
+ end
21
+ end
22
+
23
+ set_up_db CreateDifferentTrafficLights
24
+
14
25
  class TrafficLight < ActiveRecord::Base
15
26
  include ActiveModel::Transitions
16
27
 
@@ -147,3 +158,148 @@ class TestActiveRecord < Test::Unit::TestCase
147
158
  assert_equal "red", @light.reload.state
148
159
  end
149
160
  end
161
+
162
+ class TestNewActiveRecord < TestActiveRecord
163
+
164
+ def setup
165
+ set_up_db CreateTrafficLights
166
+ @light = TrafficLight.new
167
+ end
168
+
169
+ test "new active records defaults current state to the initial state" do
170
+ assert_equal :off, @light.current_state
171
+ end
172
+
173
+ end
174
+
175
+ class TestScopes < Test::Unit::TestCase
176
+ test "scope returns correct object" do
177
+ @light = TrafficLight.create!
178
+ assert_respond_to TrafficLight, :off
179
+ assert_equal TrafficLight.off.first, @light
180
+ assert TrafficLight.red.empty?
181
+ end
182
+
183
+ test "scopes exist" do
184
+ assert_respond_to TrafficLight, :off
185
+ assert_respond_to TrafficLight, :red
186
+ assert_respond_to TrafficLight, :green
187
+ assert_respond_to TrafficLight, :yellow
188
+ end
189
+
190
+ test 'scopes are only generated if we explicitly say so' do
191
+ assert_not_respond_to LightBulb, :off
192
+ assert_not_respond_to LightBulb, :on
193
+ end
194
+
195
+ test 'scope generation raises an exception if we try to overwrite an existing method' do
196
+ assert_raise(Transitions::InvalidMethodOverride) {
197
+ class Light < ActiveRecord::Base
198
+ include ActiveModel::Transitions
199
+
200
+ state_machine :auto_scopes => true do
201
+ state :new
202
+ state :broken
203
+ end
204
+ end
205
+ }
206
+ end
207
+ end
208
+
209
+ class DifferentTrafficLight < ActiveRecord::Base
210
+ include ActiveModel::Transitions
211
+
212
+ state_machine :attribute_name => :different_state, :auto_scopes => true do
213
+ state :off
214
+
215
+ state :red
216
+ state :green
217
+ state :yellow
218
+
219
+ event :red_on do
220
+ transitions :to => :red, :from => [:yellow]
221
+ end
222
+
223
+ event :green_on do
224
+ transitions :to => :green, :from => [:red]
225
+ end
226
+
227
+ event :yellow_on do
228
+ transitions :to => :yellow, :from => [:green]
229
+ end
230
+
231
+ event :reset do
232
+ transitions :to => :red, :from => [:off]
233
+ end
234
+ end
235
+ end
236
+
237
+ class TestActiveRecordWithDifferentColumnName < Test::Unit::TestCase
238
+ def setup
239
+ set_up_db CreateDifferentTrafficLights
240
+ @light = DifferentTrafficLight.create!
241
+ end
242
+
243
+ test "new record has the initial state set" do
244
+ @light = DifferentTrafficLight.new
245
+ assert_equal "off", @light.different_state
246
+ end
247
+
248
+ test "states initial state" do
249
+ assert @light.off?
250
+ assert_equal :off, @light.current_state
251
+ end
252
+
253
+ test "transition to a valid state" do
254
+ @light.reset
255
+ assert @light.red?
256
+ assert_equal :red, @light.current_state
257
+
258
+ @light.green_on
259
+ assert @light.green?
260
+ assert_equal :green, @light.current_state
261
+ end
262
+
263
+ test "transition does not persist state" do
264
+ @light.reset
265
+ assert_equal :red, @light.current_state
266
+ @light.reload
267
+ assert_equal "off", @light.different_state
268
+ end
269
+
270
+ test "transition does persists state" do
271
+ @light.reset!
272
+ assert_equal :red, @light.current_state
273
+ @light.reload
274
+ assert_equal "red", @light.different_state
275
+ end
276
+
277
+ test "transition to an invalid state" do
278
+ assert_raise(Transitions::InvalidTransition) { @light.yellow_on }
279
+ assert_equal :off, @light.current_state
280
+ end
281
+
282
+ test "transition with wrong state will not validate" do
283
+ for s in @light.class.state_machine.states
284
+ @light.different_state = s.name
285
+ assert @light.valid?
286
+ end
287
+ @light.different_state = "invalid_one"
288
+ assert_false @light.valid?
289
+ end
290
+
291
+ test "reloading model resets current state" do
292
+ @light.reset
293
+ assert @light.red?
294
+ @light.update_attribute(:different_state, 'green')
295
+ assert @light.reload.green?, "reloaded state should come from database, not instance variable"
296
+ end
297
+
298
+ test "calling non-bang event updates state attribute" do
299
+ @light.reset!
300
+ assert @light.red?
301
+ @light.green_on
302
+ assert_equal "green", @light.different_state
303
+ assert_equal "red", @light.reload.different_state
304
+ end
305
+ end
@@ -5,6 +5,7 @@ class TestEvent < Test::Unit::TestCase
5
5
  @state_name = :close_order
6
6
  @success_as_symbol = :success_callback
7
7
  @success_as_lambda = lambda { |record| record.success_callback }
8
+ @success_as_array = [@success_as_symbol, @success_as_lambda]
8
9
  end
9
10
 
10
11
  def event_with_symbol_success_callback
@@ -20,6 +21,12 @@ class TestEvent < Test::Unit::TestCase
20
21
  end
21
22
  end
22
23
 
24
+ def event_with_array_success_callback
25
+ @event = Transitions::Event.new(nil, @state_name, {:success => @success_as_array}) do
26
+ transitions :to => :closed, :from => [:open, :received]
27
+ end
28
+ end
29
+
23
30
  test "should set the name" do
24
31
  assert_equal @state_name, new_event.name
25
32
  end
@@ -39,6 +46,24 @@ class TestEvent < Test::Unit::TestCase
39
46
  assert_respond_to event_with_lambda_success_callback.success, :call
40
47
  end
41
48
 
49
+ test "should build a block which calls the given success_callback lambda on the passed record instance" do
50
+ record = mock("SomeRecordToGetCalled")
51
+ record.expects(:success_callback)
52
+
53
+ event_with_lambda_success_callback.success.call(record)
54
+ end
55
+
56
+ test "should set the success callback with an array" do
57
+ assert_respond_to event_with_array_success_callback.success, :call
58
+ end
59
+
60
+ test "should build a block which calls the given success_callback array on the passed record instance for each callback" do
61
+ record = mock("SomeRecordToGetCalled")
62
+ record.expects(:success_callback).twice
63
+
64
+ event_with_array_success_callback.success.call(record)
65
+ end
66
+
42
67
  test "should create StateTransitions" do
43
68
  Transitions::StateTransition.expects(:new).with(:to => :closed, :from => :open)
44
69
  Transitions::StateTransition.expects(:new).with(:to => :closed, :from => :received)
@@ -1,11 +1,13 @@
1
1
  require "helper"
2
2
 
3
3
  class TestStateTransitionGuardCheck < Test::Unit::TestCase
4
+ args = [:foo, "bar"]
5
+
4
6
  test "should return true of there is no guard" do
5
7
  opts = {:from => "foo", :to => "bar"}
6
8
  st = Transitions::StateTransition.new(opts)
7
9
 
8
- assert st.perform(nil)
10
+ assert st.perform(nil, *args)
9
11
  end
10
12
 
11
13
  test "should call the method on the object if guard is a symbol" do
@@ -13,9 +15,9 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
13
15
  st = Transitions::StateTransition.new(opts)
14
16
 
15
17
  obj = stub
16
- obj.expects(:test_guard)
18
+ obj.expects(:test_guard).with(*args)
17
19
 
18
- st.perform(obj)
20
+ st.perform(obj, *args)
19
21
  end
20
22
 
21
23
  test "should call the method on the object if guard is a string" do
@@ -23,18 +25,18 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
23
25
  st = Transitions::StateTransition.new(opts)
24
26
 
25
27
  obj = stub
26
- obj.expects(:test_guard)
28
+ obj.expects(:test_guard).with(*args)
27
29
 
28
- st.perform(obj)
30
+ st.perform(obj, *args)
29
31
  end
30
32
 
31
33
  test "should call the proc passing the object if the guard is a proc" do
32
- opts = {:from => "foo", :to => "bar", :guard => Proc.new {|o| o.test_guard}}
34
+ opts = {:from => "foo", :to => "bar", :guard => Proc.new {|o, *args| o.test_guard(*args)}}
33
35
  st = Transitions::StateTransition.new(opts)
34
36
 
35
37
  obj = stub
36
- obj.expects(:test_guard)
38
+ obj.expects(:test_guard).with(*args)
37
39
 
38
- st.perform(obj)
40
+ st.perform(obj, *args)
39
41
  end
40
42
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transitions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-01-28 00:00:00.000000000 Z
13
+ date: 2013-02-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -179,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
179
  version: '0'
180
180
  segments:
181
181
  - 0
182
- hash: -1071042129
182
+ hash: -1024202641
183
183
  required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  none: false
185
185
  requirements: