state_machine 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -11
- data/.travis.yml +49 -7
- data/Appraisals +255 -87
- data/CHANGELOG.md +30 -0
- data/README.md +142 -21
- data/Rakefile +1 -11
- data/examples/Gemfile +5 -0
- data/examples/Gemfile.lock +14 -0
- data/examples/auto_shop.rb +2 -0
- data/examples/car.rb +2 -0
- data/examples/doc/AutoShop.html +2856 -0
- data/examples/doc/AutoShop_state.png +0 -0
- data/examples/doc/Car.html +919 -0
- data/examples/doc/Car_state.png +0 -0
- data/examples/doc/TrafficLight.html +2230 -0
- data/examples/doc/TrafficLight_state.png +0 -0
- data/examples/doc/Vehicle.html +7921 -0
- data/examples/doc/Vehicle_state.png +0 -0
- data/examples/doc/_index.html +136 -0
- data/examples/doc/class_list.html +47 -0
- data/examples/doc/css/common.css +1 -0
- data/examples/doc/css/full_list.css +55 -0
- data/examples/doc/css/style.css +322 -0
- data/examples/doc/file_list.html +46 -0
- data/examples/doc/frames.html +13 -0
- data/examples/doc/index.html +136 -0
- data/examples/doc/js/app.js +205 -0
- data/examples/doc/js/full_list.js +173 -0
- data/examples/doc/js/jquery.js +16 -0
- data/examples/doc/method_list.html +734 -0
- data/examples/doc/top-level-namespace.html +105 -0
- data/examples/rails-rest/migration.rb +1 -5
- data/examples/rails-rest/view__form.html.erb +34 -0
- data/examples/rails-rest/view_edit.html.erb +2 -21
- data/examples/rails-rest/view_index.html.erb +6 -4
- data/examples/rails-rest/view_new.html.erb +2 -11
- data/examples/rails-rest/view_show.html.erb +5 -3
- data/examples/traffic_light.rb +2 -0
- data/examples/vehicle.rb +2 -0
- data/gemfiles/active_model-3.0.0.gemfile.lock +9 -6
- data/gemfiles/active_model-3.0.5.gemfile.lock +10 -7
- data/gemfiles/active_model-3.1.1.gemfile.lock +12 -10
- data/gemfiles/{active_model-3.2.0.gemfile → active_model-3.2.1.gemfile} +1 -1
- data/gemfiles/{graphviz-0.9.0.gemfile → active_model-3.2.12.gemfile} +1 -1
- data/gemfiles/active_model-3.2.12.gemfile.lock +36 -0
- data/gemfiles/{active_record-3.2.0.gemfile → active_model-3.2.13.rc1.gemfile} +1 -2
- data/gemfiles/active_model-3.2.13.rc1.gemfile.lock +36 -0
- data/gemfiles/active_model-4.0.0.gemfile +9 -0
- data/gemfiles/active_model-4.0.0.gemfile.lock +78 -0
- data/gemfiles/active_record-2.0.0.gemfile +2 -1
- data/gemfiles/active_record-2.0.0.gemfile.lock +15 -6
- data/gemfiles/active_record-2.0.5.gemfile +2 -1
- data/gemfiles/active_record-2.0.5.gemfile.lock +15 -6
- data/gemfiles/active_record-2.1.0.gemfile +2 -1
- data/gemfiles/active_record-2.1.0.gemfile.lock +15 -6
- data/gemfiles/active_record-2.1.2.gemfile +2 -1
- data/gemfiles/active_record-2.1.2.gemfile.lock +15 -6
- data/gemfiles/active_record-2.2.3.gemfile +2 -1
- data/gemfiles/active_record-2.2.3.gemfile.lock +15 -6
- data/gemfiles/active_record-2.3.12.gemfile +2 -1
- data/gemfiles/active_record-2.3.12.gemfile.lock +15 -6
- data/gemfiles/active_record-2.3.5.gemfile +9 -0
- data/gemfiles/active_record-2.3.5.gemfile.lock +39 -0
- data/gemfiles/active_record-3.0.0.gemfile +2 -1
- data/gemfiles/active_record-3.0.0.gemfile.lock +18 -11
- data/gemfiles/active_record-3.0.5.gemfile +2 -1
- data/gemfiles/active_record-3.0.5.gemfile.lock +19 -12
- data/gemfiles/active_record-3.1.1.gemfile +2 -1
- data/gemfiles/active_record-3.1.1.gemfile.lock +22 -16
- data/gemfiles/active_record-3.2.12.gemfile +9 -0
- data/gemfiles/active_record-3.2.12.gemfile.lock +51 -0
- data/gemfiles/active_record-3.2.13.rc1.gemfile +9 -0
- data/gemfiles/active_record-3.2.13.rc1.gemfile.lock +51 -0
- data/gemfiles/active_record-4.0.0.gemfile +11 -0
- data/gemfiles/active_record-4.0.0.gemfile.lock +83 -0
- data/gemfiles/data_mapper-0.10.2.gemfile +1 -0
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +13 -9
- data/gemfiles/data_mapper-0.9.11.gemfile +1 -0
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +31 -7
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +25 -14
- data/gemfiles/data_mapper-0.9.7.gemfile +1 -0
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +27 -15
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +20 -17
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +20 -17
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +20 -17
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +19 -16
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +19 -16
- data/gemfiles/default.gemfile.lock +8 -5
- data/gemfiles/graphviz-0.9.17.gemfile +7 -0
- data/gemfiles/graphviz-0.9.17.gemfile.lock +29 -0
- data/gemfiles/graphviz-0.9.21.gemfile.lock +7 -4
- data/gemfiles/graphviz-1.0.0.gemfile.lock +7 -4
- data/gemfiles/graphviz-1.0.3.gemfile +7 -0
- data/gemfiles/graphviz-1.0.3.gemfile.lock +29 -0
- data/gemfiles/graphviz-1.0.8.gemfile +7 -0
- data/gemfiles/graphviz-1.0.8.gemfile.lock +29 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile +1 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +14 -11
- data/gemfiles/mongo_mapper-0.11.1.gemfile +7 -0
- data/gemfiles/mongo_mapper-0.11.1.gemfile.lock +44 -0
- data/gemfiles/mongo_mapper-0.11.2.gemfile +9 -0
- data/gemfiles/mongo_mapper-0.11.2.gemfile.lock +48 -0
- data/gemfiles/mongo_mapper-0.12.0.gemfile +9 -0
- data/gemfiles/mongo_mapper-0.12.0.gemfile.lock +48 -0
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +7 -4
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +7 -4
- data/gemfiles/mongoid-2.0.0.gemfile +2 -0
- data/gemfiles/mongoid-2.0.0.gemfile.lock +22 -18
- data/gemfiles/mongoid-2.1.4.gemfile +2 -0
- data/gemfiles/mongoid-2.1.4.gemfile.lock +21 -17
- data/gemfiles/mongoid-2.2.4.gemfile +2 -0
- data/gemfiles/mongoid-2.2.4.gemfile.lock +21 -17
- data/gemfiles/mongoid-2.3.3.gemfile +2 -0
- data/gemfiles/mongoid-2.3.3.gemfile.lock +21 -17
- data/gemfiles/mongoid-2.4.0.gemfile +9 -0
- data/gemfiles/mongoid-2.4.0.gemfile.lock +47 -0
- data/gemfiles/mongoid-2.4.10.gemfile +9 -0
- data/gemfiles/mongoid-2.4.10.gemfile.lock +47 -0
- data/gemfiles/mongoid-2.5.2.gemfile +9 -0
- data/gemfiles/mongoid-2.5.2.gemfile.lock +47 -0
- data/gemfiles/mongoid-2.6.0.gemfile +9 -0
- data/gemfiles/mongoid-2.6.0.gemfile.lock +47 -0
- data/gemfiles/mongoid-3.0.0.gemfile +8 -0
- data/gemfiles/mongoid-3.0.0.gemfile.lock +45 -0
- data/gemfiles/mongoid-3.0.22.gemfile +8 -0
- data/gemfiles/mongoid-3.0.22.gemfile.lock +45 -0
- data/gemfiles/mongoid-3.1.0.gemfile +8 -0
- data/gemfiles/mongoid-3.1.0.gemfile.lock +45 -0
- data/gemfiles/sequel-2.11.0.gemfile +2 -1
- data/gemfiles/sequel-2.11.0.gemfile.lock +11 -6
- data/gemfiles/sequel-2.12.0.gemfile +2 -1
- data/gemfiles/sequel-2.12.0.gemfile.lock +11 -6
- data/gemfiles/sequel-2.8.0.gemfile +2 -1
- data/gemfiles/sequel-2.8.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.0.0.gemfile +2 -1
- data/gemfiles/sequel-3.0.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.10.0.gemfile +9 -0
- data/gemfiles/sequel-3.10.0.gemfile.lock +33 -0
- data/gemfiles/sequel-3.13.0.gemfile +2 -1
- data/gemfiles/sequel-3.13.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.14.0.gemfile +2 -1
- data/gemfiles/sequel-3.14.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.23.0.gemfile +2 -1
- data/gemfiles/sequel-3.23.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.24.0.gemfile +2 -1
- data/gemfiles/sequel-3.24.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.29.0.gemfile +2 -1
- data/gemfiles/sequel-3.29.0.gemfile.lock +11 -6
- data/gemfiles/sequel-3.34.0.gemfile +9 -0
- data/gemfiles/sequel-3.34.0.gemfile.lock +33 -0
- data/gemfiles/sequel-3.35.0.gemfile +9 -0
- data/gemfiles/sequel-3.35.0.gemfile.lock +33 -0
- data/gemfiles/sequel-3.4.0.gemfile +9 -0
- data/gemfiles/sequel-3.4.0.gemfile.lock +33 -0
- data/gemfiles/sequel-3.44.0.gemfile +9 -0
- data/gemfiles/sequel-3.44.0.gemfile.lock +33 -0
- data/lib/state_machine.rb +6 -0
- data/lib/state_machine/branch.rb +9 -8
- data/lib/state_machine/callback.rb +2 -2
- data/lib/state_machine/core.rb +10 -0
- data/lib/state_machine/core_ext.rb +1 -0
- data/lib/state_machine/eval_helpers.rb +5 -3
- data/lib/state_machine/event.rb +17 -6
- data/lib/state_machine/graph.rb +92 -0
- data/lib/state_machine/integrations.rb +13 -1
- data/lib/state_machine/integrations/active_model.rb +14 -20
- data/lib/state_machine/integrations/active_model/observer.rb +3 -3
- data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
- data/lib/state_machine/integrations/active_record.rb +52 -25
- data/lib/state_machine/integrations/active_record/locale.rb +1 -1
- data/lib/state_machine/integrations/active_record/versions.rb +1 -17
- data/lib/state_machine/integrations/base.rb +15 -6
- data/lib/state_machine/integrations/data_mapper.rb +98 -35
- data/lib/state_machine/integrations/data_mapper/versions.rb +46 -8
- data/lib/state_machine/integrations/mongo_mapper.rb +39 -12
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +1 -1
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -20
- data/lib/state_machine/integrations/mongoid.rb +52 -14
- data/lib/state_machine/integrations/mongoid/locale.rb +1 -1
- data/lib/state_machine/integrations/mongoid/versions.rb +52 -26
- data/lib/state_machine/integrations/sequel.rb +82 -33
- data/lib/state_machine/integrations/sequel/versions.rb +19 -44
- data/lib/state_machine/machine.rb +99 -59
- data/lib/state_machine/machine_collection.rb +1 -2
- data/lib/state_machine/macro_methods.rb +29 -0
- data/lib/state_machine/node_collection.rb +1 -1
- data/lib/state_machine/state.rb +18 -10
- data/lib/state_machine/state_context.rb +2 -2
- data/lib/state_machine/transition.rb +8 -1
- data/lib/state_machine/transition_collection.rb +2 -1
- data/lib/state_machine/version.rb +1 -1
- data/lib/state_machine/yard.rb +8 -0
- data/lib/state_machine/yard/handlers.rb +12 -0
- data/lib/state_machine/yard/handlers/base.rb +32 -0
- data/lib/state_machine/yard/handlers/event.rb +25 -0
- data/lib/state_machine/yard/handlers/machine.rb +344 -0
- data/lib/state_machine/yard/handlers/state.rb +25 -0
- data/lib/state_machine/yard/handlers/transition.rb +47 -0
- data/lib/state_machine/yard/templates.rb +3 -0
- data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
- data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
- data/lib/tasks/state_machine.rb +2 -1
- data/lib/yard-state_machine.rb +2 -0
- data/state_machine.gemspec +4 -3
- data/test/files/switch.rb +4 -0
- data/test/test_helper.rb +5 -0
- data/test/unit/branch_test.rb +117 -36
- data/test/unit/callback_test.rb +5 -2
- data/test/unit/eval_helpers_test.rb +49 -1
- data/test/unit/event_collection_test.rb +3 -1
- data/test/unit/event_test.rb +182 -12
- data/test/unit/graph_test.rb +98 -0
- data/test/unit/integrations/active_model_test.rb +82 -12
- data/test/unit/integrations/active_record_test.rb +393 -37
- data/test/unit/integrations/base_test.rb +7 -2
- data/test/unit/integrations/data_mapper_test.rb +326 -72
- data/test/unit/integrations/mongo_mapper_test.rb +338 -44
- data/test/unit/integrations/mongoid_test.rb +606 -98
- data/test/unit/integrations/sequel_test.rb +429 -102
- data/test/unit/integrations_test.rb +28 -6
- data/test/unit/machine_collection_test.rb +6 -2
- data/test/unit/machine_test.rb +134 -82
- data/test/unit/node_collection_test.rb +2 -2
- data/test/unit/path_test.rb +1 -1
- data/test/unit/state_test.rb +65 -21
- data/test/unit/transition_collection_test.rb +43 -23
- data/test/unit/transition_test.rb +8 -2
- metadata +303 -221
- data/gemfiles/active_model-3.2.0.gemfile.lock +0 -32
- data/gemfiles/active_record-3.2.0.gemfile.lock +0 -43
- data/gemfiles/graphviz-0.9.0.gemfile.lock +0 -26
@@ -489,7 +489,7 @@ module StateMachine
|
|
489
489
|
@default_messages = {
|
490
490
|
:invalid => 'is invalid',
|
491
491
|
:invalid_event => 'cannot transition when %s',
|
492
|
-
:invalid_transition => 'cannot transition via "%s"'
|
492
|
+
:invalid_transition => 'cannot transition via "%1$s"'
|
493
493
|
}
|
494
494
|
|
495
495
|
# Whether to ignore any conflicts that are detected for helper methods that
|
@@ -498,7 +498,7 @@ module StateMachine
|
|
498
498
|
@ignore_method_conflicts = false
|
499
499
|
|
500
500
|
# The class that the machine is defined in
|
501
|
-
|
501
|
+
attr_reader :owner_class
|
502
502
|
|
503
503
|
# The name of the machine, used for scoping methods generated for the
|
504
504
|
# machine as a whole (not states or events)
|
@@ -542,7 +542,7 @@ module StateMachine
|
|
542
542
|
|
543
543
|
# Find an integration that matches this machine's owner class
|
544
544
|
if options.include?(:integration)
|
545
|
-
@integration = StateMachine::Integrations.find_by_name(options[:integration])
|
545
|
+
@integration = options[:integration] && StateMachine::Integrations.find_by_name(options[:integration])
|
546
546
|
else
|
547
547
|
@integration = StateMachine::Integrations.match(owner_class)
|
548
548
|
end
|
@@ -566,6 +566,7 @@ module StateMachine
|
|
566
566
|
@action = options[:action]
|
567
567
|
@use_transactions = options[:use_transactions]
|
568
568
|
@initialize_state = options[:initialize]
|
569
|
+
@action_hook_defined = false
|
569
570
|
self.owner_class = owner_class
|
570
571
|
self.initial_state = options[:initial] unless sibling_machines.any?
|
571
572
|
|
@@ -631,8 +632,19 @@ module StateMachine
|
|
631
632
|
|
632
633
|
# Update all states to reflect the new initial state
|
633
634
|
states.each {|state| state.initial = (state.name == @initial_state)}
|
635
|
+
|
636
|
+
# Output a warning if there are conflicting initial states for the machine's
|
637
|
+
# attribute
|
638
|
+
initial_state = states.detect {|state| state.initial}
|
639
|
+
if !owner_class_attribute_default.nil? && (dynamic_initial_state? || !owner_class_attribute_default_matches?(initial_state))
|
640
|
+
warn(
|
641
|
+
"Both #{owner_class.name} and its #{name.inspect} machine have defined "\
|
642
|
+
"a different default for \"#{attribute}\". Use only one or the other for "\
|
643
|
+
"defining defaults to avoid unexpected behaviors."
|
644
|
+
)
|
645
|
+
end
|
634
646
|
end
|
635
|
-
|
647
|
+
|
636
648
|
# Gets the initial state of the machine for the given object. If a dynamic
|
637
649
|
# initial state was configured for this machine, then the object will be
|
638
650
|
# passed into the lambda block to help determine the actual state.
|
@@ -673,7 +685,7 @@ module StateMachine
|
|
673
685
|
|
674
686
|
# Whether a dynamic initial state is being used in the machine
|
675
687
|
def dynamic_initial_state?
|
676
|
-
@initial_state.is_a?(Proc)
|
688
|
+
instance_variable_defined?('@initial_state') && @initial_state.is_a?(Proc)
|
677
689
|
end
|
678
690
|
|
679
691
|
# Initializes the state on the given object. Initial values are only set if
|
@@ -749,8 +761,8 @@ module StateMachine
|
|
749
761
|
else
|
750
762
|
name = self.name
|
751
763
|
helper_module.class_eval do
|
752
|
-
define_method(method) do |*
|
753
|
-
block.call((scope == :instance ? self.class : self).state_machine(name), self, *
|
764
|
+
define_method(method) do |*block_args|
|
765
|
+
block.call((scope == :instance ? self.class : self).state_machine(name), self, *block_args)
|
754
766
|
end
|
755
767
|
end
|
756
768
|
end
|
@@ -1074,7 +1086,11 @@ module StateMachine
|
|
1074
1086
|
# Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event
|
1075
1087
|
def read(object, attribute, ivar = false)
|
1076
1088
|
attribute = self.attribute(attribute)
|
1077
|
-
ivar
|
1089
|
+
if ivar
|
1090
|
+
object.instance_variable_defined?("@#{attribute}") ? object.instance_variable_get("@#{attribute}") : nil
|
1091
|
+
else
|
1092
|
+
object.send(attribute)
|
1093
|
+
end
|
1078
1094
|
end
|
1079
1095
|
|
1080
1096
|
# Sets a new value in the given object's attribute.
|
@@ -1445,6 +1461,7 @@ module StateMachine
|
|
1445
1461
|
raise ArgumentError, 'Must specify :on event' unless options[:on]
|
1446
1462
|
|
1447
1463
|
branches = []
|
1464
|
+
options = options.dup
|
1448
1465
|
event(*Array(options.delete(:on))) { branches << transition(options) }
|
1449
1466
|
|
1450
1467
|
branches.length == 1 ? branches.first : branches
|
@@ -1569,7 +1586,7 @@ module StateMachine
|
|
1569
1586
|
# before_transition :parked => :idling, :if => :moving?, :do => ...
|
1570
1587
|
# before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
|
1571
1588
|
#
|
1572
|
-
#
|
1589
|
+
# == Accessing the transition
|
1573
1590
|
#
|
1574
1591
|
# In addition to passing the object being transitioned, the actual
|
1575
1592
|
# transition describing the context (e.g. event, from, to) can be accessed
|
@@ -1597,6 +1614,40 @@ module StateMachine
|
|
1597
1614
|
# See StateMachine::Transition for more information about the
|
1598
1615
|
# attributes available on the transition.
|
1599
1616
|
#
|
1617
|
+
# == Usage with delegates
|
1618
|
+
#
|
1619
|
+
# As noted above, state_machine uses the callback method's argument list
|
1620
|
+
# arity to determine whether to include the transition in the method call.
|
1621
|
+
# If you're using delegates, such as those defined in ActiveSupport or
|
1622
|
+
# Forwardable, the actual arity of the delegated method gets masked. This
|
1623
|
+
# means that callbacks which reference delegates will always get passed the
|
1624
|
+
# transition as an argument. For example:
|
1625
|
+
#
|
1626
|
+
# class Vehicle
|
1627
|
+
# extend Forwardable
|
1628
|
+
# delegate :refresh => :dashboard
|
1629
|
+
#
|
1630
|
+
# state_machine do
|
1631
|
+
# before_transition :refresh
|
1632
|
+
# ...
|
1633
|
+
# end
|
1634
|
+
#
|
1635
|
+
# def dashboard
|
1636
|
+
# @dashboard ||= Dashboard.new
|
1637
|
+
# end
|
1638
|
+
# end
|
1639
|
+
#
|
1640
|
+
# class Dashboard
|
1641
|
+
# def refresh(transition)
|
1642
|
+
# # ...
|
1643
|
+
# end
|
1644
|
+
# end
|
1645
|
+
#
|
1646
|
+
# In the above example, <tt>Dashboard#refresh</tt> *must* defined a
|
1647
|
+
# +transition+ argument. Otherwise, an +ArgumentError+ exception will get
|
1648
|
+
# raised. The only way around this is to avoid the use of delegates and
|
1649
|
+
# manually define the delegate method so that the correct arity is used.
|
1650
|
+
#
|
1600
1651
|
# == Examples
|
1601
1652
|
#
|
1602
1653
|
# Below is an example of a class with one state machine and various types
|
@@ -1830,7 +1881,15 @@ module StateMachine
|
|
1830
1881
|
# Generates the message to use when invalidating the given object after
|
1831
1882
|
# failing to transition on a specific event
|
1832
1883
|
def generate_message(name, values = [])
|
1833
|
-
(@messages[name] || self.class.default_messages[name])
|
1884
|
+
message = (@messages[name] || self.class.default_messages[name])
|
1885
|
+
|
1886
|
+
# Check whether there are actually any values to interpolate to avoid
|
1887
|
+
# any warnings
|
1888
|
+
if message.scan(/%./).any? {|match| match != '%%'}
|
1889
|
+
message % values.map {|value| value.last}
|
1890
|
+
else
|
1891
|
+
message
|
1892
|
+
end
|
1834
1893
|
end
|
1835
1894
|
|
1836
1895
|
# Runs a transaction, rolling back any changes if the yielded block fails.
|
@@ -1863,53 +1922,22 @@ module StateMachine
|
|
1863
1922
|
# Default is "Arial".
|
1864
1923
|
# * <tt>:orientation</tt> - The direction of the graph ("portrait" or
|
1865
1924
|
# "landscape"). Default is "portrait".
|
1866
|
-
# * <tt>:
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
:font => 'Arial',
|
1873
|
-
:orientation => 'portrait'
|
1874
|
-
}.merge(options)
|
1875
|
-
assert_valid_keys(options, :name, :path, :format, :font, :orientation)
|
1925
|
+
# * <tt>:human_names</tt> - Whether to use human state / event names for
|
1926
|
+
# node labels on the graph instead of the internal name. Default is false.
|
1927
|
+
def draw(graph_options = {})
|
1928
|
+
name = graph_options.delete(:name) || "#{owner_class.name}_#{self.name}"
|
1929
|
+
draw_options = {:human_name => false}
|
1930
|
+
draw_options[:human_name] = graph_options.delete(:human_names) if graph_options.include?(:human_names)
|
1876
1931
|
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
states.by_priority.each do |state|
|
1887
|
-
node = state.draw(graph)
|
1888
|
-
node.fontname = options[:font]
|
1889
|
-
end
|
1890
|
-
|
1891
|
-
# Add edges
|
1892
|
-
events.each do |event|
|
1893
|
-
edges = event.draw(graph)
|
1894
|
-
edges.each {|edge| edge.fontname = options[:font]}
|
1895
|
-
end
|
1896
|
-
|
1897
|
-
# Generate the graph
|
1898
|
-
graphvizVersion = Constants::RGV_VERSION.split('.')
|
1899
|
-
file = File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
1900
|
-
|
1901
|
-
if graphvizVersion[0] == '0' && graphvizVersion[1] == '9' && graphvizVersion[2] == '0'
|
1902
|
-
outputOptions = {:output => options[:format], :file => file}
|
1903
|
-
else
|
1904
|
-
outputOptions = {options[:format] => file}
|
1905
|
-
end
|
1906
|
-
|
1907
|
-
graph.output(outputOptions)
|
1908
|
-
graph
|
1909
|
-
rescue LoadError => ex
|
1910
|
-
$stderr.puts "Cannot draw the machine (#{ex.message}). `gem install ruby-graphviz` >= v0.9.0 and try again."
|
1911
|
-
false
|
1912
|
-
end
|
1932
|
+
graph = Graph.new(name, graph_options)
|
1933
|
+
|
1934
|
+
# Add nodes / edges
|
1935
|
+
states.by_priority.each {|state| state.draw(graph, draw_options)}
|
1936
|
+
events.each {|event| event.draw(graph, draw_options)}
|
1937
|
+
|
1938
|
+
# Output result
|
1939
|
+
graph.output
|
1940
|
+
graph
|
1913
1941
|
end
|
1914
1942
|
|
1915
1943
|
# Determines whether an action hook was defined for firing attribute-based
|
@@ -2142,9 +2170,9 @@ module StateMachine
|
|
2142
2170
|
def define_scopes(custom_plural = nil)
|
2143
2171
|
plural = custom_plural || pluralize(name)
|
2144
2172
|
|
2145
|
-
[
|
2146
|
-
[
|
2147
|
-
method = "#{kind}_#{
|
2173
|
+
[:with, :without].each do |kind|
|
2174
|
+
[name, plural].map {|s| s.to_s}.uniq.each do |suffix|
|
2175
|
+
method = "#{kind}_#{suffix}"
|
2148
2176
|
|
2149
2177
|
if scope = send("create_#{kind}_scope", method)
|
2150
2178
|
# Converts state names to their corresponding values so that they
|
@@ -2194,6 +2222,18 @@ module StateMachine
|
|
2194
2222
|
yield
|
2195
2223
|
end
|
2196
2224
|
|
2225
|
+
# Gets the initial attribute value defined by the owner class (outside of
|
2226
|
+
# the machine's definition). By default, this is always nil.
|
2227
|
+
def owner_class_attribute_default
|
2228
|
+
nil
|
2229
|
+
end
|
2230
|
+
|
2231
|
+
# Checks whether the given state matches the attribute default specified
|
2232
|
+
# by the owner class
|
2233
|
+
def owner_class_attribute_default_matches?(state)
|
2234
|
+
state.matches?(owner_class_attribute_default)
|
2235
|
+
end
|
2236
|
+
|
2197
2237
|
# Updates this machine based on the configuration of other machines in the
|
2198
2238
|
# owner class that share the same target attribute.
|
2199
2239
|
def add_sibling_machine_configs
|
@@ -25,7 +25,7 @@ module StateMachine
|
|
25
25
|
# writing to the object. Default is to write directly to the object.
|
26
26
|
def initialize_states(object, options = {})
|
27
27
|
assert_valid_keys(options, :static, :dynamic, :to)
|
28
|
-
options = {:static =>
|
28
|
+
options = {:static => true, :dynamic => true}.merge(options)
|
29
29
|
|
30
30
|
each_value do |machine|
|
31
31
|
machine.initialize_state(object, :force => options[:static] == :force, :to => options[:to]) unless machine.dynamic_initial_state?
|
@@ -55,7 +55,6 @@ module StateMachine
|
|
55
55
|
|
56
56
|
# Get the transition that will be performed for the event
|
57
57
|
unless transition = event.transition_for(object)
|
58
|
-
machine = event.machine
|
59
58
|
event.on_failure(object)
|
60
59
|
end
|
61
60
|
|
@@ -285,6 +285,35 @@ module StateMachine
|
|
285
285
|
# vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
|
286
286
|
# vehicle.state # => "parked"
|
287
287
|
#
|
288
|
+
# You may also need to call the +initialize_state_machines+ helper manually
|
289
|
+
# in cases where you want to change how static / dynamic initial states get
|
290
|
+
# set. For example, the following example forces the initialization of
|
291
|
+
# static states regardless of their current value:
|
292
|
+
#
|
293
|
+
# class Vehicle
|
294
|
+
# state_machine :state, :initial => :parked do
|
295
|
+
# state nil, :idling
|
296
|
+
# ...
|
297
|
+
# end
|
298
|
+
#
|
299
|
+
# def initialize(attributes = {})
|
300
|
+
# @state = 'idling'
|
301
|
+
# initialize_state_machines(:static => :force) do
|
302
|
+
# ...
|
303
|
+
# end
|
304
|
+
# end
|
305
|
+
# end
|
306
|
+
#
|
307
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
|
308
|
+
# vehicle.state # => "parked"
|
309
|
+
#
|
310
|
+
# The above example is also noteworthy because it demonstrates how to avoid
|
311
|
+
# initialization issues when +nil+ is a valid state. Without passing in
|
312
|
+
# <tt>:static => :force</tt>, state_machine would never have initialized
|
313
|
+
# the state because +nil+ (the default attribute value) would have been
|
314
|
+
# interpreted as a valid current state. As a result, state_machine would
|
315
|
+
# have simply skipped initialization.
|
316
|
+
#
|
288
317
|
# == States
|
289
318
|
#
|
290
319
|
# All of the valid states for the machine are automatically tracked based
|
@@ -44,7 +44,7 @@ module StateMachine
|
|
44
44
|
contexts = @contexts
|
45
45
|
@nodes = []
|
46
46
|
@contexts = []
|
47
|
-
@indices = @indices.inject({}) {|indices, (name,
|
47
|
+
@indices = @indices.inject({}) {|indices, (name, *)| indices[name] = {}; indices}
|
48
48
|
|
49
49
|
# Add nodes *prior* to copying over the contexts so that they don't get
|
50
50
|
# evaluated multiple times
|
data/lib/state_machine/state.rb
CHANGED
@@ -74,11 +74,11 @@ module StateMachine
|
|
74
74
|
@initial = options[:initial] == true
|
75
75
|
|
76
76
|
if name
|
77
|
-
conflicting_machines = machine.owner_class.state_machines.select {|
|
77
|
+
conflicting_machines = machine.owner_class.state_machines.select {|other_name, other_machine| other_machine != machine && other_machine.states[qualified_name, :qualified_name]}
|
78
78
|
|
79
79
|
# Output a warning if another machine has a conflicting qualified name
|
80
80
|
# for a different attribute
|
81
|
-
if conflict = conflicting_machines.detect {|
|
81
|
+
if conflict = conflicting_machines.detect {|other_name, other_machine| other_machine.attribute != machine.attribute}
|
82
82
|
name, other_machine = conflict
|
83
83
|
warn "State #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
|
84
84
|
elsif conflicting_machines.empty?
|
@@ -125,8 +125,13 @@ module StateMachine
|
|
125
125
|
# State.new(machine, :parked, :value => nil).description # => "parked (nil)"
|
126
126
|
# State.new(machine, :parked, :value => 1).description # => "parked (1)"
|
127
127
|
# State.new(machine, :parked, :value => lambda {Time.now}).description # => "parked (*)
|
128
|
-
|
129
|
-
|
128
|
+
#
|
129
|
+
# Configuration options:
|
130
|
+
# * <tt>:human_name</tt> - Whether to use this state's human name in the
|
131
|
+
# description or just the internal name
|
132
|
+
def description(options = {})
|
133
|
+
label = options[:human_name] ? human_name : name
|
134
|
+
description = label ? label.to_s : label.inspect
|
130
135
|
description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
|
131
136
|
description
|
132
137
|
end
|
@@ -189,6 +194,7 @@ module StateMachine
|
|
189
194
|
|
190
195
|
# Calls the method defined by the current state of the machine
|
191
196
|
context.class_eval <<-end_eval, __FILE__, __LINE__ + 1
|
197
|
+
remove_method :#{method}
|
192
198
|
def #{method}(*args, &block)
|
193
199
|
self.class.state_machine(#{machine_name.inspect}).states.fetch(#{name.inspect}).call(self, #{method.inspect}, lambda {super(*args, &block)}, *args, &block)
|
194
200
|
end
|
@@ -226,19 +232,21 @@ module StateMachine
|
|
226
232
|
# * +shape+ - The actual shape of the node. If the state is a final
|
227
233
|
# state, then "doublecircle", otherwise "ellipse".
|
228
234
|
#
|
229
|
-
#
|
230
|
-
|
231
|
-
|
232
|
-
|
235
|
+
# Configuration options:
|
236
|
+
# * <tt>:human_name</tt> - Whether to use the state's human name for the
|
237
|
+
# node's label that gets drawn on the graph
|
238
|
+
def draw(graph, options = {})
|
239
|
+
node = graph.add_nodes(name ? name.to_s : 'nil',
|
240
|
+
:label => description(options),
|
233
241
|
:width => '1',
|
234
242
|
:height => '1',
|
235
243
|
:shape => final? ? 'doublecircle' : 'ellipse'
|
236
244
|
)
|
237
245
|
|
238
246
|
# Add open arrow for initial state
|
239
|
-
graph.
|
247
|
+
graph.add_edges(graph.add_nodes('starting_state', :shape => 'point'), node) if initial?
|
240
248
|
|
241
|
-
|
249
|
+
true
|
242
250
|
end
|
243
251
|
|
244
252
|
# Generates a nicely formatted description of this state's contents.
|
@@ -116,10 +116,10 @@ module StateMachine
|
|
116
116
|
|
117
117
|
# Replace the configuration condition with the one configured for this
|
118
118
|
# proxy, merging together any existing conditions
|
119
|
-
options[:if] = lambda do |*
|
119
|
+
options[:if] = lambda do |*condition_args|
|
120
120
|
# Block may be executed within the context of the actual object, so
|
121
121
|
# it'll either be the first argument or the executing context
|
122
|
-
object =
|
122
|
+
object = condition_args.first || self
|
123
123
|
|
124
124
|
proxy.evaluate_method(object, proxy_condition) &&
|
125
125
|
Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} &&
|
@@ -84,12 +84,19 @@ module StateMachine
|
|
84
84
|
# Whether the transition is only existing temporarily for the object
|
85
85
|
attr_writer :transient
|
86
86
|
|
87
|
+
# Determines whether the curreny ruby implementation supports pausing and
|
88
|
+
# resuming transitions
|
89
|
+
def self.pause_supported?
|
90
|
+
!defined?(RUBY_ENGINE) || %w(ruby maglev).include?(RUBY_ENGINE)
|
91
|
+
end
|
92
|
+
|
87
93
|
# Creates a new, specific transition
|
88
94
|
def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc:
|
89
95
|
@object = object
|
90
96
|
@machine = machine
|
91
97
|
@args = []
|
92
98
|
@transient = false
|
99
|
+
@resume_block = nil
|
93
100
|
|
94
101
|
@event = machine.events.fetch(event)
|
95
102
|
@from_state = machine.states.fetch(from_name)
|
@@ -354,7 +361,7 @@ module StateMachine
|
|
354
361
|
# around callbacks when the remainder of the callback will be executed at
|
355
362
|
# a later point in time.
|
356
363
|
def pause
|
357
|
-
raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.'
|
364
|
+
raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported?
|
358
365
|
|
359
366
|
unless @resume_block
|
360
367
|
require 'continuation' unless defined?(callcc)
|
@@ -73,9 +73,10 @@ module StateMachine
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
|
76
|
+
protected
|
77
77
|
attr_reader :results #:nodoc:
|
78
78
|
|
79
|
+
private
|
79
80
|
# Is this a valid set of transitions? If the collection was creating with
|
80
81
|
# any +false+ values for transitions, then the the collection will be
|
81
82
|
# marked as invalid.
|