state_machine 0.10.1 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|