state_machine 1.1.2 → 1.2.0
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/.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
@@ -53,7 +53,9 @@ module StateMachine
|
|
53
53
|
def evaluate_method(object, method, *args, &block)
|
54
54
|
case method
|
55
55
|
when Symbol
|
56
|
-
|
56
|
+
klass = (class << object; self; end)
|
57
|
+
args = [] if (klass.method_defined?(method) || klass.private_method_defined?(method)) && object.method(method).arity == 0
|
58
|
+
object.send(method, *args, &block)
|
57
59
|
when Proc, Method
|
58
60
|
args.unshift(object)
|
59
61
|
arity = method.arity
|
@@ -74,12 +76,12 @@ module StateMachine
|
|
74
76
|
args = args[0, arity] if [0, 1].include?(arity)
|
75
77
|
end
|
76
78
|
|
77
|
-
method.call(*args, &block)
|
79
|
+
method.is_a?(Proc) ? method.call(*args) : method.call(*args, &block)
|
78
80
|
when String
|
79
81
|
eval(method, object.instance_eval {binding}, &block)
|
80
82
|
else
|
81
83
|
raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
|
82
|
-
|
84
|
+
end
|
83
85
|
end
|
84
86
|
end
|
85
87
|
end
|
data/lib/state_machine/event.rb
CHANGED
@@ -58,7 +58,7 @@ module StateMachine
|
|
58
58
|
reset
|
59
59
|
|
60
60
|
# Output a warning if another event has a conflicting qualified name
|
61
|
-
if conflict = machine.owner_class.state_machines.detect {|
|
61
|
+
if conflict = machine.owner_class.state_machines.detect {|other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name]}
|
62
62
|
name, other_machine = conflict
|
63
63
|
warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
|
64
64
|
else
|
@@ -107,7 +107,7 @@ module StateMachine
|
|
107
107
|
|
108
108
|
# Only a certain subset of explicit options are allowed for transition
|
109
109
|
# requirements
|
110
|
-
assert_valid_keys(options, :from, :to, :except_from, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
|
110
|
+
assert_valid_keys(options, :from, :to, :except_from, :except_to, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
|
111
111
|
|
112
112
|
branches << branch = Branch.new(options.merge(:on => name))
|
113
113
|
@known_states |= branch.known_states
|
@@ -144,7 +144,12 @@ module StateMachine
|
|
144
144
|
if match = branch.match(object, requirements)
|
145
145
|
# Branch allows for the transition to occur
|
146
146
|
from = requirements[:from]
|
147
|
-
to = match[:to].
|
147
|
+
to = if match[:to].is_a?(LoopbackMatcher)
|
148
|
+
from
|
149
|
+
else
|
150
|
+
values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.keys(:name)
|
151
|
+
match[:to].filter(values).first
|
152
|
+
end
|
148
153
|
|
149
154
|
return Transition.new(object, machine, name, from, to, !custom_from_state)
|
150
155
|
end
|
@@ -193,10 +198,16 @@ module StateMachine
|
|
193
198
|
# create 1 or more edges on the graph for each branch (i.e. transition)
|
194
199
|
# configured.
|
195
200
|
#
|
196
|
-
#
|
197
|
-
|
201
|
+
# Configuration options:
|
202
|
+
# * <tt>:human_name</tt> - Whether to use the event's human name for the
|
203
|
+
# node's label that gets drawn on the graph
|
204
|
+
def draw(graph, options = {})
|
198
205
|
valid_states = machine.states.by_priority.map {|state| state.name}
|
199
|
-
branches.
|
206
|
+
branches.each do |branch|
|
207
|
+
branch.draw(graph, options[:human_name] ? human_name : name, valid_states)
|
208
|
+
end
|
209
|
+
|
210
|
+
true
|
200
211
|
end
|
201
212
|
|
202
213
|
# Generates a nicely formatted description of this event's contents.
|
@@ -0,0 +1,92 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'ruby-graphviz', '>=0.9.17'
|
4
|
+
require 'graphviz'
|
5
|
+
rescue LoadError => ex
|
6
|
+
$stderr.puts "Cannot draw the machine (#{ex.message}). `gem install ruby-graphviz` >= v0.9.17 and try again."
|
7
|
+
raise
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'state_machine/assertions'
|
11
|
+
|
12
|
+
module StateMachine
|
13
|
+
# Provides a set of higher-order features on top of the raw GraphViz graphs
|
14
|
+
class Graph < GraphViz
|
15
|
+
include Assertions
|
16
|
+
|
17
|
+
# The name of the font to draw state names in
|
18
|
+
attr_reader :font
|
19
|
+
|
20
|
+
# The graph's full filename
|
21
|
+
attr_reader :file_path
|
22
|
+
|
23
|
+
# The image format to generate the graph in
|
24
|
+
attr_reader :file_format
|
25
|
+
|
26
|
+
# Creates a new graph with the given name.
|
27
|
+
#
|
28
|
+
# Configuration options:
|
29
|
+
# * <tt>:path</tt> - The path to write the graph file to. Default is the
|
30
|
+
# current directory (".").
|
31
|
+
# * <tt>:format</tt> - The image format to generate the graph in.
|
32
|
+
# Default is "png'.
|
33
|
+
# * <tt>:font</tt> - The name of the font to draw state names in.
|
34
|
+
# Default is "Arial".
|
35
|
+
# * <tt>:orientation</tt> - The direction of the graph ("portrait" or
|
36
|
+
# "landscape"). Default is "portrait".
|
37
|
+
def initialize(name, options = {})
|
38
|
+
options = {:path => '.', :format => 'png', :font => 'Arial', :orientation => 'portrait'}.merge(options)
|
39
|
+
assert_valid_keys(options, :path, :format, :font, :orientation)
|
40
|
+
|
41
|
+
@font = options[:font]
|
42
|
+
@file_path = File.join(options[:path], "#{name}.#{options[:format]}")
|
43
|
+
@file_format = options[:format]
|
44
|
+
|
45
|
+
super('G', :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB')
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generates the actual image file based on the nodes / edges added to the
|
49
|
+
# graph. The path to the file is based on the configuration options for
|
50
|
+
# this graph.
|
51
|
+
def output
|
52
|
+
super(@file_format => @file_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Adds a new node to the graph. The font for the node will be automatically
|
56
|
+
# set based on the graph configuration. The generated node will be returned.
|
57
|
+
#
|
58
|
+
# For example,
|
59
|
+
#
|
60
|
+
# graph = StateMachine::Graph.new('test')
|
61
|
+
# graph.add_nodes('parked', :label => 'Parked', :width => '1', :height => '1', :shape => 'ellipse')
|
62
|
+
def add_nodes(*args)
|
63
|
+
node = v0_api? ? add_node(*args) : super
|
64
|
+
node.fontname = @font
|
65
|
+
node
|
66
|
+
end
|
67
|
+
|
68
|
+
# Adds a new edge to the graph. The font for the edge will be automatically
|
69
|
+
# set based on the graph configuration. The generated edge will be returned.
|
70
|
+
#
|
71
|
+
# For example,
|
72
|
+
#
|
73
|
+
# graph = StateMachine::Graph.new('test')
|
74
|
+
# graph.add_edges('parked', 'idling', :label => 'ignite')
|
75
|
+
def add_edges(*args)
|
76
|
+
edge = v0_api? ? add_edge(*args) : super
|
77
|
+
edge.fontname = @font
|
78
|
+
edge
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
# Determines whether the old v0 api is in use
|
83
|
+
def v0_api?
|
84
|
+
version[0] == '0' || version[0] == '1' && version[1] == '0' && version[2] <= '2'
|
85
|
+
end
|
86
|
+
|
87
|
+
# The ruby-graphviz version data
|
88
|
+
def version
|
89
|
+
Constants::RGV_VERSION.split('.')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -73,7 +73,19 @@ module StateMachine
|
|
73
73
|
# StateMachine::Integrations.match(MongoMapperVehicle) # => StateMachine::Integrations::MongoMapper
|
74
74
|
# StateMachine::Integrations.match(SequelVehicle) # => StateMachine::Integrations::Sequel
|
75
75
|
def self.match(klass)
|
76
|
-
all.detect {|integration| integration.
|
76
|
+
all.detect {|integration| integration.matches?(klass)}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Attempts to find an integration that matches the given list of ancestors.
|
80
|
+
# This will look through all of the built-in integrations under the StateMachine::Integrations
|
81
|
+
# namespace and find one that successfully matches one of the ancestors.
|
82
|
+
#
|
83
|
+
# == Examples
|
84
|
+
#
|
85
|
+
# StateMachine::Integrations.match([]) # => nil
|
86
|
+
# StateMachine::Integrations.match(['ActiveRecord::Base') # => StateMachine::Integrations::ActiveModel
|
87
|
+
def self.match_ancestors(ancestors)
|
88
|
+
all.detect {|integration| integration.matches_ancestors?(ancestors)}
|
77
89
|
end
|
78
90
|
|
79
91
|
# Finds an integration with the given name. If the integration cannot be
|
@@ -366,26 +366,19 @@ module StateMachine
|
|
366
366
|
|
367
367
|
@defaults = {}
|
368
368
|
|
369
|
-
# Whether this integration is available. Only true if ActiveModel is
|
370
|
-
# defined.
|
371
|
-
def self.available?
|
372
|
-
defined?(::ActiveModel)
|
373
|
-
end
|
374
|
-
|
375
|
-
# Should this integration be used for state machines in the given class?
|
376
369
|
# Classes that include ActiveModel::Observing or ActiveModel::Validations
|
377
370
|
# will automatically use the ActiveModel integration.
|
378
|
-
def self.
|
379
|
-
%w(Observing
|
371
|
+
def self.matching_ancestors
|
372
|
+
%w(ActiveModel ActiveModel::Observing ActiveModel::Validations)
|
380
373
|
end
|
381
374
|
|
382
375
|
# Adds a validation error to the given object
|
383
376
|
def invalidate(object, attribute, message, values = [])
|
384
377
|
if supports_validations?
|
385
378
|
attribute = self.attribute(attribute)
|
386
|
-
options = values.inject({}) do |
|
387
|
-
|
388
|
-
|
379
|
+
options = values.inject({}) do |h, (key, value)|
|
380
|
+
h[key] = value
|
381
|
+
h
|
389
382
|
end
|
390
383
|
|
391
384
|
default_options = default_error_message_options(object, attribute, message)
|
@@ -454,8 +447,8 @@ module StateMachine
|
|
454
447
|
value = value ? value.to_s : 'nil'
|
455
448
|
|
456
449
|
# Generate all possible translation keys
|
457
|
-
translations = ancestors.map {|ancestor| :"#{ancestor.model_name.underscore}.#{name}.#{group}.#{value}"}
|
458
|
-
translations.concat(ancestors.map {|ancestor| :"#{ancestor.model_name.underscore}.#{group}.#{value}"})
|
450
|
+
translations = ancestors.map {|ancestor| :"#{ancestor.model_name.to_s.underscore}.#{name}.#{group}.#{value}"}
|
451
|
+
translations.concat(ancestors.map {|ancestor| :"#{ancestor.model_name.to_s.underscore}.#{group}.#{value}"})
|
459
452
|
translations.concat([:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.humanize.downcase])
|
460
453
|
I18n.translate(translations.shift, :default => translations, :scope => [i18n_scope(klass), :state_machines])
|
461
454
|
end
|
@@ -482,6 +475,7 @@ module StateMachine
|
|
482
475
|
# Loads extensions to ActiveModel's Observers
|
483
476
|
def load_observer_extensions
|
484
477
|
require 'state_machine/integrations/active_model/observer'
|
478
|
+
require 'state_machine/integrations/active_model/observer_update'
|
485
479
|
end
|
486
480
|
|
487
481
|
# Adds a set of default callbacks that utilize the Observer extensions
|
@@ -537,15 +531,15 @@ module StateMachine
|
|
537
531
|
|
538
532
|
# Configures new states with the built-in humanize scheme
|
539
533
|
def add_states(new_states)
|
540
|
-
super.each do |
|
541
|
-
|
534
|
+
super.each do |new_state|
|
535
|
+
new_state.human_name = lambda {|state, klass| translate(klass, :state, state.name)}
|
542
536
|
end
|
543
537
|
end
|
544
538
|
|
545
539
|
# Configures new event with the built-in humanize scheme
|
546
540
|
def add_events(new_events)
|
547
|
-
super.each do |
|
548
|
-
|
541
|
+
super.each do |new_event|
|
542
|
+
new_event.human_name = lambda {|event, klass| translate(klass, :event, event.name)}
|
549
543
|
end
|
550
544
|
end
|
551
545
|
|
@@ -575,14 +569,14 @@ module StateMachine
|
|
575
569
|
["_from_#{from}", nil].each do |from_segment|
|
576
570
|
["_to_#{to}", nil].each do |to_segment|
|
577
571
|
object.class.changed if object.class.respond_to?(:changed)
|
578
|
-
object.class.notify_observers('update_with_transition', [
|
572
|
+
object.class.notify_observers('update_with_transition', ObserverUpdate.new([event_segment, from_segment, to_segment].join, object, transition))
|
579
573
|
end
|
580
574
|
end
|
581
575
|
end
|
582
576
|
|
583
577
|
# Generic updates
|
584
578
|
object.class.changed if object.class.respond_to?(:changed)
|
585
|
-
object.class.notify_observers('update_with_transition',
|
579
|
+
object.class.notify_observers('update_with_transition', ObserverUpdate.new("#{type}_transition", object, transition))
|
586
580
|
|
587
581
|
true
|
588
582
|
end
|
@@ -19,9 +19,9 @@ module StateMachine
|
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
module Observer
|
22
|
-
def update_with_transition(
|
23
|
-
|
24
|
-
send(
|
22
|
+
def update_with_transition(observer_update)
|
23
|
+
method = observer_update.method
|
24
|
+
send(method, *observer_update.args) if respond_to?(method)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module StateMachine
|
2
|
+
module Integrations #:nodoc:
|
3
|
+
module ActiveModel
|
4
|
+
# Represents the encapsulation of all of the details to be included in an
|
5
|
+
# update to state machine observers. This allows multiple arguments to
|
6
|
+
# get passed to an observer method (instead of just a single +object+)
|
7
|
+
# while still respecting the way in which ActiveModel checks for the
|
8
|
+
# object's list of observers.
|
9
|
+
class ObserverUpdate
|
10
|
+
# The method to invoke on the observer
|
11
|
+
attr_reader :method
|
12
|
+
|
13
|
+
# The object being transitioned
|
14
|
+
attr_reader :object
|
15
|
+
|
16
|
+
# The transition being run
|
17
|
+
attr_reader :transition
|
18
|
+
|
19
|
+
def initialize(method, object, transition) #:nodoc:
|
20
|
+
@method, @object, @transition = method, object, transition
|
21
|
+
end
|
22
|
+
|
23
|
+
# The arguments to pass into the method
|
24
|
+
def args
|
25
|
+
[object, transition]
|
26
|
+
end
|
27
|
+
|
28
|
+
# The class of the object being transitioned. Normally the object
|
29
|
+
# getting passed into observer methods is the actual instance of the
|
30
|
+
# ActiveModel class. ActiveModel uses that instance's class to check
|
31
|
+
# for enabled / disabled observers.
|
32
|
+
#
|
33
|
+
# Since state_machine is passing an ObserverUpdate instance into observer
|
34
|
+
# methods, +class+ needs to be overridden so that ActiveModel can still
|
35
|
+
# get access to the enabled / disabled observers.
|
36
|
+
def class
|
37
|
+
object.class
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -136,18 +136,6 @@ module StateMachine
|
|
136
136
|
# end
|
137
137
|
# end
|
138
138
|
#
|
139
|
-
# If using the +save+ action for the machine, this option will be ignored as
|
140
|
-
# the transaction will be created by ActiveRecord within +save+. To avoid
|
141
|
-
# this, use a different action like so:
|
142
|
-
#
|
143
|
-
# class Vehicle < ActiveRecord::Base
|
144
|
-
# state_machine :initial => :parked, :use_transactions => false, :action => :save_state do
|
145
|
-
# ...
|
146
|
-
# end
|
147
|
-
#
|
148
|
-
# alias_method :save_state, :save
|
149
|
-
# end
|
150
|
-
#
|
151
139
|
# == Validations
|
152
140
|
#
|
153
141
|
# As mentioned in StateMachine::Machine#state, you can define behaviors,
|
@@ -290,6 +278,27 @@ module StateMachine
|
|
290
278
|
# that allows new records to be saved without being affected by rollbacks
|
291
279
|
# in the +Vehicle+ model's transaction.
|
292
280
|
#
|
281
|
+
# === Callback Order
|
282
|
+
#
|
283
|
+
# Callbacks occur in the following order. Callbacks specific to state_machine
|
284
|
+
# are bolded. The remaining callbacks are part of ActiveRecord.
|
285
|
+
#
|
286
|
+
# * (-) save
|
287
|
+
# * (-) begin transaction (if enabled)
|
288
|
+
# * (1) *before_transition*
|
289
|
+
# * (-) valid
|
290
|
+
# * (2) before_validation
|
291
|
+
# * (-) validate
|
292
|
+
# * (3) after_validation
|
293
|
+
# * (4) before_save
|
294
|
+
# * (5) before_create
|
295
|
+
# * (-) create
|
296
|
+
# * (6) after_create
|
297
|
+
# * (7) after_save
|
298
|
+
# * (8) *after_transition*
|
299
|
+
# * (-) end transaction (if enabled)
|
300
|
+
# * (9) after_commit
|
301
|
+
#
|
293
302
|
# == Observers
|
294
303
|
#
|
295
304
|
# In addition to support for ActiveRecord-like hooks, there is additional
|
@@ -407,17 +416,10 @@ module StateMachine
|
|
407
416
|
# The default options to use for state machines using this integration
|
408
417
|
@defaults = {:action => :save}
|
409
418
|
|
410
|
-
# Whether this integration is available. Only true if ActiveRecord::Base
|
411
|
-
# is defined.
|
412
|
-
def self.available?
|
413
|
-
defined?(::ActiveRecord::Base)
|
414
|
-
end
|
415
|
-
|
416
|
-
# Should this integration be used for state machines in the given class?
|
417
419
|
# Classes that inherit from ActiveRecord::Base will automatically use
|
418
420
|
# the ActiveRecord integration.
|
419
|
-
def self.
|
420
|
-
|
421
|
+
def self.matching_ancestors
|
422
|
+
%w(ActiveRecord::Base)
|
421
423
|
end
|
422
424
|
|
423
425
|
def self.extended(base) #:nodoc:
|
@@ -431,6 +433,13 @@ module StateMachine
|
|
431
433
|
action == :save
|
432
434
|
end
|
433
435
|
|
436
|
+
# Gets the db default for the machine's attribute
|
437
|
+
def owner_class_attribute_default
|
438
|
+
if owner_class.connected? && owner_class.table_exists? && column = owner_class.columns_hash[attribute.to_s]
|
439
|
+
column.default
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
434
443
|
# Defines an initialization hook into the owner class for setting the
|
435
444
|
# initial state of the machine *before* any attributes are set on the
|
436
445
|
# object
|
@@ -448,7 +457,7 @@ module StateMachine
|
|
448
457
|
def column_defaults(*) #:nodoc:
|
449
458
|
result = super
|
450
459
|
# No need to pass in an object, since the overrides will be forced
|
451
|
-
self.state_machines.initialize_states(nil, :dynamic => false, :to => result)
|
460
|
+
self.state_machines.initialize_states(nil, :static => :force, :dynamic => false, :to => result)
|
452
461
|
result
|
453
462
|
end
|
454
463
|
end_eval
|
@@ -469,7 +478,19 @@ module StateMachine
|
|
469
478
|
# Uses around callbacks to run state events if using the :save hook
|
470
479
|
def define_action_hook
|
471
480
|
if action_hook == :save
|
472
|
-
|
481
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
482
|
+
def save(*)
|
483
|
+
self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
|
484
|
+
end
|
485
|
+
|
486
|
+
def save!(*)
|
487
|
+
self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(ActiveRecord::RecordInvalid.new(self))
|
488
|
+
end
|
489
|
+
|
490
|
+
def changed_for_autosave?
|
491
|
+
super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)}
|
492
|
+
end
|
493
|
+
end_eval
|
473
494
|
else
|
474
495
|
super
|
475
496
|
end
|
@@ -477,7 +498,9 @@ module StateMachine
|
|
477
498
|
|
478
499
|
# Runs state events around the machine's :save action
|
479
500
|
def around_save(object)
|
480
|
-
|
501
|
+
transaction(object) do
|
502
|
+
object.class.state_machines.transitions(object, action).perform { yield }
|
503
|
+
end
|
481
504
|
end
|
482
505
|
|
483
506
|
# Creates a scope for finding records *with* a particular state or
|
@@ -502,7 +525,11 @@ module StateMachine
|
|
502
525
|
# an ActiveRecord::Rollback exception if the yielded block fails
|
503
526
|
# (i.e. returns false).
|
504
527
|
def transaction(object)
|
505
|
-
|
528
|
+
result = nil
|
529
|
+
object.class.transaction do
|
530
|
+
raise ::ActiveRecord::Rollback unless result = yield
|
531
|
+
end
|
532
|
+
result
|
506
533
|
end
|
507
534
|
|
508
535
|
# Defines a new named scope with the given name
|