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
@@ -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
|