state_machine 0.4.3 → 0.5.0
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.rdoc +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +54 -84
- data/Rakefile +1 -1
- data/examples/Car_state.png +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/examples/auto_shop.rb +11 -0
- data/examples/car.rb +19 -0
- data/examples/traffic_light.rb +9 -0
- data/examples/vehicle.rb +35 -0
- data/lib/state_machine.rb +65 -52
- data/lib/state_machine/assertions.rb +1 -1
- data/lib/state_machine/callback.rb +13 -9
- data/lib/state_machine/eval_helpers.rb +4 -3
- data/lib/state_machine/event.rb +51 -33
- data/lib/state_machine/extensions.rb +2 -2
- data/lib/state_machine/guard.rb +47 -41
- data/lib/state_machine/integrations.rb +67 -0
- data/lib/state_machine/integrations/active_record.rb +62 -36
- data/lib/state_machine/integrations/active_record/observer.rb +41 -0
- data/lib/state_machine/integrations/data_mapper.rb +23 -37
- data/lib/state_machine/integrations/data_mapper/observer.rb +23 -9
- data/lib/state_machine/integrations/sequel.rb +23 -24
- data/lib/state_machine/machine.rb +380 -277
- data/lib/state_machine/node_collection.rb +142 -0
- data/lib/state_machine/state.rb +114 -69
- data/lib/state_machine/state_collection.rb +38 -0
- data/lib/state_machine/transition.rb +36 -17
- data/test/active_record.log +2940 -85664
- data/test/functional/state_machine_test.rb +49 -53
- data/test/sequel.log +747 -11990
- data/test/unit/assertions_test.rb +2 -1
- data/test/unit/callback_test.rb +14 -12
- data/test/unit/eval_helpers_test.rb +25 -6
- data/test/unit/event_test.rb +144 -124
- data/test/unit/guard_test.rb +118 -140
- data/test/unit/integrations/active_record_test.rb +102 -68
- data/test/unit/integrations/data_mapper_test.rb +48 -37
- data/test/unit/integrations/sequel_test.rb +34 -25
- data/test/unit/integrations_test.rb +42 -0
- data/test/unit/machine_test.rb +460 -531
- data/test/unit/node_collection_test.rb +208 -0
- data/test/unit/state_collection_test.rb +167 -0
- data/test/unit/state_machine_test.rb +1 -1
- data/test/unit/state_test.rb +223 -200
- data/test/unit/transition_test.rb +81 -46
- metadata +17 -3
- data/test/data_mapper.log +0 -30860
@@ -8,9 +8,9 @@ module StateMachine
|
|
8
8
|
# ActiveRecord model:
|
9
9
|
#
|
10
10
|
# class Vehicle < ActiveRecord::Base
|
11
|
-
# state_machine :initial =>
|
11
|
+
# state_machine :initial => :parked do
|
12
12
|
# event :ignite do
|
13
|
-
# transition :to =>
|
13
|
+
# transition :to => :idling, :from => :parked
|
14
14
|
# end
|
15
15
|
# end
|
16
16
|
# end
|
@@ -28,7 +28,7 @@ module StateMachine
|
|
28
28
|
#
|
29
29
|
# For example,
|
30
30
|
#
|
31
|
-
# vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state:
|
31
|
+
# vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
|
32
32
|
# vehicle.name = 'Ford Explorer'
|
33
33
|
# vehicle.ignite # => true
|
34
34
|
# vehicle.reload # => #<Vehicle id: 1, name: "Ford Explorer", state: "idling">
|
@@ -51,7 +51,7 @@ module StateMachine
|
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state:
|
54
|
+
# vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
|
55
55
|
# vehicle.ignite # => false
|
56
56
|
# Message.count # => 0
|
57
57
|
#
|
@@ -66,21 +66,24 @@ module StateMachine
|
|
66
66
|
# scopes are defined on the model for finding records with or without a
|
67
67
|
# particular set of states.
|
68
68
|
#
|
69
|
-
# These named scopes are the functional equivalent of the
|
70
|
-
# definitions:
|
69
|
+
# These named scopes are essentially the functional equivalent of the
|
70
|
+
# following definitions:
|
71
71
|
#
|
72
72
|
# class Vehicle < ActiveRecord::Base
|
73
|
-
# named_scope :with_states, lambda {|*
|
73
|
+
# named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
|
74
74
|
# # with_states also aliased to with_state
|
75
75
|
#
|
76
|
-
# named_scope :without_states, lambda {|*
|
76
|
+
# named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
|
77
77
|
# # without_states also aliased to without_state
|
78
78
|
# end
|
79
79
|
#
|
80
|
+
# *Note*, however, that the states are converted to their stored values
|
81
|
+
# before being passed into the query.
|
82
|
+
#
|
80
83
|
# Because of the way named scopes work in ActiveRecord, they can be
|
81
84
|
# chained like so:
|
82
85
|
#
|
83
|
-
# Vehicle.with_state(
|
86
|
+
# Vehicle.with_state(:parked).all(:order => 'id DESC')
|
84
87
|
#
|
85
88
|
# == Callbacks
|
86
89
|
#
|
@@ -91,8 +94,8 @@ module StateMachine
|
|
91
94
|
# For example,
|
92
95
|
#
|
93
96
|
# class Vehicle < ActiveRecord::Base
|
94
|
-
# state_machine :initial =>
|
95
|
-
# before_transition :to =>
|
97
|
+
# state_machine :initial => :parked do
|
98
|
+
# before_transition :to => :idling do |vehicle|
|
96
99
|
# vehicle.put_on_seatbelt
|
97
100
|
# end
|
98
101
|
#
|
@@ -101,7 +104,7 @@ module StateMachine
|
|
101
104
|
# end
|
102
105
|
#
|
103
106
|
# event :ignite do
|
104
|
-
# transition :to =>
|
107
|
+
# transition :to => :idling, :from => :parked
|
105
108
|
# end
|
106
109
|
# end
|
107
110
|
#
|
@@ -164,6 +167,11 @@ module StateMachine
|
|
164
167
|
defined?(::ActiveRecord::Base) && klass <= ::ActiveRecord::Base
|
165
168
|
end
|
166
169
|
|
170
|
+
# Loads additional files specific to ActiveRecord
|
171
|
+
def self.extended(base) #:nodoc:
|
172
|
+
require 'state_machine/integrations/active_record/observer'
|
173
|
+
end
|
174
|
+
|
167
175
|
# Runs a new database transaction, rolling back any changes by raising
|
168
176
|
# an ActiveRecord::Rollback exception if the yielded block fails
|
169
177
|
# (i.e. returns false).
|
@@ -188,24 +196,23 @@ module StateMachine
|
|
188
196
|
# Forces all attribute methods to be generated for the model so that
|
189
197
|
# the reader/writer methods for the attribute are available
|
190
198
|
def define_attribute_accessor
|
191
|
-
|
199
|
+
# If an exception is raised while trying to access the connection, then
|
200
|
+
# the assumption is that there's an issue with the database (most likely
|
201
|
+
# doesn't exist yet), so we won't be able to check the table properties
|
202
|
+
connection_exists = begin; owner_class.connection; true; rescue Exception; false; end
|
203
|
+
|
204
|
+
if connection_exists && owner_class.table_exists?
|
192
205
|
owner_class.define_attribute_methods
|
193
206
|
|
194
207
|
# Support attribute predicate for ActiveRecord columns
|
195
|
-
if owner_class.column_names.include?(attribute)
|
208
|
+
if owner_class.column_names.include?(attribute.to_s)
|
196
209
|
attribute = self.attribute
|
197
210
|
|
198
211
|
owner_class.class_eval do
|
212
|
+
# Checks whether the current state is a given value. If there
|
213
|
+
# are no arguments, then this checks for the presence of the attribute.
|
199
214
|
define_method("#{attribute}?") do |*args|
|
200
|
-
|
201
|
-
# No arguments: querying for presence of the attribute
|
202
|
-
super
|
203
|
-
else
|
204
|
-
# Arguments: querying for the attribute's current value
|
205
|
-
state = args.first
|
206
|
-
raise ArgumentError, "#{state.inspect} is not a known #{attribute} value" unless self.class.state_machines[attribute].states.include?(state)
|
207
|
-
send(attribute) == state
|
208
|
-
end
|
215
|
+
args.empty? ? super(*args) : self.class.state_machines[attribute].state?(self, *args)
|
209
216
|
end
|
210
217
|
end
|
211
218
|
end
|
@@ -214,18 +221,18 @@ module StateMachine
|
|
214
221
|
super
|
215
222
|
end
|
216
223
|
|
217
|
-
#
|
218
|
-
#
|
219
|
-
def
|
224
|
+
# Creates a scope for finding records *with* a particular state or
|
225
|
+
# states for the attribute
|
226
|
+
def create_with_scope(name)
|
220
227
|
attribute = self.attribute
|
221
|
-
|
228
|
+
define_scope(name, lambda {|values| {:conditions => {attribute => values}}})
|
222
229
|
end
|
223
230
|
|
224
|
-
#
|
225
|
-
#
|
226
|
-
def
|
231
|
+
# Creates a scope for finding records *without* a particular state or
|
232
|
+
# states for the attribute
|
233
|
+
def create_without_scope(name)
|
227
234
|
attribute = self.attribute
|
228
|
-
|
235
|
+
define_scope(name, lambda {|values| {:conditions => ["#{attribute} NOT IN (?)", values]}})
|
229
236
|
end
|
230
237
|
|
231
238
|
# Creates a new callback in the callback chain, always inserting it
|
@@ -240,6 +247,28 @@ module StateMachine
|
|
240
247
|
end
|
241
248
|
|
242
249
|
private
|
250
|
+
# Defines a new named scope with the given name. Since ActiveRecord
|
251
|
+
# does not allow direct access to the model being used within the
|
252
|
+
# evaluation of a dynamic named scope, the scope must be generated
|
253
|
+
# manually. It's necessary to have access to the model so that the
|
254
|
+
# state names can be translated to their associated values and so that
|
255
|
+
# inheritance is respected properly.
|
256
|
+
def define_scope(name, scope)
|
257
|
+
name = name.to_sym
|
258
|
+
attribute = self.attribute
|
259
|
+
|
260
|
+
# Created the scope and then override it with state translation
|
261
|
+
owner_class.named_scope(name)
|
262
|
+
owner_class.scopes[name] = lambda do |klass, *states|
|
263
|
+
machine_states = klass.state_machines[attribute].states
|
264
|
+
values = states.flatten.map {|state| machine_states.fetch(state).value}
|
265
|
+
|
266
|
+
::ActiveRecord::NamedScope::Scope.new(klass, scope.call(values))
|
267
|
+
end
|
268
|
+
|
269
|
+
false
|
270
|
+
end
|
271
|
+
|
243
272
|
# Notifies observers on the given object that a callback occurred
|
244
273
|
# involving the given transition. This will attempt to call the
|
245
274
|
# following methods on observers:
|
@@ -251,11 +280,8 @@ module StateMachine
|
|
251
280
|
def notify(type, object, transition)
|
252
281
|
qualified_event = namespace ? "#{transition.event}_#{namespace}" : transition.event
|
253
282
|
["#{type}_#{qualified_event}", "#{type}_transition"].each do |method|
|
254
|
-
object.class.
|
255
|
-
|
256
|
-
observer.send(method, object, transition) if observer.respond_to?(method)
|
257
|
-
end if defined?(@observer_peers)
|
258
|
-
end
|
283
|
+
object.class.changed
|
284
|
+
object.class.notify_observers(method, object, transition)
|
259
285
|
end
|
260
286
|
|
261
287
|
true
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module StateMachine
|
2
|
+
module Integrations #:nodoc:
|
3
|
+
module ActiveRecord
|
4
|
+
# Adds support for invoking callbacks on ActiveRecord observers with more
|
5
|
+
# than one argument (e.g. the record *and* the state transition). By
|
6
|
+
# default, ActiveRecord only supports passing the record into the
|
7
|
+
# callbacks.
|
8
|
+
#
|
9
|
+
# For example:
|
10
|
+
#
|
11
|
+
# class VehicleObserver < ActiveRecord::Observer
|
12
|
+
# # The default behavior: only pass in the record
|
13
|
+
# def after_save(vehicle)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Custom behavior: allow the transition to be passed in as well
|
17
|
+
# def after_transition(vehicle, transition)
|
18
|
+
# Audit.log(vehicle, transition)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
module Observer
|
22
|
+
def self.included(base) #:nodoc:
|
23
|
+
base.class_eval do
|
24
|
+
alias_method :update_without_multiple_args, :update
|
25
|
+
alias_method :update, :update_with_multiple_args
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Allows additional arguments other than the object to be passed to the
|
30
|
+
# observed methods
|
31
|
+
def update_with_multiple_args(observed_method, object, *args) #:nodoc:
|
32
|
+
send(observed_method, object, *args) if respond_to?(observed_method)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::Observer.class_eval do
|
40
|
+
include StateMachine::Integrations::ActiveRecord::Observer
|
41
|
+
end
|
@@ -2,19 +2,6 @@ module StateMachine
|
|
2
2
|
module Integrations #:nodoc:
|
3
3
|
# Adds support for integrating state machines with DataMapper resources.
|
4
4
|
#
|
5
|
-
# == Requirements
|
6
|
-
#
|
7
|
-
# To use this feature of the DataMapper integration, the dm-observer library
|
8
|
-
# must be available. This can be installed either directly or indirectly
|
9
|
-
# through dm-more. When loading DataMapper, be sure to load the dm-observer
|
10
|
-
# library as well like so:
|
11
|
-
#
|
12
|
-
# require 'rubygems'
|
13
|
-
# require 'dm-core'
|
14
|
-
# require 'dm-observer'
|
15
|
-
#
|
16
|
-
# If dm-observer is not available, then this feature will be skipped.
|
17
|
-
#
|
18
5
|
# == Examples
|
19
6
|
#
|
20
7
|
# Below is an example of a simple state machine defined within a
|
@@ -27,9 +14,9 @@ module StateMachine
|
|
27
14
|
# property :name, String
|
28
15
|
# property :state, String
|
29
16
|
#
|
30
|
-
# state_machine :initial =>
|
17
|
+
# state_machine :initial => :parked do
|
31
18
|
# event :ignite do
|
32
|
-
# transition :to =>
|
19
|
+
# transition :to => :idling, :from => :parked
|
33
20
|
# end
|
34
21
|
# end
|
35
22
|
# end
|
@@ -47,7 +34,7 @@ module StateMachine
|
|
47
34
|
#
|
48
35
|
# For example,
|
49
36
|
#
|
50
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=
|
37
|
+
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
51
38
|
# vehicle.name = 'Ford Explorer'
|
52
39
|
# vehicle.ignite # => true
|
53
40
|
# vehicle.reload # => #<Vehicle id=1 name="Ford Explorer" state="idling">
|
@@ -74,7 +61,7 @@ module StateMachine
|
|
74
61
|
# end
|
75
62
|
# end
|
76
63
|
#
|
77
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=
|
64
|
+
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
78
65
|
# vehicle.ignite # => false
|
79
66
|
# Message.all.count # => 0
|
80
67
|
#
|
@@ -99,22 +86,25 @@ module StateMachine
|
|
99
86
|
# property :state, String
|
100
87
|
#
|
101
88
|
# class << self
|
102
|
-
# def with_states(*
|
103
|
-
# all(:state =>
|
89
|
+
# def with_states(*states)
|
90
|
+
# all(:state => states.flatten)
|
104
91
|
# end
|
105
92
|
# alias_method :with_state, :with_states
|
106
93
|
#
|
107
|
-
# def without_states(*
|
108
|
-
# all(:state.not =>
|
94
|
+
# def without_states(*states)
|
95
|
+
# all(:state.not => states.flatten)
|
109
96
|
# end
|
110
97
|
# alias_method :without_state, :without_states
|
111
98
|
# end
|
112
99
|
# end
|
113
100
|
#
|
101
|
+
# *Note*, however, that the states are converted to their stored values
|
102
|
+
# before being passed into the query.
|
103
|
+
#
|
114
104
|
# Because of the way scopes work in DataMapper, they can be chained like
|
115
105
|
# so:
|
116
106
|
#
|
117
|
-
# Vehicle.with_state(
|
107
|
+
# Vehicle.with_state(:parked).all(:order => [:id.desc])
|
118
108
|
#
|
119
109
|
# == Callbacks / Observers
|
120
110
|
#
|
@@ -131,8 +121,8 @@ module StateMachine
|
|
131
121
|
# property :id, Serial
|
132
122
|
# property :state, String
|
133
123
|
#
|
134
|
-
# state_machine :initial =>
|
135
|
-
# before_transition :to =>
|
124
|
+
# state_machine :initial => :parked do
|
125
|
+
# before_transition :to => :idling do
|
136
126
|
# put_on_seatbelt
|
137
127
|
# end
|
138
128
|
#
|
@@ -141,7 +131,7 @@ module StateMachine
|
|
141
131
|
# end
|
142
132
|
#
|
143
133
|
# event :ignite do
|
144
|
-
# transition :to =>
|
134
|
+
# transition :to => :idling, :from => :parked
|
145
135
|
# end
|
146
136
|
# end
|
147
137
|
#
|
@@ -181,22 +171,18 @@ module StateMachine
|
|
181
171
|
:save
|
182
172
|
end
|
183
173
|
|
184
|
-
#
|
185
|
-
#
|
186
|
-
def
|
174
|
+
# Creates a scope for finding records *with* a particular state or
|
175
|
+
# states for the attribute
|
176
|
+
def create_with_scope(name)
|
187
177
|
attribute = self.attribute
|
188
|
-
|
189
|
-
define_method(name) {|*values| all(attribute => values.flatten)}
|
190
|
-
end
|
178
|
+
lambda {|resource, values| resource.all(attribute => values)}
|
191
179
|
end
|
192
180
|
|
193
|
-
#
|
194
|
-
#
|
195
|
-
def
|
181
|
+
# Creates a scope for finding records *without* a particular state or
|
182
|
+
# states for the attribute
|
183
|
+
def create_without_scope(name)
|
196
184
|
attribute = self.attribute
|
197
|
-
|
198
|
-
define_method(name) {|*values| all(attribute.to_sym.not => values.flatten)}
|
199
|
-
end
|
185
|
+
lambda {|resource, values| resource.all(attribute.to_sym.not => values)}
|
200
186
|
end
|
201
187
|
|
202
188
|
# Creates a new callback in the callback chain, always ensuring that
|
@@ -22,6 +22,20 @@ module StateMachine
|
|
22
22
|
# Audit.log(self, transition) if saved
|
23
23
|
# end
|
24
24
|
# end
|
25
|
+
#
|
26
|
+
# == Requirements
|
27
|
+
#
|
28
|
+
# To use this feature of the DataMapper integration, the dm-observer library
|
29
|
+
# must be available. This can be installed either directly or indirectly
|
30
|
+
# through dm-more. When loading DataMapper, be sure to load the dm-observer
|
31
|
+
# library as well like so:
|
32
|
+
#
|
33
|
+
# require 'rubygems'
|
34
|
+
# require 'dm-core'
|
35
|
+
# require 'dm-observer'
|
36
|
+
#
|
37
|
+
# If dm-observer is not available, then this feature will be skipped.
|
38
|
+
#
|
25
39
|
module Observer
|
26
40
|
# Creates a callback that will be invoked *before* a transition is
|
27
41
|
# performed, so long as the given configuration options match the
|
@@ -39,9 +53,9 @@ module StateMachine
|
|
39
53
|
# property :id, Serial
|
40
54
|
# property :state, :String
|
41
55
|
#
|
42
|
-
# state_machine :initial =>
|
56
|
+
# state_machine :initial => :parked do
|
43
57
|
# event :ignite do
|
44
|
-
# transition :to =>
|
58
|
+
# transition :to => :idling, :from => :parked
|
45
59
|
# end
|
46
60
|
# end
|
47
61
|
# end
|
@@ -56,12 +70,12 @@ module StateMachine
|
|
56
70
|
# end
|
57
71
|
#
|
58
72
|
# # Target all state machines
|
59
|
-
# before_transition :to =>
|
73
|
+
# before_transition :to => :idling, :from => :parked, :on => :ignite do
|
60
74
|
# # put on seatbelt
|
61
75
|
# end
|
62
76
|
#
|
63
77
|
# # Target a specific state machine
|
64
|
-
# before_transition :state, :to =>
|
78
|
+
# before_transition :state, :to => :idling do
|
65
79
|
# # put on seatbelt
|
66
80
|
# end
|
67
81
|
#
|
@@ -95,9 +109,9 @@ module StateMachine
|
|
95
109
|
# property :id, Serial
|
96
110
|
# property :state, :String
|
97
111
|
#
|
98
|
-
# state_machine :initial =>
|
112
|
+
# state_machine :initial => :parked do
|
99
113
|
# event :ignite do
|
100
|
-
# transition :to =>
|
114
|
+
# transition :to => :idling, :from => :parked
|
101
115
|
# end
|
102
116
|
# end
|
103
117
|
# end
|
@@ -112,12 +126,12 @@ module StateMachine
|
|
112
126
|
# end
|
113
127
|
#
|
114
128
|
# # Target all state machines
|
115
|
-
# after_transition :to =>
|
129
|
+
# after_transition :to => :idling, :from => :parked, :on => :ignite do
|
116
130
|
# # put on seatbelt
|
117
131
|
# end
|
118
132
|
#
|
119
133
|
# # Target a specific state machine
|
120
|
-
# after_transition :state, :to =>
|
134
|
+
# after_transition :state, :to => :idling do
|
121
135
|
# # put on seatbelt
|
122
136
|
# end
|
123
137
|
#
|
@@ -143,7 +157,7 @@ module StateMachine
|
|
143
157
|
def add_transition_callback(type, *args, &block)
|
144
158
|
if args.first && !args.first.is_a?(Hash)
|
145
159
|
# Specific attribute is being targeted
|
146
|
-
attribute = args.first
|
160
|
+
attribute = args.first
|
147
161
|
transition_args = args[1..-1]
|
148
162
|
else
|
149
163
|
# Target all state machines
|
@@ -8,9 +8,9 @@ module StateMachine
|
|
8
8
|
# Sequel model:
|
9
9
|
#
|
10
10
|
# class Vehicle < Sequel::Model
|
11
|
-
# state_machine :initial =>
|
11
|
+
# state_machine :initial => :parked do
|
12
12
|
# event :ignite do
|
13
|
-
# transition :to =>
|
13
|
+
# transition :to => :idling, :from => :parked
|
14
14
|
# end
|
15
15
|
# end
|
16
16
|
# end
|
@@ -28,7 +28,7 @@ module StateMachine
|
|
28
28
|
#
|
29
29
|
# For example,
|
30
30
|
#
|
31
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=
|
31
|
+
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
32
32
|
# vehicle.name = 'Ford Explorer'
|
33
33
|
# vehicle.ignite # => true
|
34
34
|
# vehicle.refresh # => #<Vehicle id=1 name="Ford Explorer" state="idling">
|
@@ -51,7 +51,7 @@ module StateMachine
|
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=
|
54
|
+
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
55
55
|
# vehicle.ignite # => false
|
56
56
|
# Message.count # => 0
|
57
57
|
#
|
@@ -71,21 +71,24 @@ module StateMachine
|
|
71
71
|
#
|
72
72
|
# class Vehicle < Sequel::Model
|
73
73
|
# class << self
|
74
|
-
# def with_states(*
|
75
|
-
# filter(:state =>
|
74
|
+
# def with_states(*states)
|
75
|
+
# filter(:state => states)
|
76
76
|
# end
|
77
77
|
# alias_method :with_state, :with_states
|
78
78
|
#
|
79
|
-
# def without_states(*
|
80
|
-
# filter(~{:state =>
|
79
|
+
# def without_states(*states)
|
80
|
+
# filter(~{:state => states})
|
81
81
|
# end
|
82
82
|
# alias_method :without_state, :without_states
|
83
83
|
# end
|
84
84
|
# end
|
85
85
|
#
|
86
|
+
# *Note*, however, that the states are converted to their stored values
|
87
|
+
# before being passed into the query.
|
88
|
+
#
|
86
89
|
# Because of the way scopes work in Sequel, they can be chained like so:
|
87
90
|
#
|
88
|
-
# Vehicle.with_state(
|
91
|
+
# Vehicle.with_state(:parked).order(:id.desc)
|
89
92
|
#
|
90
93
|
# == Callbacks
|
91
94
|
#
|
@@ -97,8 +100,8 @@ module StateMachine
|
|
97
100
|
# For example,
|
98
101
|
#
|
99
102
|
# class Vehicle < Sequel::Model
|
100
|
-
# state_machine :initial =>
|
101
|
-
# before_transition :to =>
|
103
|
+
# state_machine :initial => :parked do
|
104
|
+
# before_transition :to => :idling do
|
102
105
|
# put_on_seatbelt
|
103
106
|
# end
|
104
107
|
#
|
@@ -107,7 +110,7 @@ module StateMachine
|
|
107
110
|
# end
|
108
111
|
#
|
109
112
|
# event :ignite do
|
110
|
-
# transition :to =>
|
113
|
+
# transition :to => :idling, :from => :parked
|
111
114
|
# end
|
112
115
|
# end
|
113
116
|
#
|
@@ -138,22 +141,18 @@ module StateMachine
|
|
138
141
|
:save
|
139
142
|
end
|
140
143
|
|
141
|
-
#
|
142
|
-
#
|
143
|
-
def
|
144
|
+
# Creates a scope for finding records *with* a particular state or
|
145
|
+
# states for the attribute
|
146
|
+
def create_with_scope(name)
|
144
147
|
attribute = self.attribute
|
145
|
-
|
146
|
-
define_method(name) {|*values| filter(attribute.to_sym => values.flatten)}
|
147
|
-
end
|
148
|
+
lambda {|model, values| model.filter(attribute.to_sym => values)}
|
148
149
|
end
|
149
150
|
|
150
|
-
#
|
151
|
-
#
|
152
|
-
def
|
151
|
+
# Creates a scope for finding records *without* a particular state or
|
152
|
+
# states for the attribute
|
153
|
+
def create_without_scope(name)
|
153
154
|
attribute = self.attribute
|
154
|
-
|
155
|
-
define_method(name) {|*values| filter(~{attribute.to_sym => values.flatten})}
|
156
|
-
end
|
155
|
+
lambda {|model, values| model.filter(~{attribute.to_sym => values})}
|
157
156
|
end
|
158
157
|
|
159
158
|
# Creates a new callback in the callback chain, always ensuring that
|