state_machine 0.7.5 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -109,19 +109,19 @@ module StateMachine
109
109
  # state machines for each observed class.
110
110
  def add_transition_callback(type, *args, &block)
111
111
  if args.any? && !args.first.is_a?(Hash)
112
- # Specific attribute(s) being targeted
113
- attributes = args
112
+ # Specific machine(s) being targeted
113
+ names = args
114
114
  args = args.last.is_a?(Hash) ? [args.pop] : []
115
115
  else
116
116
  # Target all state machines
117
- attributes = nil
117
+ names = nil
118
118
  end
119
119
 
120
120
  # Add the transition callback to each class being observed
121
121
  observing.each do |klass|
122
122
  state_machines =
123
- if attributes
124
- attributes.map {|attribute| klass.state_machines.fetch(attribute)}
123
+ if names
124
+ names.map {|name| klass.state_machines.fetch(name)}
125
125
  else
126
126
  klass.state_machines.values
127
127
  end
@@ -84,17 +84,14 @@ module StateMachine
84
84
  # you can build two state machines (one public and one protected) like so:
85
85
  #
86
86
  # class Vehicle < Sequel::Model
87
- # # Allow both machines to share the same state
88
- # alias_method :public_state, :state
89
- # alias_method :public_state=, :state=
90
- #
91
87
  # set_restricted_columns :state_event # Prevent access to events in the first machine
92
88
  #
93
89
  # state_machine do
94
90
  # # Define private events here
95
91
  # end
96
92
  #
97
- # state_machine :public_state do
93
+ # # Allow both machines to share the same state
94
+ # state_machine :public_state, :attribute => :state do
98
95
  # # Define public events here
99
96
  # end
100
97
  # end
@@ -231,7 +228,7 @@ module StateMachine
231
228
 
232
229
  # Adds a validation error to the given object
233
230
  def invalidate(object, attribute, message, values = [])
234
- object.errors.add(attribute, generate_message(message, values))
231
+ object.errors.add(self.attribute(attribute), generate_message(message, values))
235
232
  end
236
233
 
237
234
  # Resets any errors previously added when invalidating the given object
@@ -242,8 +239,9 @@ module StateMachine
242
239
  protected
243
240
  # Skips defining reader/writer methods since this is done automatically
244
241
  def define_state_accessor
242
+ name = self.name
245
243
  owner_class.validates_each(attribute) do |record, attr, value|
246
- machine = record.class.state_machine(attr)
244
+ machine = record.class.state_machine(name)
247
245
  machine.invalidate(record, attr, :invalid) unless machine.states.match(record)
248
246
  end
249
247
  end
@@ -113,14 +113,14 @@ module StateMachine
113
113
  #
114
114
  # Callbacks are supported for hooking before and after every possible
115
115
  # transition in the machine. Each callback is invoked in the order in which
116
- # it was defined. See StateMachine::Machine#before_transition
117
- # and StateMachine::Machine#after_transition for documentation
118
- # on how to define new callbacks.
116
+ # it was defined. See StateMachine::Machine#before_transition and
117
+ # StateMachine::Machine#after_transition for documentation on how to define
118
+ # new callbacks.
119
119
  #
120
- # *Note* that callbacks only get executed within the context of an event.
121
- # As a result, if a class has an initial state when it's created, any
122
- # callbacks that would normally get executed when the object enters that
123
- # state will *not* get triggered.
120
+ # *Note* that callbacks only get executed within the context of an event. As
121
+ # a result, if a class has an initial state when it's created, any callbacks
122
+ # that would normally get executed when the object enters that state will
123
+ # *not* get triggered.
124
124
  #
125
125
  # For example,
126
126
  #
@@ -229,7 +229,7 @@ module StateMachine
229
229
  #
230
230
  # [Vehicle, Switch, Project].each do |klass|
231
231
  # klass.state_machines.each do |attribute, machine|
232
- # machine.before_transition klass.method(:before_transition)
232
+ # machine.before_transition StateMachineObserver.method(:before_transition)
233
233
  # end
234
234
  # end
235
235
  #
@@ -300,10 +300,10 @@ module StateMachine
300
300
  # in the new owner class (the original will remain unchanged).
301
301
  def find_or_create(owner_class, *args, &block)
302
302
  options = args.last.is_a?(Hash) ? args.pop : {}
303
- attribute = args.first || :state
303
+ name = args.first || :state
304
304
 
305
305
  # Find an existing machine
306
- if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[attribute]
306
+ if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[name]
307
307
  # Only create a new copy if changes are being made to the machine in
308
308
  # a subclass
309
309
  if machine.owner_class != owner_class && (options.any? || block_given?)
@@ -316,7 +316,7 @@ module StateMachine
316
316
  machine.instance_eval(&block) if block_given?
317
317
  else
318
318
  # No existing machine: create a new one
319
- machine = new(owner_class, attribute, options, &block)
319
+ machine = new(owner_class, name, options, &block)
320
320
  end
321
321
 
322
322
  machine
@@ -347,7 +347,7 @@ module StateMachine
347
347
  end
348
348
 
349
349
  # Draw each of the class's state machines
350
- klass.state_machines.each do |name, machine|
350
+ klass.state_machines.each_value do |machine|
351
351
  machine.draw(options)
352
352
  end
353
353
  end
@@ -365,8 +365,9 @@ module StateMachine
365
365
  # The class that the machine is defined in
366
366
  attr_accessor :owner_class
367
367
 
368
- # The attribute for which the machine is being defined
369
- attr_reader :attribute
368
+ # The name of the machine, used for scoping methods generated for the
369
+ # machine as a whole (not states or events)
370
+ attr_reader :name
370
371
 
371
372
  # The events that trigger transitions. These are sorted, by default, in
372
373
  # the order in which they were defined.
@@ -402,7 +403,7 @@ module StateMachine
402
403
  # Creates a new state machine for the given attribute
403
404
  def initialize(owner_class, *args, &block)
404
405
  options = args.last.is_a?(Hash) ? args.pop : {}
405
- assert_valid_keys(options, :initial, :action, :plural, :namespace, :integration, :messages, :use_transactions)
406
+ assert_valid_keys(options, :attribute, :initial, :action, :plural, :namespace, :integration, :messages, :use_transactions)
406
407
 
407
408
  # Find an integration that matches this machine's owner class
408
409
  if integration = options[:integration] ? StateMachine::Integrations.find(options[:integration]) : StateMachine::Integrations.match(owner_class)
@@ -414,7 +415,8 @@ module StateMachine
414
415
  options = {:use_transactions => true}.merge(options)
415
416
 
416
417
  # Set machine configuration
417
- @attribute = args.first || :state
418
+ @name = args.first || :state
419
+ @attribute = options[:attribute] || @name
418
420
  @events = EventCollection.new(self)
419
421
  @states = StateCollection.new(self)
420
422
  @callbacks = {:before => [], :after => []}
@@ -467,10 +469,9 @@ module StateMachine
467
469
  include instance_helper_module
468
470
  end
469
471
 
470
- # Record this machine as matched to the attribute in the current owner
471
- # class. This will override any machines mapped to the same attribute
472
- # in any superclasses.
473
- owner_class.state_machines[attribute] = self
472
+ # Record this machine as matched to the name in the current owner class.
473
+ # This will override any machines mapped to the same name in any superclasses.
474
+ owner_class.state_machines[name] = self
474
475
  end
475
476
 
476
477
  # Sets the initial state of the machine. This can be either the static name
@@ -484,22 +485,27 @@ module StateMachine
484
485
  states.each {|state| state.initial = (state.name == @initial_state)}
485
486
  end
486
487
 
488
+ # Gets the actual name of the attribute on the machine's owner class that
489
+ # stores data with the given name.
490
+ def attribute(name = :state)
491
+ name == :state ? @attribute : :"#{self.name}_#{name}"
492
+ end
493
+
487
494
  # Defines a new instance method with the given name on the machine's owner
488
495
  # class. If the method is already defined in the class, then this will not
489
496
  # override it.
490
497
  #
491
498
  # Example:
492
499
  #
493
- # attribute = machine.attribute
494
500
  # machine.define_instance_method(:state_name) do |machine, object|
495
501
  # machine.states.match(object)
496
502
  # end
497
503
  def define_instance_method(method, &block)
498
- attribute = self.attribute
504
+ name = self.name
499
505
 
500
506
  @instance_helper_module.class_eval do
501
507
  define_method(method) do |*args|
502
- block.call(self.class.state_machine(attribute), self, *args)
508
+ block.call(self.class.state_machine(name), self, *args)
503
509
  end
504
510
  end
505
511
  end
@@ -515,11 +521,11 @@ module StateMachine
515
521
  # machine.states.keys
516
522
  # end
517
523
  def define_class_method(method, &block)
518
- attribute = self.attribute
524
+ name = self.name
519
525
 
520
526
  @class_helper_module.class_eval do
521
527
  define_method(method) do |*args|
522
- block.call(self.state_machine(attribute), self, *args)
528
+ block.call(self.state_machine(name), self, *args)
523
529
  end
524
530
  end
525
531
  end
@@ -616,7 +622,7 @@ module StateMachine
616
622
  # end
617
623
  #
618
624
  # class Vehicle < ActiveRecord::Base
619
- # state_machine :state_id, :initial => :parked do
625
+ # state_machine :attribute => :state_id, :initial => :parked do
620
626
  # event :ignite do
621
627
  # transition :parked => :idling
622
628
  # end
@@ -824,7 +830,7 @@ module StateMachine
824
830
  end
825
831
  alias_method :other_states, :state
826
832
 
827
- # Gets the current value stored in the given object's state.
833
+ # Gets the current value stored in the given object's attribute.
828
834
  #
829
835
  # For example,
830
836
  #
@@ -834,13 +840,15 @@ module StateMachine
834
840
  # end
835
841
  # end
836
842
  #
837
- # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
838
- # Vehicle.state_machine.read(vehicle) # => "parked"
839
- def read(object)
840
- object.send(attribute)
843
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
844
+ # Vehicle.state_machine.read(vehicle, :state) # => "parked" # Equivalent to vehicle.state
845
+ # Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event
846
+ def read(object, attribute, ivar = false)
847
+ attribute = self.attribute(attribute)
848
+ ivar ? object.instance_variable_get("@#{attribute}") : object.send(attribute)
841
849
  end
842
850
 
843
- # Sets a new value in the given object's state.
851
+ # Sets a new value in the given object's attribute.
844
852
  #
845
853
  # For example,
846
854
  #
@@ -850,11 +858,13 @@ module StateMachine
850
858
  # end
851
859
  # end
852
860
  #
853
- # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
854
- # Vehicle.state_machine.write(vehicle, 'idling')
855
- # vehicle.state # => "idling"
856
- def write(object, value)
857
- object.send("#{attribute}=", value)
861
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
862
+ # Vehicle.state_machine.write(vehicle, :state, 'idling') # => Equivalent to vehicle.state = 'idling'
863
+ # Vehicle.state_machine.write(vehicle, :event, 'park') # => Equivalent to vehicle.state_event = 'park'
864
+ # vehicle.state # => "idling"
865
+ # vehicle.event # => "park"
866
+ def write(object, attribute, value)
867
+ object.send("#{self.attribute(attribute)}=", value)
858
868
  end
859
869
 
860
870
  # Defines one or more events for the machine and the transitions that can
@@ -1112,12 +1122,12 @@ module StateMachine
1112
1122
  #
1113
1123
  # class Vehicle
1114
1124
  # # Only specifies one parameter (the object being transitioned)
1115
- # before_transition :to => :parked do |vehicle|
1125
+ # before_transition all => :parked do |vehicle|
1116
1126
  # vehicle.set_alarm
1117
1127
  # end
1118
1128
  #
1119
1129
  # # Specifies 2 parameters (object being transitioned and actual transition)
1120
- # before_transition :to => :parked do |vehicle, transition|
1130
+ # before_transition all => :parked do |vehicle, transition|
1121
1131
  # vehicle.set_alarm(transition)
1122
1132
  # end
1123
1133
  # end
@@ -1143,7 +1153,7 @@ module StateMachine
1143
1153
  # before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
1144
1154
  #
1145
1155
  # # With conditional callback:
1146
- # before_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
1156
+ # before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
1147
1157
  #
1148
1158
  # # Using helpers:
1149
1159
  # before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
@@ -1205,7 +1215,7 @@ module StateMachine
1205
1215
  #
1206
1216
  # Configuration options:
1207
1217
  # * <tt>:name</tt> - The name of the file to write to (without the file extension).
1208
- # Default is "#{owner_class.name}_#{attribute}"
1218
+ # Default is "#{owner_class.name}_#{name}"
1209
1219
  # * <tt>:path</tt> - The path to write the graph file to. Default is the
1210
1220
  # current directory (".").
1211
1221
  # * <tt>:format</tt> - The image format to generate the graph in.
@@ -1217,7 +1227,7 @@ module StateMachine
1217
1227
  # * <tt>:output</tt> - Whether to generate the output of the graph
1218
1228
  def draw(options = {})
1219
1229
  options = {
1220
- :name => "#{owner_class.name}_#{attribute}",
1230
+ :name => "#{owner_class.name}_#{name}",
1221
1231
  :path => '.',
1222
1232
  :format => 'png',
1223
1233
  :font => 'Arial',
@@ -1272,7 +1282,7 @@ module StateMachine
1272
1282
  define_action_helpers if action
1273
1283
 
1274
1284
  # Gets the state name for the current value
1275
- define_instance_method("#{attribute}_name") do |machine, object|
1285
+ define_instance_method(attribute(:name)) do |machine, object|
1276
1286
  machine.states.match!(object).name
1277
1287
  end
1278
1288
  end
@@ -1289,7 +1299,7 @@ module StateMachine
1289
1299
  # Adds predicate method to the owner class for determining the name of the
1290
1300
  # current state
1291
1301
  def define_state_predicate
1292
- define_instance_method("#{attribute}?") do |machine, object, state|
1302
+ define_instance_method("#{name}?") do |machine, object, state|
1293
1303
  machine.states.matches?(object, state)
1294
1304
  end
1295
1305
  end
@@ -1298,31 +1308,31 @@ module StateMachine
1298
1308
  # events
1299
1309
  def define_event_helpers
1300
1310
  # Gets the events that are allowed to fire on the current object
1301
- define_instance_method("#{attribute}_events") do |machine, object|
1311
+ define_instance_method(attribute(:events)) do |machine, object|
1302
1312
  machine.events.valid_for(object).map {|event| event.name}
1303
1313
  end
1304
1314
 
1305
1315
  # Gets the next possible transitions that can be run on the current
1306
1316
  # object
1307
- define_instance_method("#{attribute}_transitions") do |machine, object, *args|
1317
+ define_instance_method(attribute(:transitions)) do |machine, object, *args|
1308
1318
  machine.events.transitions_for(object, *args)
1309
1319
  end
1310
1320
 
1311
1321
  # Add helpers for interacting with the action
1312
1322
  if action
1313
- attribute = self.attribute
1314
-
1315
1323
  # Tracks the event / transition to invoke when the action is called
1324
+ event_attribute = attribute(:event)
1325
+ event_transition_attribute = attribute(:event_transition)
1316
1326
  @instance_helper_module.class_eval do
1317
- attr_writer "#{attribute}_event"
1327
+ attr_writer event_attribute
1318
1328
 
1319
1329
  protected
1320
- attr_accessor "#{attribute}_event_transition"
1330
+ attr_accessor event_transition_attribute
1321
1331
  end
1322
1332
 
1323
1333
  # Interpret non-blank events as present
1324
- define_instance_method("#{attribute}_event") do |machine, object|
1325
- event = object.instance_variable_get("@#{attribute}_event")
1334
+ define_instance_method(attribute(:event)) do |machine, object|
1335
+ event = machine.read(object, :event, true)
1326
1336
  event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
1327
1337
  end
1328
1338
  end
@@ -1334,7 +1344,7 @@ module StateMachine
1334
1344
  action = self.action
1335
1345
  private_method = owner_class.private_method_defined?(action_hook)
1336
1346
 
1337
- if (owner_class.method_defined?(action_hook) || private_method) && !owner_class.state_machines.any? {|attribute, machine| machine.action == action && machine != self}
1347
+ if (owner_class.method_defined?(action_hook) || private_method) && !owner_class.state_machines.any? {|name, machine| machine.action == action && machine != self}
1338
1348
  # Action is defined and hasn't already been overridden by another machine
1339
1349
  @instance_helper_module.class_eval do
1340
1350
  # Override the default action to invoke the before / after hooks
@@ -1358,9 +1368,9 @@ module StateMachine
1358
1368
  # automatically determined by either calling +pluralize+ on the attribute
1359
1369
  # name or adding an "s" to the end of the name.
1360
1370
  def define_scopes(custom_plural = nil)
1361
- plural = custom_plural || (attribute.to_s.respond_to?(:pluralize) ? attribute.to_s.pluralize : "#{attribute}s")
1371
+ plural = custom_plural || (name.to_s.respond_to?(:pluralize) ? name.to_s.pluralize : "#{name}s")
1362
1372
 
1363
- [attribute, plural].uniq.each do |name|
1373
+ [name, plural].uniq.each do |name|
1364
1374
  [:with, :without].each do |kind|
1365
1375
  method = "#{kind}_#{name}"
1366
1376
 
@@ -5,9 +5,9 @@ module StateMachine
5
5
  # values are only set if the machine's attribute doesn't already exist
6
6
  # (which must mean the defaults are being skipped)
7
7
  def initialize_states(object)
8
- each do |attribute, machine|
9
- value = machine.read(object)
10
- machine.write(object, machine.initial_state(object).value) if value.nil? || value.respond_to?(:empty?) && value.empty?
8
+ each_value do |machine|
9
+ value = machine.read(object, :state)
10
+ machine.write(object, :state, machine.initial_state(object).value) if value.nil? || value.respond_to?(:empty?) && value.empty?
11
11
  end
12
12
  end
13
13
 
@@ -17,19 +17,19 @@ module StateMachine
17
17
  run_action = [true, false].include?(events.last) ? events.pop : true
18
18
 
19
19
  # Generate the transitions to run for each event
20
- transitions = events.collect do |name|
20
+ transitions = events.collect do |event_name|
21
21
  # Find the actual event being run
22
22
  event = nil
23
- detect do |attribute, machine|
24
- event = machine.events[name, :qualified_name]
23
+ detect do |name, machine|
24
+ event = machine.events[event_name, :qualified_name]
25
25
  end
26
26
 
27
- raise InvalidEvent, "#{name.inspect} is an unknown state machine event" unless event
27
+ raise InvalidEvent, "#{event_name.inspect} is an unknown state machine event" unless event
28
28
 
29
29
  # Get the transition that will be performed for the event
30
30
  unless transition = event.transition_for(object)
31
31
  machine = event.machine
32
- machine.invalidate(object, machine.attribute, :invalid_transition, [[:event, name]])
32
+ machine.invalidate(object, :state, :invalid_transition, [[:event, event_name]])
33
33
  end
34
34
 
35
35
  transition
@@ -107,7 +107,7 @@ module StateMachine
107
107
  # vehicle.state_event_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
108
108
  def fire_event_attributes(object, action, complete = true)
109
109
  # Get the transitions to fire for each applicable machine
110
- transitions = map {|attribute, machine| machine.action == action ? machine.events.attribute_transition_for(object, true) : nil}.compact
110
+ transitions = map {|name, machine| machine.action == action ? machine.events.attribute_transition_for(object, true) : nil}.compact
111
111
  return yield if transitions.empty?
112
112
 
113
113
  # The value generated by the yielded block (the actual action)
@@ -118,27 +118,25 @@ module StateMachine
118
118
  begin
119
119
  result = Transition.perform(transitions, :after => complete) do
120
120
  # Prevent events from being evaluated multiple times if actions are nested
121
- transitions.each {|transition| object.send("#{transition.attribute}_event=", nil)}
121
+ transitions.each {|transition| transition.machine.write(object, :event, nil)}
122
122
  action_value = yield
123
123
  end
124
124
  rescue Exception
125
- # Revert attribute modifications
125
+ # Revert object modifications
126
126
  transitions.each do |transition|
127
- object.send("#{transition.attribute}_event=", transition.event)
128
- object.send("#{transition.attribute}_event_transition=", nil) if complete
127
+ transition.machine.write(object, :event, transition.event)
128
+ transition.machine.write(object, :event_transition, nil) if complete
129
129
  end
130
130
 
131
131
  raise
132
132
  end
133
133
 
134
134
  transitions.each do |transition|
135
- attribute = transition.attribute
136
-
137
135
  # Revert event unless transition was successful
138
- object.send("#{attribute}_event=", transition.event) unless complete && result
136
+ transition.machine.write(object, :event, transition.event) unless complete && result
139
137
 
140
138
  # Track transition if partial transition completed successfully
141
- object.send("#{attribute}_event_transition=", !complete && result ? transition : nil)
139
+ transition.machine.write(object, :event_transition, !complete && result ? transition : nil)
142
140
  end
143
141
  end
144
142