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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Appraisals +23 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -1
- data/README.md +138 -116
- data/Rakefile +2 -0
- data/gemfiles/rails_3_0.gemfile +9 -0
- data/gemfiles/rails_3_1.gemfile +9 -0
- data/gemfiles/rails_3_2.gemfile +9 -0
- data/gemfiles/rails_4_0.gemfile +9 -0
- data/lib/active_model/transitions.rb +9 -5
- data/lib/transitions/event.rb +2 -2
- data/lib/transitions/state_transition.rb +1 -1
- data/lib/transitions/version.rb +1 -1
- data/test/active_record/test_active_record.rb +34 -22
- data/test/active_record/test_active_record_scopes.rb +4 -14
- data/test/active_record/test_active_record_timestamps.rb +27 -20
- data/test/active_record/test_custom_select.rb +7 -10
- data/test/state_transition/test_state_transition_guard_check.rb +5 -5
- data/transitions.gemspec +2 -2
- metadata +19 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5fdc12a9620c4afb752d88f581df0416e1bfb2c
|
4
|
+
data.tar.gz: 2e513473a1ab671758977874cc4670c43a38d76b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8843c2845f29a81cc1a4fe170796976850515a3d5b513996e50e4904ae8a418d47fa38be5e72f1a325623e0295910df7022c4f061c395bc9aeb8120a6d84e2c8
|
7
|
+
data.tar.gz: a00d3f4ed41f725e47e4afc81aa030958e86d966f1192008365c988e9a167d3243dd2d79126fd7365a2965cd58071779294b59d8caaefef2849e3f41d43c56f5
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
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
data/Gemfile
CHANGED
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
|
-
|
25
|
+
```ruby
|
26
|
+
gem "transitions", :require => ["transitions", "active_model/transitions"]
|
27
|
+
```
|
27
28
|
|
28
29
|
… and this into your ORM model:
|
29
|
-
|
30
|
-
|
30
|
+
```ruby
|
31
|
+
include ActiveModel::Transitions
|
32
|
+
```
|
31
33
|
|
32
34
|
#### Standalone
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
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
|
-
|
94
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
113
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
253
|
-
|
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
|
-
|
258
|
-
|
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
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
272
|
-
include Transitions
|
295
|
+
state_machine :attribute_name => :different_column do
|
273
296
|
|
274
|
-
|
297
|
+
...
|
275
298
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
```
|
280
302
|
|
281
303
|
### Known bugs / limitations
|
282
304
|
|
data/Rakefile
CHANGED
@@ -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]
|
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
|
data/lib/transitions/event.rb
CHANGED
@@ -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.
|
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
|
|
data/lib/transitions/version.rb
CHANGED
@@ -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
|
55
|
-
|
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
|
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
|
3
|
+
class CreateBunnies < ActiveRecord::Migration
|
4
4
|
def self.up
|
5
|
-
create_table(:
|
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
|
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
|
-
#
|
4
|
-
|
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(:
|
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
|
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
|
27
|
-
|
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 =
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
23
|
-
s.add_development_dependency "activerecord", "
|
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.
|
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-
|
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:
|
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: '
|
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: '
|
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.
|
186
|
+
rubygems_version: 2.1.11
|
176
187
|
signing_key:
|
177
188
|
specification_version: 4
|
178
189
|
summary: State machine extracted from ActiveModel
|