state_machine 0.10.1 → 0.10.2
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 +9 -0
- data/Rakefile +1 -1
- data/lib/state_machine/event.rb +4 -4
- data/lib/state_machine/integrations/active_model.rb +4 -32
- data/lib/state_machine/integrations/active_model/versions.rb +5 -4
- data/lib/state_machine/integrations/active_record.rb +15 -30
- data/lib/state_machine/integrations/active_record/versions.rb +5 -2
- data/lib/state_machine/integrations/data_mapper.rb +10 -14
- data/lib/state_machine/integrations/mongo_mapper.rb +5 -16
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +20 -12
- data/lib/state_machine/integrations/mongoid.rb +71 -14
- data/lib/state_machine/integrations/sequel.rb +45 -52
- data/lib/state_machine/integrations/sequel/versions.rb +2 -10
- data/lib/state_machine/machine.rb +105 -78
- data/lib/state_machine/machine_collection.rb +18 -3
- data/lib/state_machine/state.rb +1 -1
- data/test/unit/integrations/active_model_test.rb +21 -9
- data/test/unit/integrations/active_record_test.rb +20 -13
- data/test/unit/integrations/data_mapper_test.rb +7 -2
- data/test/unit/integrations/mongo_mapper_test.rb +5 -1
- data/test/unit/integrations/mongoid_test.rb +25 -8
- data/test/unit/integrations/sequel_test.rb +10 -6
- data/test/unit/machine_collection_test.rb +40 -0
- data/test/unit/machine_test.rb +91 -154
- metadata +4 -4
@@ -266,31 +266,28 @@ module StateMachine
|
|
266
266
|
require 'sequel/extensions/inflector'
|
267
267
|
end
|
268
268
|
|
269
|
-
# Only allows state initialization on new records that aren't being
|
270
|
-
# created with a set of attributes that includes this machine's
|
271
|
-
# attribute.
|
272
|
-
def initialize_state?(object, options)
|
273
|
-
if object.new?
|
274
|
-
attributes = options[:attributes] || {}
|
275
|
-
ignore = object.send(:setter_methods, nil, nil).map {|setter| setter.chop.to_sym} & attributes.keys.map {|key| key.to_sym}
|
276
|
-
!ignore.map {|attribute| attribute.to_sym}.include?(attribute)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
269
|
# Defines an initialization hook into the owner class for setting the
|
281
270
|
# initial state of the machine *before* any attributes are set on the
|
282
271
|
# object
|
283
272
|
def define_state_initializer
|
284
273
|
# Hooks in to attribute initialization to set the states *prior* to
|
285
274
|
# the attributes being set
|
286
|
-
define_helper
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
275
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
276
|
+
# Initializes dynamic states
|
277
|
+
def initialize(*)
|
278
|
+
super do |*args|
|
279
|
+
self.class.state_machines.initialize_states(self, :static => false)
|
280
|
+
changed_columns.clear
|
281
|
+
yield(*args) if block_given?
|
282
|
+
end
|
292
283
|
end
|
293
|
-
|
284
|
+
|
285
|
+
# Initializes static states
|
286
|
+
def set(*)
|
287
|
+
self.class.state_machines.initialize_states(self, :dynamic => false) if values.empty?
|
288
|
+
super
|
289
|
+
end
|
290
|
+
end_eval
|
294
291
|
end
|
295
292
|
|
296
293
|
# Skips defining reader/writer methods since this is done automatically
|
@@ -309,16 +306,21 @@ module StateMachine
|
|
309
306
|
super
|
310
307
|
|
311
308
|
if action == :save
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
309
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
310
|
+
def valid?(*args)
|
311
|
+
yielded = false
|
312
|
+
result = self.class.state_machines.transitions(self, :save, :after => false).perform do
|
313
|
+
yielded = true
|
314
|
+
super
|
315
|
+
end
|
316
|
+
|
317
|
+
if yielded || result
|
318
|
+
result
|
319
|
+
else
|
320
|
+
#{handle_validation_failure}
|
321
|
+
end
|
318
322
|
end
|
319
|
-
|
320
|
-
!yielded && !result ? handle_validation_failure.call(object, args, yielded, result) : result
|
321
|
-
end
|
323
|
+
end_eval
|
322
324
|
end
|
323
325
|
end
|
324
326
|
|
@@ -328,22 +330,21 @@ module StateMachine
|
|
328
330
|
# save calls.
|
329
331
|
def define_action_hook
|
330
332
|
if action == :save
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
handle_save_failure.call(object)
|
333
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
334
|
+
def #{action_hook}(*)
|
335
|
+
yielded = false
|
336
|
+
result = self.class.state_machines.transitions(self, :save).perform do
|
337
|
+
yielded = true
|
338
|
+
super
|
339
|
+
end
|
340
|
+
|
341
|
+
if yielded || result
|
342
|
+
result
|
343
|
+
else
|
344
|
+
#{handle_save_failure}
|
345
|
+
end
|
345
346
|
end
|
346
|
-
|
347
|
+
end_eval
|
347
348
|
else
|
348
349
|
super
|
349
350
|
end
|
@@ -356,20 +357,12 @@ module StateMachine
|
|
356
357
|
|
357
358
|
# Handles whether validation errors should be raised
|
358
359
|
def handle_validation_failure
|
359
|
-
|
360
|
-
object.instance_eval do
|
361
|
-
raise_on_failure?(args.first || {}) ? raise_hook_failure(:validation) : result
|
362
|
-
end
|
363
|
-
end
|
360
|
+
'raise_on_failure?(args.first || {}) ? raise_hook_failure(:validation) : result'
|
364
361
|
end
|
365
362
|
|
366
363
|
# Handles how save failures are raised
|
367
364
|
def handle_save_failure
|
368
|
-
|
369
|
-
object.instance_eval do
|
370
|
-
raise_hook_failure(:save)
|
371
|
-
end
|
372
|
-
end
|
365
|
+
'raise_hook_failure(:save)'
|
373
366
|
end
|
374
367
|
|
375
368
|
# Creates a scope for finding records *with* a particular state or
|
@@ -7,19 +7,11 @@ module StateMachine
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def handle_validation_failure
|
10
|
-
|
11
|
-
object.instance_eval do
|
12
|
-
raise_on_save_failure ? save_failure(:validation) : result
|
13
|
-
end
|
14
|
-
end
|
10
|
+
'raise_on_save_failure ? save_failure(:validation) : result'
|
15
11
|
end
|
16
12
|
|
17
13
|
def handle_save_failure
|
18
|
-
|
19
|
-
object.instance_eval do
|
20
|
-
save_failure(:save)
|
21
|
-
end
|
22
|
-
end
|
14
|
+
'save_failure(:save)'
|
23
15
|
end
|
24
16
|
end
|
25
17
|
|
@@ -445,9 +445,8 @@ module StateMachine
|
|
445
445
|
@action = options[:action]
|
446
446
|
@use_transactions = options[:use_transactions]
|
447
447
|
@initialize_state = options[:initialize]
|
448
|
-
@helpers = {:instance => {}, :class => {}}
|
449
448
|
self.owner_class = owner_class
|
450
|
-
self.initial_state = options[:initial]
|
449
|
+
self.initial_state = options[:initial] unless owner_class.state_machines.any? {|name, machine| machine.attribute == attribute && machine != self}
|
451
450
|
|
452
451
|
# Define class integration
|
453
452
|
define_helpers
|
@@ -469,7 +468,6 @@ module StateMachine
|
|
469
468
|
@states = @states.dup
|
470
469
|
@states.machine = self
|
471
470
|
@callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup, :failure => @callbacks[:failure].dup}
|
472
|
-
@helpers = {:instance => @helpers[:instance].dup, :class => @helpers[:class].dup}
|
473
471
|
end
|
474
472
|
|
475
473
|
# Sets the class which is the owner of this state machine. Any methods
|
@@ -511,56 +509,6 @@ module StateMachine
|
|
511
509
|
states.each {|state| state.initial = (state.name == @initial_state)}
|
512
510
|
end
|
513
511
|
|
514
|
-
# Initializes the state on the given object. Initial values are only set if
|
515
|
-
# the machine's attribute hasn't been previously initialized.
|
516
|
-
def initialize_state(object, options = {})
|
517
|
-
write(object, :state, initial_state(object).value) if initialize_state?(object, options)
|
518
|
-
end
|
519
|
-
|
520
|
-
# Gets the actual name of the attribute on the machine's owner class that
|
521
|
-
# stores data with the given name.
|
522
|
-
def attribute(name = :state)
|
523
|
-
name == :state ? @attribute : :"#{self.name}_#{name}"
|
524
|
-
end
|
525
|
-
|
526
|
-
# Defines a new helper method in an instance or class scope with the given
|
527
|
-
# name. If the method is already defined in the scope, then this will not
|
528
|
-
# override it.
|
529
|
-
#
|
530
|
-
# Example:
|
531
|
-
#
|
532
|
-
# # Instance helper
|
533
|
-
# machine.define_helper(:instance, :state_name) do |machine, object, _super|
|
534
|
-
# machine.states.match(object)
|
535
|
-
# end
|
536
|
-
#
|
537
|
-
# # Class helper
|
538
|
-
# machine.define_helper(:class, :state_machine_name) do |machine, klass, _super|
|
539
|
-
# "State"
|
540
|
-
# end
|
541
|
-
def define_helper(scope, method, &block)
|
542
|
-
@helpers.fetch(scope)[method] = block
|
543
|
-
@helper_modules.fetch(scope).class_eval <<-end_eval, __FILE__, __LINE__
|
544
|
-
def #{method}(*args)
|
545
|
-
_super = lambda {|*new_args| new_args.empty? ? super(*args) : super(*new_args)}
|
546
|
-
#{scope == :class ? 'self' : 'self.class'}.state_machine(#{name.inspect}).call_helper(#{scope.inspect}, #{method.inspect}, self, _super, *args)
|
547
|
-
end
|
548
|
-
end_eval
|
549
|
-
end
|
550
|
-
|
551
|
-
# Invokes the helper method defined in the given scope.
|
552
|
-
#
|
553
|
-
# Example:
|
554
|
-
#
|
555
|
-
# # Instance helper
|
556
|
-
# machine.call_helper(:instance, :state_name, self, lambda {super})
|
557
|
-
#
|
558
|
-
# # Class helper
|
559
|
-
# machine.call_helper(:class, :state_machine_name, self, lambda {super})
|
560
|
-
def call_helper(scope, method, object, _super, *args)
|
561
|
-
@helpers.fetch(scope).fetch(method).call(self, object, _super, *args)
|
562
|
-
end
|
563
|
-
|
564
512
|
# Gets the initial state of the machine for the given object. If a dynamic
|
565
513
|
# initial state was configured for this machine, then the object will be
|
566
514
|
# passed into the lambda block to help determine the actual state.
|
@@ -596,7 +544,7 @@ module StateMachine
|
|
596
544
|
# vehicle.force_idle = false
|
597
545
|
# Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=false>
|
598
546
|
def initial_state(object)
|
599
|
-
states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state)
|
547
|
+
states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state) if instance_variable_defined?('@initial_state')
|
600
548
|
end
|
601
549
|
|
602
550
|
# Whether a dynamic initial state is being used in the machine
|
@@ -604,6 +552,77 @@ module StateMachine
|
|
604
552
|
@initial_state.is_a?(Proc)
|
605
553
|
end
|
606
554
|
|
555
|
+
# Initializes the state on the given object. Initial values are only set if
|
556
|
+
# the machine's attribute hasn't been previously initialized.
|
557
|
+
#
|
558
|
+
# Configuration options:
|
559
|
+
# * <tt>:force</tt> - Whether to initialize the state regardless of its
|
560
|
+
# current value
|
561
|
+
# * <tt>:to</tt> - A hash to set the initial value in instead of writing
|
562
|
+
# directly to the object
|
563
|
+
def initialize_state(object, options = {})
|
564
|
+
state = initial_state(object)
|
565
|
+
if state && (options[:force] || initialize_state?(object))
|
566
|
+
value = state.value
|
567
|
+
|
568
|
+
if hash = options[:to]
|
569
|
+
hash[attribute.to_s] = value
|
570
|
+
else
|
571
|
+
write(object, :state, value)
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# Gets the actual name of the attribute on the machine's owner class that
|
577
|
+
# stores data with the given name.
|
578
|
+
def attribute(name = :state)
|
579
|
+
name == :state ? @attribute : :"#{self.name}_#{name}"
|
580
|
+
end
|
581
|
+
|
582
|
+
# Defines a new helper method in an instance or class scope with the given
|
583
|
+
# name. If the method is already defined in the scope, then this will not
|
584
|
+
# override it.
|
585
|
+
#
|
586
|
+
# Example:
|
587
|
+
#
|
588
|
+
# # Instance helper
|
589
|
+
# machine.define_helper(:instance, :state_name) do |machine, object|
|
590
|
+
# machine.states.match(object).name
|
591
|
+
# end
|
592
|
+
#
|
593
|
+
# # Class helper
|
594
|
+
# machine.define_helper(:class, :state_machine_name) do |machine, klass|
|
595
|
+
# "State"
|
596
|
+
# end
|
597
|
+
#
|
598
|
+
# You can also define helpers using string evaluation like so:
|
599
|
+
#
|
600
|
+
# # Instance helper
|
601
|
+
# machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
602
|
+
# def state_name
|
603
|
+
# self.class.state_machine(:state).states.match(self).name
|
604
|
+
# end
|
605
|
+
# end_eval
|
606
|
+
#
|
607
|
+
# # Class helper
|
608
|
+
# machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
|
609
|
+
# def state_machine_name
|
610
|
+
# "State"
|
611
|
+
# end
|
612
|
+
# end_eval
|
613
|
+
def define_helper(scope, method, *args, &block)
|
614
|
+
if block_given?
|
615
|
+
name = self.name
|
616
|
+
@helper_modules.fetch(scope).class_eval do
|
617
|
+
define_method(method) do |*args|
|
618
|
+
block.call((scope == :instance ? self.class : self).state_machine(name), self, *args)
|
619
|
+
end
|
620
|
+
end
|
621
|
+
else
|
622
|
+
@helper_modules.fetch(scope).class_eval(method, *args)
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
607
626
|
# Customizes the definition of one or more states in the machine.
|
608
627
|
#
|
609
628
|
# Configuration options:
|
@@ -1535,9 +1554,9 @@ module StateMachine
|
|
1535
1554
|
|
1536
1555
|
# Determines if the machine's attribute needs to be initialized. This
|
1537
1556
|
# will only be true if the machine's attribute is blank.
|
1538
|
-
def initialize_state?(object
|
1557
|
+
def initialize_state?(object)
|
1539
1558
|
value = read(object, :state)
|
1540
|
-
value.nil? || value.respond_to?(:empty?) && value.empty?
|
1559
|
+
(value.nil? || value.respond_to?(:empty?) && value.empty?) && !states[value, :value]
|
1541
1560
|
end
|
1542
1561
|
|
1543
1562
|
# Adds helper methods for interacting with the state machine, including
|
@@ -1555,9 +1574,11 @@ module StateMachine
|
|
1555
1574
|
# are set prior to the original initialize method and dynamic values are
|
1556
1575
|
# set *after* the initialize method in case it is dependent on it.
|
1557
1576
|
def define_state_initializer
|
1558
|
-
define_helper
|
1559
|
-
|
1560
|
-
|
1577
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
1578
|
+
def initialize(*)
|
1579
|
+
self.class.state_machines.initialize_states(self) { super }
|
1580
|
+
end
|
1581
|
+
end_eval
|
1561
1582
|
end
|
1562
1583
|
|
1563
1584
|
# Adds reader/writer methods for accessing the state attribute
|
@@ -1571,22 +1592,24 @@ module StateMachine
|
|
1571
1592
|
# current state
|
1572
1593
|
def define_state_predicate
|
1573
1594
|
call_super = owner_class_ancestor_has_method?("#{name}?")
|
1574
|
-
define_helper
|
1575
|
-
|
1576
|
-
|
1595
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
1596
|
+
def #{name}?(*args)
|
1597
|
+
args.empty? && #{call_super} ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args)
|
1598
|
+
end
|
1599
|
+
end_eval
|
1577
1600
|
end
|
1578
1601
|
|
1579
1602
|
# Adds helper methods for getting information about this state machine's
|
1580
1603
|
# events
|
1581
1604
|
def define_event_helpers
|
1582
1605
|
# Gets the events that are allowed to fire on the current object
|
1583
|
-
define_helper(:instance, attribute(:events)) do |machine, object,
|
1606
|
+
define_helper(:instance, attribute(:events)) do |machine, object, *args|
|
1584
1607
|
machine.events.valid_for(object, *args).map {|event| event.name}
|
1585
1608
|
end
|
1586
1609
|
|
1587
1610
|
# Gets the next possible transitions that can be run on the current
|
1588
1611
|
# object
|
1589
|
-
define_helper(:instance, attribute(:transitions)) do |machine, object,
|
1612
|
+
define_helper(:instance, attribute(:transitions)) do |machine, object, *args|
|
1590
1613
|
machine.events.transitions_for(object, *args)
|
1591
1614
|
end
|
1592
1615
|
|
@@ -1594,7 +1617,7 @@ module StateMachine
|
|
1594
1617
|
# action is called
|
1595
1618
|
if action
|
1596
1619
|
event_attribute = attribute(:event)
|
1597
|
-
define_helper(:instance, event_attribute) do |machine, object
|
1620
|
+
define_helper(:instance, event_attribute) do |machine, object|
|
1598
1621
|
# Interpret non-blank events as present
|
1599
1622
|
event = machine.read(object, :event, true)
|
1600
1623
|
event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
|
@@ -1602,12 +1625,14 @@ module StateMachine
|
|
1602
1625
|
|
1603
1626
|
# A roundabout way of writing the attribute is used here so that
|
1604
1627
|
# integrations can hook into this modification
|
1605
|
-
define_helper(:instance, "#{event_attribute}=") do |machine, object,
|
1628
|
+
define_helper(:instance, "#{event_attribute}=") do |machine, object, value|
|
1606
1629
|
machine.write(object, :event, value, true)
|
1607
1630
|
end
|
1608
1631
|
|
1609
1632
|
event_transition_attribute = attribute(:event_transition)
|
1610
|
-
|
1633
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
1634
|
+
protected; attr_accessor #{event_transition_attribute.inspect}
|
1635
|
+
end_eval
|
1611
1636
|
end
|
1612
1637
|
end
|
1613
1638
|
|
@@ -1615,7 +1640,7 @@ module StateMachine
|
|
1615
1640
|
# available transition paths
|
1616
1641
|
def define_path_helpers
|
1617
1642
|
# Gets the paths of transitions available to the current object
|
1618
|
-
define_helper(:instance, attribute(:paths)) do |machine, object,
|
1643
|
+
define_helper(:instance, attribute(:paths)) do |machine, object, *args|
|
1619
1644
|
machine.paths_for(object, *args)
|
1620
1645
|
end
|
1621
1646
|
end
|
@@ -1645,11 +1670,13 @@ module StateMachine
|
|
1645
1670
|
private_action_hook = owner_class.private_method_defined?(action_hook)
|
1646
1671
|
|
1647
1672
|
# Only define helper if it hasn't
|
1648
|
-
define_helper
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1673
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
1674
|
+
def #{action_hook}(*)
|
1675
|
+
self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
|
1676
|
+
end
|
1677
|
+
|
1678
|
+
private #{action_hook.inspect} if #{private_action_hook}
|
1679
|
+
end_eval
|
1653
1680
|
end
|
1654
1681
|
|
1655
1682
|
# The method to hook into for triggering transitions when invoked. By
|
@@ -1674,22 +1701,22 @@ module StateMachine
|
|
1674
1701
|
# events on the owner class
|
1675
1702
|
def define_name_helpers
|
1676
1703
|
# Gets the humanized version of a state
|
1677
|
-
define_helper(:class, "human_#{attribute(:name)}") do |machine, klass,
|
1704
|
+
define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, state|
|
1678
1705
|
machine.states.fetch(state).human_name(klass)
|
1679
1706
|
end
|
1680
1707
|
|
1681
1708
|
# Gets the humanized version of an event
|
1682
|
-
define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass,
|
1709
|
+
define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, event|
|
1683
1710
|
machine.events.fetch(event).human_name(klass)
|
1684
1711
|
end
|
1685
1712
|
|
1686
1713
|
# Gets the state name for the current value
|
1687
|
-
define_helper(:instance, attribute(:name)) do |machine, object
|
1714
|
+
define_helper(:instance, attribute(:name)) do |machine, object|
|
1688
1715
|
machine.states.match!(object).name
|
1689
1716
|
end
|
1690
1717
|
|
1691
1718
|
# Gets the human state name for the current value
|
1692
|
-
define_helper(:instance, "human_#{attribute(:name)}") do |machine, object
|
1719
|
+
define_helper(:instance, "human_#{attribute(:name)}") do |machine, object|
|
1693
1720
|
machine.states.match!(object).human_name(object.class)
|
1694
1721
|
end
|
1695
1722
|
end
|
@@ -1709,7 +1736,7 @@ module StateMachine
|
|
1709
1736
|
if scope = send("create_#{kind}_scope", method)
|
1710
1737
|
# Converts state names to their corresponding values so that they
|
1711
1738
|
# can be looked up properly
|
1712
|
-
define_helper(:class, method) do |machine, klass,
|
1739
|
+
define_helper(:class, method) do |machine, klass, *states|
|
1713
1740
|
values = states.flatten.map {|state| machine.states.fetch(state).value}
|
1714
1741
|
scope.call(klass, values)
|
1715
1742
|
end
|