state_machine 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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