transitions 0.1.11 → 0.1.12

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2c18ccf9710c9acf987c3bab92b3f496c0109ef
4
- data.tar.gz: 050d68e232efbe9d830eabb7163c54536e451fee
3
+ metadata.gz: c5fdc12a9620c4afb752d88f581df0416e1bfb2c
4
+ data.tar.gz: 2e513473a1ab671758977874cc4670c43a38d76b
5
5
  SHA512:
6
- metadata.gz: 4c467765c0917f255aacf6cc082a33e5e21afe38f37d408376bc6055408317b05d4f3f3862db2ffbb2d9a5bbbe1c6124ffed10e3beb356ef6dee593308cc0ba4
7
- data.tar.gz: 6891fc7543e6d755c284bd4724e8fcead9405c822f2491b420616d0557866fafea947fd1c4fdc8be7a7837418ac4fdbfe807f6dc014f83a8c7c083c37563f452
6
+ metadata.gz: 8843c2845f29a81cc1a4fe170796976850515a3d5b513996e50e4904ae8a418d47fa38be5e72f1a325623e0295910df7022c4f061c395bc9aeb8120a6d84e2c8
7
+ data.tar.gz: a00d3f4ed41f725e47e4afc81aa030958e86d966f1192008365c988e9a167d3243dd2d79126fd7365a2965cd58071779294b59d8caaefef2849e3f41d43c56f5
data/.gitignore CHANGED
@@ -3,4 +3,5 @@ pkg
3
3
  .bundle
4
4
  .idea/
5
5
  Gemfile.lock
6
+ gemfiles/*.lock
6
7
  *.swp
data/.travis.yml CHANGED
@@ -1,3 +1,9 @@
1
1
  rvm:
2
2
  - 2.0.0
3
3
  - 1.9.3
4
+ - jruby-19mode
5
+ gemfile:
6
+ - gemfiles/rails_3_0.gemfile
7
+ - gemfiles/rails_3_1.gemfile
8
+ - gemfiles/rails_3_2.gemfile
9
+ - gemfiles/rails_4_0.gemfile
data/Appraisals ADDED
@@ -0,0 +1,23 @@
1
+ appraise "rails_3_0" do
2
+ gem "activerecord", "~> 3.0.10"
3
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
4
+ gem 'sqlite3', :platforms => :ruby
5
+ end
6
+
7
+ appraise "rails_3_1" do
8
+ gem "activerecord", "~>3.1.12"
9
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
10
+ gem 'sqlite3', :platforms => :ruby
11
+ end
12
+
13
+ appraise "rails_3_2" do
14
+ gem "activerecord", "~>3.2.14"
15
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
16
+ gem 'sqlite3', :platforms => :ruby
17
+ end
18
+
19
+ appraise "rails_4_0" do
20
+ gem "activerecord", "~>4.0.0"
21
+ gem "activerecord-jdbcsqlite3-adapter", '1.3.0.rc1', :platforms=>:jruby
22
+ gem 'sqlite3', :platforms => :ruby
23
+ end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.1.12
2
+
3
+ * (troessner) Fix issue 107: timestamps are updated even if guard fails.
4
+
1
5
  # 0.1.10
2
6
 
3
7
  * (troessner) Fix: Using a custom ActiveRecord select query without the name of the state attribute would trigger an exception.
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem 'activerecord', '~> 3.2.14'
6
+ gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby
7
+ gem 'sqlite3', :platforms => :ruby
data/README.md CHANGED
@@ -22,44 +22,48 @@ issue](https://github.com/troessner/transitions/issues/86) for example).
22
22
  #### Rails
23
23
 
24
24
  This goes into your Gemfile:
25
-
26
- gem "transitions", :require => ["transitions", "active_model/transitions"]
25
+ ```ruby
26
+ gem "transitions", :require => ["transitions", "active_model/transitions"]
27
+ ```
27
28
 
28
29
  … and this into your ORM model:
29
-
30
- include ActiveModel::Transitions
30
+ ```ruby
31
+ include ActiveModel::Transitions
32
+ ```
31
33
 
32
34
  #### Standalone
33
-
34
- gem install transitions
35
+ ```shell
36
+ gem install transitions
37
+ ```
35
38
 
36
39
  ### Using transitions
40
+ ```ruby
41
+ class Product
42
+ include ActiveModel::Transitions
37
43
 
38
- class Product
39
- include ActiveModel::Transitions
40
-
41
- state_machine do
42
- state :available # first one is initial state
43
- state :out_of_stock, :exit => :exit_out_of_stock
44
- state :discontinued, :enter => lambda { |product| product.cancel_orders }
45
-
46
- event :discontinued do
47
- transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
48
- end
49
- event :out_of_stock, :success => :reorder do
50
- transitions :to => :out_of_stock, :from => [:available, :discontinued]
51
- end
52
- event :available do
53
- transitions :to => :available, :from => [:out_of_stock], :guard => lambda { |product| product.in_stock > 0 }
54
- end
55
- end
56
- end
44
+ state_machine do
45
+ state :available # first one is initial state
46
+ state :out_of_stock, :exit => :exit_out_of_stock
47
+ state :discontinued, :enter => lambda { |product| product.cancel_orders }
57
48
 
49
+ event :discontinued do
50
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
51
+ end
52
+ event :out_of_stock, :success => :reorder do
53
+ transitions :to => :out_of_stock, :from => [:available, :discontinued]
54
+ end
55
+ event :available do
56
+ transitions :to => :available, :from => [:out_of_stock], :guard => lambda { |product| product.in_stock > 0 }
57
+ end
58
+ end
59
+ end
60
+ ```
58
61
  In this example we assume that you are in a rails project using Bundler, which
59
62
  would automatically require `transitions`. If this is not the case for you you
60
63
  have to add
61
-
62
- require 'transitions'
64
+ ```ruby
65
+ require 'transitions'
66
+ ```
63
67
 
64
68
  wherever you load your dependencies in your application.
65
69
 
@@ -80,18 +84,20 @@ wherever you load your dependencies in your application.
80
84
 
81
85
  Use the (surprise ahead) `current_state` method - in case you didn't set a
82
86
  state explicitly you'll get back the state that you defined as initial state.
83
-
84
- >> Product.new.current_state
85
- => :available
87
+ ```ruby
88
+ >> Product.new.current_state
89
+ => :available
90
+ ```
86
91
 
87
92
  You can also set a new state explicitly via `update_current_state(new_state,
88
93
  persist = true / false)` but you should never do this unless you really know
89
94
  what you're doing and why - rather use events / state transitions (see below).
90
95
 
91
96
  Predicate methods are also available using the name of the state.
92
-
93
- >> Product.new.available?
94
- => true
97
+ ```ruby
98
+ >> Product.new.available?
99
+ => true
100
+ ```
95
101
 
96
102
  #### Events
97
103
 
@@ -103,14 +109,16 @@ modify state but instead returns a boolean letting you know if a given
103
109
  transition is possible.
104
110
 
105
111
  In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collission with [https://github.com/ryanb/cancan](the authorization gem CanCan).
106
-
107
- >> Product.new.can_transition? :out_of_stock
108
- => true
112
+ ```ruby
113
+ >> Product.new.can_transition? :out_of_stock
114
+ => true
115
+ ```
109
116
 
110
117
  If you need to get all available transitions for current state you can simply call:
111
-
112
- >> Product.new.available_transitions
113
- => [:discontinued, :out_of_stock]
118
+ ```ruby
119
+ >> Product.new.available_transitions
120
+ => [:discontinued, :out_of_stock]
121
+ ```
114
122
 
115
123
  #### Automatic scope generation
116
124
 
@@ -118,26 +126,28 @@ If you need to get all available transitions for current state you can simply ca
118
126
  ActiveRecord and tell it to do so via the `auto_scopes` option:
119
127
 
120
128
  Given a model like this:
121
-
122
- class Order < ActiveRecord::Base
123
- include ActiveModel::Transitions
124
- state_machine :auto_scopes => true do
125
- state :pick_line_items
126
- state :picking_line_items
127
- event :move_cart do
128
- transitions to: :pick_line_items, from: :picking_line_items
129
- end
130
- end
129
+ ```ruby
130
+ class Order < ActiveRecord::Base
131
+ include ActiveModel::Transitions
132
+ state_machine :auto_scopes => true do
133
+ state :pick_line_items
134
+ state :picking_line_items
135
+ event :move_cart do
136
+ transitions to: :pick_line_items, from: :picking_line_items
131
137
  end
138
+ end
139
+ end
140
+ ```
132
141
 
133
142
  you can use this feature a la:
134
-
135
- >> Order.pick_line_items
136
- => []
137
- >> Order.create!
138
- => #<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">
139
- >> Order.pick_line_items
140
- => [#<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">]
143
+ ```ruby
144
+ >> Order.pick_line_items
145
+ => []
146
+ >> Order.create!
147
+ => #<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">
148
+ >> Order.pick_line_items
149
+ => [#<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">]
150
+ ```
141
151
 
142
152
  #### Using `guard`
143
153
 
@@ -145,16 +155,18 @@ Each event definition takes an optional `guard` argument, which acts as a
145
155
  predicate for the transition.
146
156
 
147
157
  You can pass in Symbols, Strings, or Procs like this:
148
-
149
- event :discontinue do
150
- transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => :can_discontinue
151
- end
158
+ ```ruby
159
+ event :discontinue do
160
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => :can_discontinue
161
+ end
162
+ ```
152
163
 
153
164
  or
154
-
155
- event :discontinue do
156
- transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => [:can_discontinue, :super_sure?]
157
- end
165
+ ```ruby
166
+ event :discontinue do
167
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :guard => [:can_discontinue, :super_sure?]
168
+ end
169
+ ```
158
170
 
159
171
  Any arguments passed to the event method will be passed on to the `guard`
160
172
  predicate.
@@ -166,10 +178,11 @@ you to execute methods on transition.
166
178
 
167
179
  You can pass in a Symbol, a String, a Proc or an Array containing method names
168
180
  as Symbol or String like this:
169
-
170
- event :discontinue do
171
- transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk]
172
- end
181
+ ```ruby
182
+ event :discontinue do
183
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk]
184
+ end
185
+ ```
173
186
 
174
187
  Any arguments passed to the event method will be passed on to the `on_transition` callback.
175
188
 
@@ -193,24 +206,27 @@ In case you need to trigger a method call after a successful transition you
193
206
  can use `success`. This will be called after the `save!` is complete (if you
194
207
  use the `state_name!` method) and should be used for any methods that require
195
208
  that the object be persisted.
196
-
197
- event :discontinue, :success => :notfiy_admin do
198
- transitions :to => :discontinued, :from => [:available, :out_of_stock]
199
- end
209
+ ```ruby
210
+ event :discontinue, :success => :notfiy_admin do
211
+ transitions :to => :discontinued, :from => [:available, :out_of_stock]
212
+ end
213
+ ```
200
214
 
201
215
  In addition to just specify the method name on the record as a symbol you can
202
216
  pass a lambda to perfom some more complex success callbacks:
203
-
204
- event :discontinue, :success => lambda { |order| AdminNotifier.notify_about_discontinued_order(order) } do
205
- transitions :to => :discontinued, :from => [:available, :out_of_stock]
206
- end
217
+ ```ruby
218
+ event :discontinue, :success => lambda { |order| AdminNotifier.notify_about_discontinued_order(order) } do
219
+ transitions :to => :discontinued, :from => [:available, :out_of_stock]
220
+ end
221
+ ```
207
222
 
208
223
  If you need it, you can even call multiple methods or lambdas just passing an
209
224
  array:
210
-
211
- event :discontinue, :success => [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do
212
- transitions :to => :discontinued, :from => [:available, :out_of_stock]
213
- end
225
+ ```ruby
226
+ event :discontinue, :success => [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do
227
+ transitions :to => :discontinued, :from => [:available, :out_of_stock]
228
+ end
229
+ ```
214
230
 
215
231
  #### Timestamps
216
232
 
@@ -218,17 +234,18 @@ If you'd like to note the time of a state change, Transitions comes with
218
234
  timestamps free! To activate them, simply pass the `timestamp` option to the
219
235
  event definition with a value of either true or the name of the timestamp
220
236
  column. *NOTE - This should be either true, a String or a Symbol*
221
-
222
- # This will look for an attribute called exploded_at or exploded_on (in that order)
223
- # If present, it will be updated
224
- event :explode, :timestamp => true do
225
- transitions :from => :complete, :to => :exploded
226
- end
227
-
228
- # This will look for an attribute named repaired_on to update upon save
229
- event :rebuild, :timestamp => :repaired_on do
230
- transitions :from => :exploded, :to => :rebuilt
231
- end
237
+ ```ruby
238
+ # This will look for an attribute called exploded_at or exploded_on (in that order)
239
+ # If present, it will be updated
240
+ event :explode, :timestamp => true do
241
+ transitions :from => :complete, :to => :exploded
242
+ end
243
+
244
+ # This will look for an attribute named repaired_on to update upon save
245
+ event :rebuild, :timestamp => :repaired_on do
246
+ transitions :from => :exploded, :to => :rebuilt
247
+ end
248
+ ```
232
249
 
233
250
  #### Using `event_fired` and `event_failed`
234
251
 
@@ -236,47 +253,52 @@ In case you define `event_fired` and / or `event_failed`, `transitions` will
236
253
  use those callbacks correspondingly.
237
254
 
238
255
  You can use those callbacks like this:
256
+ ```ruby
257
+ def event_fired(current_state, new_state, event)
258
+ MyLogger.info "Event fired #{event.inspect}"
259
+ end
239
260
 
240
- def event_fired(current_state, new_state, event)
241
- MyLogger.info "Event fired #{event.inspect}"
242
- end
243
-
244
- def event_failed(event)
245
- MyLogger.warn "Event failed #{event.inspect}"
246
- end
261
+ def event_failed(event)
262
+ MyLogger.warn "Event failed #{event.inspect}"
263
+ end
264
+ ```
247
265
 
248
266
  #### Listing all the available states and events
249
267
 
250
268
  You can easily get a listing of all available states:
251
-
252
- Order.available_states # Uses the <tt>default</tt> state machine
253
- # => [:pick_line_items, :picking_line_items]
269
+ ```ruby
270
+ Order.available_states # Uses the <tt>default</tt> state machine
271
+ # => [:pick_line_items, :picking_line_items]
272
+ ```
254
273
 
255
274
  Same goes for the available events:
256
-
257
- Order.available_events
258
- # => [:move_cart]
275
+ ```ruby
276
+ Order.available_events
277
+ # => [:move_cart]
278
+ ```
259
279
 
260
280
  #### Explicitly setting the initial state with the `initial` option
261
-
262
- state_machine :initial => :closed do
263
- state :open
264
- state :closed
265
- end
281
+ ```ruby
282
+ state_machine :initial => :closed do
283
+ state :open
284
+ state :closed
285
+ end
286
+ ```
266
287
 
267
288
  ### Configuring a different column name with ActiveRecord
268
289
 
269
290
  To use a different column than `state` to track it's value simply do this:
291
+ ```ruby
292
+ class Product < ActiveRecord::Base
293
+ include Transitions
270
294
 
271
- class Product < ActiveRecord::Base
272
- include Transitions
295
+ state_machine :attribute_name => :different_column do
273
296
 
274
- state_machine :attribute_name => :different_column do
297
+ ...
275
298
 
276
- ...
277
-
278
- end
279
- end
299
+ end
300
+ end
301
+ ```
280
302
 
281
303
  ### Known bugs / limitations
282
304
 
data/Rakefile CHANGED
@@ -2,6 +2,8 @@ require "bundler"
2
2
  Bundler::GemHelper.install_tasks
3
3
  Bundler.setup
4
4
 
5
+ require 'appraisal'
6
+
5
7
  require "rake/testtask"
6
8
  Rake::TestTask.new(:test) do |test|
7
9
  test.libs = %w(lib test)
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.0.10"
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
7
+ gem "sqlite3", :platforms=>:ruby
8
+
9
+ gemspec :path=>"../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>3.1.12"
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
7
+ gem "sqlite3", :platforms=>:ruby
8
+
9
+ gemspec :path=>"../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>3.2.14"
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms=>:jruby
7
+ gem "sqlite3", :platforms=>:ruby
8
+
9
+ gemspec :path=>"../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>4.0.0"
6
+ gem "activerecord-jdbcsqlite3-adapter", "1.3.0.rc1", :platforms=>:jruby
7
+ gem "sqlite3", :platforms=>:ruby
8
+
9
+ gemspec :path=>"../"
@@ -50,10 +50,10 @@ module ActiveModel
50
50
  validate :state_presence
51
51
  validate :state_inclusion
52
52
  end
53
-
53
+
54
54
  # The optional options argument is passed to find when reloading so you may
55
55
  # do e.g. record.reload(:lock => true) to reload the same record with an
56
- # exclusive row lock.
56
+ # exclusive row lock.
57
57
  def reload(options = nil)
58
58
  super.tap do
59
59
  sm = self.class.get_state_machine
@@ -75,7 +75,7 @@ module ActiveModel
75
75
  write_state_without_persistence(prev_state)
76
76
  raise
77
77
  end
78
-
78
+
79
79
  def write_state_without_persistence(state)
80
80
  ivar = self.class.get_state_machine.current_state_variable
81
81
  instance_variable_set(ivar, state)
@@ -88,8 +88,8 @@ module ActiveModel
88
88
 
89
89
  def set_initial_state
90
90
  # In case we use a query with a custom select that excludes our state attribute name we need to skip the initialization below.
91
- if self.has_attribute?(transitions_state_column_name)
92
- self[transitions_state_column_name] ||= self.class.get_state_machine.initial_state.to_s
91
+ if self.has_attribute?(transitions_state_column_name) && state_not_set?
92
+ self[transitions_state_column_name] = self.class.get_state_machine.initial_state.to_s
93
93
  self.class.get_state_machine.state_index[self[transitions_state_column_name].to_sym].call_action(:enter, self)
94
94
  end
95
95
  end
@@ -105,5 +105,9 @@ module ActiveModel
105
105
  self.errors.add(transitions_state_column_name, :inclusion, :value => self[transitions_state_column_name])
106
106
  end
107
107
  end
108
+
109
+ def state_not_set?
110
+ self[transitions_state_column_name].nil?
111
+ end
108
112
  end
109
113
  end
@@ -49,14 +49,14 @@ 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, *args)
52
+ if transition.executable?(obj, *args)
53
53
  next_state = to_state || Array(transition.to).first
54
54
  transition.execute(obj, *args)
55
+ update_event_timestamp(obj, next_state) if timestamp_defined?
55
56
  break
56
57
  end
57
58
  end
58
59
  # Update timestamps on obj if a timestamp has been defined
59
- update_event_timestamp(obj, next_state) if timestamp_defined?
60
60
  next_state
61
61
  end
62
62
 
@@ -29,7 +29,7 @@ module Transitions
29
29
  @options = opts
30
30
  end
31
31
 
32
- def perform(obj, *args)
32
+ def executable?(obj, *args)
33
33
  [@guard].flatten.all? { |g| perform_guard(obj, g, *args) }
34
34
  end
35
35
 
@@ -1,3 +1,3 @@
1
1
  module Transitions
2
- VERSION = '0.1.11'
2
+ VERSION = '0.1.12'
3
3
  end
@@ -5,12 +5,11 @@ class CreateTrafficLights < ActiveRecord::Migration
5
5
  create_table(:traffic_lights, :force => true) do |t|
6
6
  t.string :state
7
7
  t.string :name
8
+ t.string :power
8
9
  end
9
10
  end
10
11
  end
11
12
 
12
- set_up_db CreateTrafficLights
13
-
14
13
  class CreateDifferentTrafficLights < ActiveRecord::Migration
15
14
  def self.up
16
15
  create_table(:different_traffic_lights) do |t|
@@ -20,11 +19,8 @@ class CreateDifferentTrafficLights < ActiveRecord::Migration
20
19
  end
21
20
  end
22
21
 
23
- set_up_db CreateDifferentTrafficLights
24
-
25
22
  class TrafficLight < ActiveRecord::Base
26
23
  include ActiveModel::Transitions
27
- attr_reader :power
28
24
 
29
25
  state_machine :auto_scopes => true do
30
26
  state :off, enter: :turn_power_on
@@ -51,15 +47,11 @@ class TrafficLight < ActiveRecord::Base
51
47
  end
52
48
 
53
49
  def turn_power_on
54
- raise "the power should not have been on already" if @power == :on
55
- @power = :on
50
+ raise "the power should not have been on already" if power == "on"
51
+ self.power = "on"
56
52
  end
57
53
  end
58
54
 
59
- class ProtectedTrafficLight < TrafficLight
60
- attr_protected :state
61
- end
62
-
63
55
  class ValidatingTrafficLight < TrafficLight
64
56
  validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
65
57
  end
@@ -90,7 +82,12 @@ class TestActiveRecord < Test::Unit::TestCase
90
82
 
91
83
  test "calls enter when setting the initial state" do
92
84
  @new_light = TrafficLight.new
93
- assert_equal :on, @new_light.power
85
+ assert_equal "on", @new_light.power
86
+ end
87
+
88
+ test "does not call enter when loading a persisted record" do
89
+ assert_equal "on", @light.power
90
+ assert_nothing_raised { TrafficLight.find(@light.id) }
94
91
  end
95
92
 
96
93
  test "transition to a valid state" do
@@ -122,14 +119,6 @@ class TestActiveRecord < Test::Unit::TestCase
122
119
  assert_equal :off, @light.current_state
123
120
  end
124
121
 
125
- test "transition does persists state when state is protected" do
126
- protected_light = ProtectedTrafficLight.create!
127
- protected_light.reset!
128
- assert_equal :red, protected_light.current_state
129
- protected_light.reload
130
- assert_equal "red", protected_light.state
131
- end
132
-
133
122
  test "transition with wrong state will not validate" do
134
123
  for s in @light.class.get_state_machine.states
135
124
  @light.state = s.name
@@ -160,7 +149,7 @@ class TestActiveRecord < Test::Unit::TestCase
160
149
  @light.update_attribute(:state, 'green')
161
150
  assert @light.reload.green?, "reloaded state should come from database, not instance variable"
162
151
  end
163
-
152
+
164
153
  test "calling non-bang event updates state attribute" do
165
154
  @light.reset!
166
155
  assert @light.red?
@@ -170,6 +159,29 @@ class TestActiveRecord < Test::Unit::TestCase
170
159
  end
171
160
  end
172
161
 
162
+ if ActiveRecord::VERSION::MAJOR == 3
163
+
164
+ class TestMassAssignmentActiveRecord < Test::Unit::TestCase
165
+ # attr_protected unfortunately invokes a db call, so this test requires that
166
+ # we define the class after the table already exists.
167
+ def setup
168
+ set_up_db CreateTrafficLights
169
+
170
+ @light_with_protected_state = Class.new(TrafficLight) do
171
+ attr_protected :state
172
+ end
173
+ end
174
+
175
+ test "transition does persists state when state is protected" do
176
+ protected_light = @light_with_protected_state.create!
177
+ protected_light.reset!
178
+ assert_equal :red, protected_light.current_state
179
+ protected_light.reload
180
+ assert_equal "red", protected_light.state
181
+ end
182
+ end
183
+ end
184
+
173
185
  class TestNewActiveRecord < TestActiveRecord
174
186
 
175
187
  def setup
@@ -305,7 +317,7 @@ class TestActiveRecordWithDifferentColumnName < Test::Unit::TestCase
305
317
  @light.update_attribute(:different_state, 'green')
306
318
  assert @light.reload.green?, "reloaded state should come from database, not instance variable"
307
319
  end
308
-
320
+
309
321
  test "calling non-bang event updates state attribute" do
310
322
  @light.reset!
311
323
  assert @light.red?
@@ -1,9 +1,9 @@
1
1
  require "helper"
2
2
 
3
- class CreateBears < ActiveRecord::Migration
3
+ class CreateBunnies < ActiveRecord::Migration
4
4
  def self.up
5
- create_table(:bears, :force => true) do |t|
6
- t.string :state
5
+ create_table(:bunnies, :force => true) do |t|
6
+ t.string :status # Explicitly use another state column to ensure that this whole enchilada is working with other state column names than the default ones.
7
7
  end
8
8
  end
9
9
  end
@@ -16,16 +16,6 @@ class CreatePuppies < ActiveRecord::Migration
16
16
  end
17
17
  end
18
18
 
19
- class CreateBunnies < ActiveRecord::Migration
20
- def self.up
21
- create_table(:bunnies, :force => true) do |t|
22
- t.string :status # Explicitly use another state column to ensure that this whole enchilada is working with other state column names than the default ones.
23
- end
24
- end
25
- end
26
-
27
- set_up_db CreateBunnies, CreatePuppies
28
-
29
19
  class Bunny < ActiveRecord::Base
30
20
  include ActiveModel::Transitions
31
21
 
@@ -44,7 +34,7 @@ end
44
34
 
45
35
  class TestScopes < Test::Unit::TestCase
46
36
  def setup
47
- set_up_db CreateBears, CreateBunnies, CreatePuppies
37
+ set_up_db CreateBunnies, CreatePuppies
48
38
  @bunny = Bunny.create!
49
39
  end
50
40
 
@@ -9,12 +9,11 @@ class CreateOrders < ActiveRecord::Migration
9
9
  t.datetime :prepared_on
10
10
  t.datetime :dispatched_at
11
11
  t.date :cancellation_date
12
+ t.boolean :allow_transition, :default => true
12
13
  end
13
14
  end
14
15
  end
15
16
 
16
- set_up_db CreateOrders
17
-
18
17
  class Order < ActiveRecord::Base
19
18
  include ActiveModel::Transitions
20
19
 
@@ -25,7 +24,7 @@ class Order < ActiveRecord::Base
25
24
  state :prepared
26
25
  state :delivered
27
26
  state :cancelled
28
-
27
+
29
28
  # no timestamp col is being specified here - should be ignored
30
29
  event :place do
31
30
  transitions :from => :opened, :to => :placed
@@ -33,7 +32,7 @@ class Order < ActiveRecord::Base
33
32
 
34
33
  # should set paid_at timestamp
35
34
  event :pay, :timestamp => true do
36
- transitions :from => :placed, :to => :paid
35
+ transitions :from => :placed, :to => :paid, :guard => lambda { |obj| obj.allow_transition }
37
36
  end
38
37
 
39
38
  # should set prepared_on
@@ -45,12 +44,12 @@ class Order < ActiveRecord::Base
45
44
  event :deliver, :timestamp => "dispatched_at" do
46
45
  transitions :from => :prepared, :to => :delivered
47
46
  end
48
-
47
+
49
48
  # should set cancellation_date
50
49
  event :cancel, :timestamp => :cancellation_date do
51
50
  transitions :from => [:placed, :paid, :prepared], :to => :cancelled
52
51
  end
53
-
52
+
54
53
  # should raise an exception as there is no timestamp col
55
54
  event :reopen, :timestamp => true do
56
55
  transitions :from => :cancelled, :to => :opened
@@ -75,41 +74,49 @@ class TestActiveRecordTimestamps < Test::Unit::TestCase
75
74
  assert_nothing_raised { @order.place! }
76
75
  assert_equal @order.state, "placed"
77
76
  end
78
-
77
+
79
78
  test "moving to paid should set paid_at" do
80
- @order = create_order(:placed)
79
+ @order = create_order(:placed)
81
80
  @order.pay!
82
81
  @order.reload
83
82
  assert_not_nil @order.paid_at
84
83
  end
85
-
84
+
85
+ test "moving to paid should not set paid_at if our guard evaluates to false" do
86
+ @order = create_order(:placed)
87
+ @order.update_attribute :allow_transition, false
88
+ @order.pay!
89
+ @order.reload
90
+ assert_nil @order.paid_at
91
+ end
92
+
86
93
  test "moving to prepared should set prepared_on" do
87
- @order = create_order(:paid)
94
+ @order = create_order(:paid)
88
95
  @order.prepare!
89
96
  @order.reload
90
97
  assert_not_nil @order.prepared_on
91
98
  end
92
-
99
+
93
100
  test "moving to delivered should set dispatched_at" do
94
- @order = create_order(:prepared)
101
+ @order = create_order(:prepared)
95
102
  @order.deliver!
96
103
  @order.reload
97
104
  assert_not_nil @order.dispatched_at
98
105
  end
99
-
106
+
100
107
  test "moving to cancelled should set cancellation_date" do
101
- @order = create_order(:placed)
108
+ @order = create_order(:placed)
102
109
  @order.cancel!
103
110
  @order.reload
104
111
  assert_not_nil @order.cancellation_date
105
112
  end
106
-
113
+
107
114
  test "moving to reopened should raise an exception as there is no attribute" do
108
- @order = create_order(:cancelled)
109
- assert_raise(NoMethodError) { @order.re_open! }
115
+ @order = create_order(:cancelled)
116
+ assert_raise(NoMethodError) { @order.re_open! }
110
117
  @order.reload
111
118
  end
112
-
119
+
113
120
  test "passing an invalid value to timestamp options should raise an exception" do
114
121
  assert_raise(ArgumentError) do
115
122
  class Order < ActiveRecord::Base
@@ -119,8 +126,8 @@ class TestActiveRecordTimestamps < Test::Unit::TestCase
119
126
  transitions :from => :prepared, :to => :placed
120
127
  end
121
128
  end
122
-
123
- end
129
+
130
+ end
124
131
  end
125
132
  end
126
133
  end
@@ -1,18 +1,15 @@
1
1
  require "helper"
2
2
 
3
- # Regressiontest for https://github.com/troessner/transitions/issues/95
4
- # TODO We use this Trafficlight class quite a lot in our specs including a ton of duplication.
5
- # Unify class and migration definition in one place and then clean up all related specs, including this one.
6
- class CreateTrafficLights < ActiveRecord::Migration
3
+ # Regression test for https://github.com/troessner/transitions/issues/95
4
+ class CreateSwitches < ActiveRecord::Migration
7
5
  def self.up
8
- create_table(:traffic_lights, :force => true) do |t|
6
+ create_table(:switches, :force => true) do |t|
9
7
  t.string :state
10
- t.string :name
11
8
  end
12
9
  end
13
10
  end
14
11
 
15
- class TrafficLight < ActiveRecord::Base
12
+ class Switch < ActiveRecord::Base
16
13
  include ActiveModel::Transitions
17
14
 
18
15
  state_machine do
@@ -23,12 +20,12 @@ end
23
20
 
24
21
  class TestCustomSelect < Test::Unit::TestCase
25
22
  def setup
26
- set_up_db CreateTrafficLights
27
- @light = TrafficLight.create!
23
+ set_up_db CreateSwitches
24
+ Switch.create!
28
25
  end
29
26
 
30
27
  test "should not trigger an exception when we use a custom select query which excludes the name of our state attribute" do
31
- result = TrafficLight.select("id, name")
28
+ result = Switch.select(:id)
32
29
  assert_nothing_raised NoMethodError do
33
30
  result.inspect
34
31
  end
@@ -7,7 +7,7 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
7
7
  opts = {:from => "foo", :to => "bar"}
8
8
  st = Transitions::StateTransition.new(opts)
9
9
 
10
- assert st.perform(nil, *args)
10
+ assert st.executable?(nil, *args)
11
11
  end
12
12
 
13
13
  test "should call the method on the object if guard is a symbol" do
@@ -17,7 +17,7 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
17
17
  obj = stub
18
18
  obj.expects(:test_guard).with(*args)
19
19
 
20
- st.perform(obj, *args)
20
+ st.executable?(obj, *args)
21
21
  end
22
22
 
23
23
  test "should call the method on the object if guard is a string" do
@@ -27,7 +27,7 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
27
27
  obj = stub
28
28
  obj.expects(:test_guard).with(*args)
29
29
 
30
- st.perform(obj, *args)
30
+ st.executable?(obj, *args)
31
31
  end
32
32
 
33
33
  test "should call the proc passing the object if the guard is a proc" do
@@ -37,7 +37,7 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
37
37
  obj = stub
38
38
  obj.expects(:test_guard).with(*args)
39
39
 
40
- st.perform(obj, *args)
40
+ st.executable?(obj, *args)
41
41
  end
42
42
 
43
43
  test "should call the method on the object if guard is a symbol" do
@@ -48,7 +48,7 @@ class TestStateTransitionGuardCheck < Test::Unit::TestCase
48
48
  obj.expects(:test_guard).with(*args).returns(true)
49
49
  obj.expects(:test_another_guard).with(*args).returns(true)
50
50
 
51
- assert st.perform(obj, *args)
51
+ assert st.executable?(obj, *args)
52
52
  end
53
53
 
54
54
  end
data/transitions.gemspec CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.add_development_dependency "mocha", '~> 0.11.0' # With mocha 0.12 we get: undefined method `run' for #<StateMachineMachineTest:0x94918b8> (NoMethodError)
20
20
  s.add_development_dependency "rake"
21
21
  s.add_development_dependency "random_data"
22
- s.add_development_dependency "sqlite3"
23
- s.add_development_dependency "activerecord", "~> 3"
22
+ s.add_development_dependency 'appraisal'
23
+ s.add_development_dependency "activerecord", [">= 3.0", "<= 4.0"]
24
24
 
25
25
  s.files = `git ls-files`.split("\n")
26
26
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
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.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Kuzma
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-09 00:00:00.000000000 Z
12
+ date: 2013-11-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -82,7 +82,7 @@ dependencies:
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
84
  - !ruby/object:Gem::Dependency
85
- name: sqlite3
85
+ name: appraisal
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - '>='
@@ -99,16 +99,22 @@ dependencies:
99
99
  name: activerecord
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - ~>
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '3.0'
105
+ - - <=
103
106
  - !ruby/object:Gem::Version
104
- version: '3'
107
+ version: '4.0'
105
108
  type: :development
106
109
  prerelease: false
107
110
  version_requirements: !ruby/object:Gem::Requirement
108
111
  requirements:
109
- - - ~>
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '3.0'
115
+ - - <=
110
116
  - !ruby/object:Gem::Version
111
- version: '3'
117
+ version: '4.0'
112
118
  description: Lightweight state machine extracted from ActiveModel
113
119
  email: timo.roessner@googlemail.com
114
120
  executables: []
@@ -119,11 +125,16 @@ files:
119
125
  - .ruby-gemset
120
126
  - .ruby-version
121
127
  - .travis.yml
128
+ - Appraisals
122
129
  - CHANGELOG.md
123
130
  - Gemfile
124
131
  - MIT-LICENSE.txt
125
132
  - README.md
126
133
  - Rakefile
134
+ - gemfiles/rails_3_0.gemfile
135
+ - gemfiles/rails_3_1.gemfile
136
+ - gemfiles/rails_3_2.gemfile
137
+ - gemfiles/rails_4_0.gemfile
127
138
  - lib/active_model/transitions.rb
128
139
  - lib/active_record/transitions.rb
129
140
  - lib/transitions.rb
@@ -172,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
183
  version: 1.3.6
173
184
  requirements: []
174
185
  rubyforge_project: transitions
175
- rubygems_version: 2.0.6
186
+ rubygems_version: 2.1.11
176
187
  signing_key:
177
188
  specification_version: 4
178
189
  summary: State machine extracted from ActiveModel