telvue_state_machine 1.2.1

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.
Files changed (307) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +72 -0
  4. data/.yardopts +5 -0
  5. data/Appraisals +491 -0
  6. data/CHANGELOG.md +502 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +20 -0
  9. data/README.md +1263 -0
  10. data/Rakefile +41 -0
  11. data/examples/AutoShop_state.png +0 -0
  12. data/examples/Car_state.png +0 -0
  13. data/examples/Gemfile +5 -0
  14. data/examples/Gemfile.lock +14 -0
  15. data/examples/TrafficLight_state.png +0 -0
  16. data/examples/Vehicle_state.png +0 -0
  17. data/examples/auto_shop.rb +13 -0
  18. data/examples/car.rb +21 -0
  19. data/examples/doc/AutoShop.html +2856 -0
  20. data/examples/doc/AutoShop_state.png +0 -0
  21. data/examples/doc/Car.html +919 -0
  22. data/examples/doc/Car_state.png +0 -0
  23. data/examples/doc/TrafficLight.html +2230 -0
  24. data/examples/doc/TrafficLight_state.png +0 -0
  25. data/examples/doc/Vehicle.html +7921 -0
  26. data/examples/doc/Vehicle_state.png +0 -0
  27. data/examples/doc/_index.html +136 -0
  28. data/examples/doc/class_list.html +47 -0
  29. data/examples/doc/css/common.css +1 -0
  30. data/examples/doc/css/full_list.css +55 -0
  31. data/examples/doc/css/style.css +322 -0
  32. data/examples/doc/file_list.html +46 -0
  33. data/examples/doc/frames.html +13 -0
  34. data/examples/doc/index.html +136 -0
  35. data/examples/doc/js/app.js +205 -0
  36. data/examples/doc/js/full_list.js +173 -0
  37. data/examples/doc/js/jquery.js +16 -0
  38. data/examples/doc/method_list.html +734 -0
  39. data/examples/doc/top-level-namespace.html +105 -0
  40. data/examples/merb-rest/controller.rb +51 -0
  41. data/examples/merb-rest/model.rb +28 -0
  42. data/examples/merb-rest/view_edit.html.erb +24 -0
  43. data/examples/merb-rest/view_index.html.erb +23 -0
  44. data/examples/merb-rest/view_new.html.erb +13 -0
  45. data/examples/merb-rest/view_show.html.erb +17 -0
  46. data/examples/rails-rest/controller.rb +43 -0
  47. data/examples/rails-rest/migration.rb +7 -0
  48. data/examples/rails-rest/model.rb +23 -0
  49. data/examples/rails-rest/view__form.html.erb +34 -0
  50. data/examples/rails-rest/view_edit.html.erb +6 -0
  51. data/examples/rails-rest/view_index.html.erb +25 -0
  52. data/examples/rails-rest/view_new.html.erb +5 -0
  53. data/examples/rails-rest/view_show.html.erb +19 -0
  54. data/examples/traffic_light.rb +9 -0
  55. data/examples/vehicle.rb +33 -0
  56. data/gemfiles/active_model_3.0.0.gemfile +7 -0
  57. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  58. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  59. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  60. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  61. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  62. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  63. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  64. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  65. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  66. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  67. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  68. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  69. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  70. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  71. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  72. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  73. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  74. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  75. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  76. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  77. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  78. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  79. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  80. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  81. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  82. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  83. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  84. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  85. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  86. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  87. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  88. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  89. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  90. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  91. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  92. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  93. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  94. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  95. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  96. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  97. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  98. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  99. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  100. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  101. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  102. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  103. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  104. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  105. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  106. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  107. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  108. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  109. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  110. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  111. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  112. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  113. data/gemfiles/default.gemfile +7 -0
  114. data/gemfiles/default.gemfile.lock +27 -0
  115. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  116. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  117. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  118. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  119. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  120. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  121. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  122. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  123. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  124. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  125. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  126. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  127. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  128. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  129. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  130. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  131. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  132. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  133. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  134. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  135. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  136. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  137. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  138. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  139. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  140. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  141. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  142. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  143. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  144. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  145. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  146. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  147. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  148. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  149. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  150. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  151. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  152. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  153. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  154. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  155. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  156. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  157. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  158. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  159. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  160. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  161. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  162. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  163. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  164. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  165. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  166. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  167. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  168. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  169. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  170. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  171. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  172. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  173. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  174. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  175. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  176. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  177. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  178. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  179. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  180. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  181. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  182. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  183. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  184. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  185. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  186. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  187. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  188. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  189. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  190. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  191. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  192. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  193. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  194. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  195. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  196. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  197. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  198. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  199. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  200. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  201. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  202. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  203. data/init.rb +1 -0
  204. data/lib/state_machine.rb +8 -0
  205. data/lib/state_machine/assertions.rb +36 -0
  206. data/lib/state_machine/branch.rb +225 -0
  207. data/lib/state_machine/callback.rb +236 -0
  208. data/lib/state_machine/core.rb +12 -0
  209. data/lib/state_machine/core_ext.rb +2 -0
  210. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  211. data/lib/state_machine/error.rb +13 -0
  212. data/lib/state_machine/eval_helpers.rb +87 -0
  213. data/lib/state_machine/event.rb +257 -0
  214. data/lib/state_machine/event_collection.rb +141 -0
  215. data/lib/state_machine/extensions.rb +149 -0
  216. data/lib/state_machine/graph.rb +92 -0
  217. data/lib/state_machine/helper_module.rb +17 -0
  218. data/lib/state_machine/initializers.rb +4 -0
  219. data/lib/state_machine/initializers/merb.rb +1 -0
  220. data/lib/state_machine/initializers/rails.rb +25 -0
  221. data/lib/state_machine/integrations.rb +121 -0
  222. data/lib/state_machine/integrations/active_model.rb +585 -0
  223. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  224. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  225. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  226. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  227. data/lib/state_machine/integrations/active_record.rb +552 -0
  228. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  229. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  230. data/lib/state_machine/integrations/base.rb +100 -0
  231. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  232. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  233. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  234. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  235. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  236. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  237. data/lib/state_machine/integrations/mongoid.rb +465 -0
  238. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  239. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  240. data/lib/state_machine/integrations/sequel.rb +486 -0
  241. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  242. data/lib/state_machine/machine.rb +2292 -0
  243. data/lib/state_machine/machine_collection.rb +86 -0
  244. data/lib/state_machine/macro_methods.rb +522 -0
  245. data/lib/state_machine/matcher.rb +123 -0
  246. data/lib/state_machine/matcher_helpers.rb +54 -0
  247. data/lib/state_machine/node_collection.rb +222 -0
  248. data/lib/state_machine/path.rb +120 -0
  249. data/lib/state_machine/path_collection.rb +90 -0
  250. data/lib/state_machine/state.rb +297 -0
  251. data/lib/state_machine/state_collection.rb +112 -0
  252. data/lib/state_machine/state_context.rb +138 -0
  253. data/lib/state_machine/transition.rb +470 -0
  254. data/lib/state_machine/transition_collection.rb +245 -0
  255. data/lib/state_machine/version.rb +3 -0
  256. data/lib/state_machine/yard.rb +8 -0
  257. data/lib/state_machine/yard/handlers.rb +12 -0
  258. data/lib/state_machine/yard/handlers/base.rb +32 -0
  259. data/lib/state_machine/yard/handlers/event.rb +25 -0
  260. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  261. data/lib/state_machine/yard/handlers/state.rb +25 -0
  262. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  263. data/lib/state_machine/yard/templates.rb +3 -0
  264. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  265. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  266. data/lib/tasks/state_machine.rake +1 -0
  267. data/lib/tasks/state_machine.rb +30 -0
  268. data/lib/yard-state_machine.rb +2 -0
  269. data/state_machine.gemspec +22 -0
  270. data/test/files/en.yml +17 -0
  271. data/test/files/switch.rb +15 -0
  272. data/test/functional/state_machine_test.rb +1066 -0
  273. data/test/test_helper.rb +7 -0
  274. data/test/unit/assertions_test.rb +40 -0
  275. data/test/unit/branch_test.rb +969 -0
  276. data/test/unit/callback_test.rb +704 -0
  277. data/test/unit/error_test.rb +43 -0
  278. data/test/unit/eval_helpers_test.rb +270 -0
  279. data/test/unit/event_collection_test.rb +398 -0
  280. data/test/unit/event_test.rb +1196 -0
  281. data/test/unit/graph_test.rb +98 -0
  282. data/test/unit/helper_module_test.rb +17 -0
  283. data/test/unit/integrations/active_model_test.rb +1245 -0
  284. data/test/unit/integrations/active_record_test.rb +2551 -0
  285. data/test/unit/integrations/base_test.rb +104 -0
  286. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  287. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  288. data/test/unit/integrations/mongoid_test.rb +2309 -0
  289. data/test/unit/integrations/sequel_test.rb +1896 -0
  290. data/test/unit/integrations_test.rb +83 -0
  291. data/test/unit/invalid_event_test.rb +20 -0
  292. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  293. data/test/unit/invalid_transition_test.rb +115 -0
  294. data/test/unit/machine_collection_test.rb +603 -0
  295. data/test/unit/machine_test.rb +3431 -0
  296. data/test/unit/matcher_helpers_test.rb +37 -0
  297. data/test/unit/matcher_test.rb +155 -0
  298. data/test/unit/node_collection_test.rb +362 -0
  299. data/test/unit/path_collection_test.rb +266 -0
  300. data/test/unit/path_test.rb +485 -0
  301. data/test/unit/state_collection_test.rb +352 -0
  302. data/test/unit/state_context_test.rb +441 -0
  303. data/test/unit/state_machine_test.rb +31 -0
  304. data/test/unit/state_test.rb +1101 -0
  305. data/test/unit/transition_collection_test.rb +2168 -0
  306. data/test/unit/transition_test.rb +1558 -0
  307. metadata +435 -0
@@ -0,0 +1,12 @@
1
+ module StateMachine
2
+ # Graphing extensions aren't required, so they're loaded when referenced
3
+ autoload :Graph, 'state_machine/graph'
4
+ end
5
+
6
+ # Load all of the core implementation required to use state_machine. This
7
+ # includes:
8
+ # * StateMachine::MacroMethods which adds the state_machine DSL to your class
9
+ # * A set of initializers for setting state_machine defaults based on the current
10
+ # running environment (such as within Rails)
11
+ require 'state_machine/macro_methods'
12
+ require 'state_machine/initializers'
@@ -0,0 +1,2 @@
1
+ # Loads all of the extensions to be made to Ruby core classes
2
+ require 'state_machine/core_ext/class/state_machine'
@@ -0,0 +1,5 @@
1
+ require 'state_machine/macro_methods'
2
+
3
+ Class.class_eval do
4
+ include StateMachine::MacroMethods
5
+ end
@@ -0,0 +1,13 @@
1
+ module StateMachine
2
+ # An error occurred during a state machine invocation
3
+ class Error < StandardError
4
+ # The object that failed
5
+ attr_reader :object
6
+
7
+ def initialize(object, message = nil) #:nodoc:
8
+ @object = object
9
+
10
+ super(message)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ module StateMachine
2
+ # Provides a set of helper methods for evaluating methods within the context
3
+ # of an object.
4
+ module EvalHelpers
5
+ # Evaluates one of several different types of methods within the context
6
+ # of the given object. Methods can be one of the following types:
7
+ # * Symbol
8
+ # * Method / Proc
9
+ # * String
10
+ #
11
+ # == Examples
12
+ #
13
+ # Below are examples of the various ways that a method can be evaluated
14
+ # on an object:
15
+ #
16
+ # class Person
17
+ # def initialize(name)
18
+ # @name = name
19
+ # end
20
+ #
21
+ # def name
22
+ # @name
23
+ # end
24
+ # end
25
+ #
26
+ # class PersonCallback
27
+ # def self.run(person)
28
+ # person.name
29
+ # end
30
+ # end
31
+ #
32
+ # person = Person.new('John Smith')
33
+ #
34
+ # evaluate_method(person, :name) # => "John Smith"
35
+ # evaluate_method(person, PersonCallback.method(:run)) # => "John Smith"
36
+ # evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith"
37
+ # evaluate_method(person, lambda {|person| person.name}) # => "John Smith"
38
+ # evaluate_method(person, '@name') # => "John Smith"
39
+ #
40
+ # == Additional arguments
41
+ #
42
+ # Additional arguments can be passed to the methods being evaluated. If
43
+ # the method defines additional arguments other than the object context,
44
+ # then all arguments are required.
45
+ #
46
+ # For example,
47
+ #
48
+ # person = Person.new('John Smith')
49
+ #
50
+ # evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith"
51
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21"
52
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2)
53
+ def evaluate_method(object, method, *args, &block)
54
+ case method
55
+ when Symbol
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)
59
+ when Proc, Method
60
+ args.unshift(object)
61
+ arity = method.arity
62
+
63
+ # Procs don't support blocks in < Ruby 1.9, so it's tacked on as an
64
+ # argument for consistency across versions of Ruby
65
+ if block_given? && Proc === method && arity != 0
66
+ if [1, 2].include?(arity)
67
+ # Force the block to be either the only argument or the 2nd one
68
+ # after the object (may mean additional arguments get discarded)
69
+ args = args[0, arity - 1] + [block]
70
+ else
71
+ # Tack the block to the end of the args
72
+ args << block
73
+ end
74
+ else
75
+ # These method types are only called with 0, 1, or n arguments
76
+ args = args[0, arity] if [0, 1].include?(arity)
77
+ end
78
+
79
+ method.is_a?(Proc) ? method.call(*args) : method.call(*args, &block)
80
+ when String
81
+ eval(method, object.instance_eval {binding}, &block)
82
+ else
83
+ raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,257 @@
1
+ require 'state_machine/transition'
2
+ require 'state_machine/branch'
3
+ require 'state_machine/assertions'
4
+ require 'state_machine/matcher_helpers'
5
+ require 'state_machine/error'
6
+
7
+ module StateMachine
8
+ # An invalid event was specified
9
+ class InvalidEvent < Error
10
+ # The event that was attempted to be run
11
+ attr_reader :event
12
+
13
+ def initialize(object, event_name) #:nodoc:
14
+ @event = event_name
15
+
16
+ super(object, "#{event.inspect} is an unknown state machine event")
17
+ end
18
+ end
19
+
20
+ # An event defines an action that transitions an attribute from one state to
21
+ # another. The state that an attribute is transitioned to depends on the
22
+ # branches configured for the event.
23
+ class Event
24
+ include Assertions
25
+ include MatcherHelpers
26
+
27
+ # The state machine for which this event is defined
28
+ attr_accessor :machine
29
+
30
+ # The name of the event
31
+ attr_reader :name
32
+
33
+ # The fully-qualified name of the event, scoped by the machine's namespace
34
+ attr_reader :qualified_name
35
+
36
+ # The human-readable name for the event
37
+ attr_writer :human_name
38
+
39
+ # The list of branches that determine what state this event transitions
40
+ # objects to when fired
41
+ attr_reader :branches
42
+
43
+ # A list of all of the states known to this event using the configured
44
+ # branches/transitions as the source
45
+ attr_reader :known_states
46
+
47
+ # Creates a new event within the context of the given machine
48
+ #
49
+ # Configuration options:
50
+ # * <tt>:human_name</tt> - The human-readable version of this event's name
51
+ def initialize(machine, name, options = {}) #:nodoc:
52
+ assert_valid_keys(options, :human_name)
53
+
54
+ @machine = machine
55
+ @name = name
56
+ @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
57
+ @human_name = options[:human_name] || @name.to_s.tr('_', ' ')
58
+ reset
59
+
60
+ # Output a warning if another event has a conflicting qualified name
61
+ if conflict = machine.owner_class.state_machines.detect {|other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name]}
62
+ name, other_machine = conflict
63
+ warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
64
+ else
65
+ add_actions
66
+ end
67
+ end
68
+
69
+ # Creates a copy of this event in addition to the list of associated
70
+ # branches to prevent conflicts across events within a class hierarchy.
71
+ def initialize_copy(orig) #:nodoc:
72
+ super
73
+ @branches = @branches.dup
74
+ @known_states = @known_states.dup
75
+ end
76
+
77
+ # Transforms the event name into a more human-readable format, such as
78
+ # "turn on" instead of "turn_on"
79
+ def human_name(klass = @machine.owner_class)
80
+ @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
81
+ end
82
+
83
+ # Evaluates the given block within the context of this event. This simply
84
+ # provides a DSL-like syntax for defining transitions.
85
+ def context(&block)
86
+ instance_eval(&block)
87
+ end
88
+
89
+ # Creates a new transition that determines what to change the current state
90
+ # to when this event fires.
91
+ #
92
+ # Since this transition is being defined within an event context, you do
93
+ # *not* need to specify the <tt>:on</tt> option for the transition. For
94
+ # example:
95
+ #
96
+ # state_machine do
97
+ # event :ignite do
98
+ # transition :parked => :idling, :idling => same, :if => :seatbelt_on? # Transitions to :idling if seatbelt is on
99
+ # transition all => :parked, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off
100
+ # end
101
+ # end
102
+ #
103
+ # See StateMachine::Machine#transition for a description of the possible
104
+ # configurations for defining transitions.
105
+ def transition(options)
106
+ raise ArgumentError, 'Must specify as least one transition requirement' if options.empty?
107
+
108
+ # Only a certain subset of explicit options are allowed for transition
109
+ # requirements
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
+
112
+ branches << branch = Branch.new(options.merge(:on => name))
113
+ @known_states |= branch.known_states
114
+ branch
115
+ end
116
+
117
+ # Determines whether any transitions can be performed for this event based
118
+ # on the current state of the given object.
119
+ #
120
+ # If the event can't be fired, then this will return false, otherwise true.
121
+ #
122
+ # *Note* that this will not take the object context into account. Although
123
+ # a transition may be possible based on the state machine definition,
124
+ # object-specific behaviors (like validations) may prevent it from firing.
125
+ def can_fire?(object, requirements = {})
126
+ !transition_for(object, requirements).nil?
127
+ end
128
+
129
+ # Finds and builds the next transition that can be performed on the given
130
+ # object. If no transitions can be made, then this will return nil.
131
+ #
132
+ # Valid requirement options:
133
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
134
+ # are specified, then this will be the object's current state.
135
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
136
+ # specified, then this will match any to state.
137
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
138
+ # conditionals defined for each one. Default is true.
139
+ def transition_for(object, requirements = {})
140
+ assert_valid_keys(requirements, :from, :to, :guard)
141
+ requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from)
142
+
143
+ branches.each do |branch|
144
+ if match = branch.match(object, requirements)
145
+ # Branch allows for the transition to occur
146
+ from = requirements[:from]
147
+ to = if match[:to].is_a?(LoopbackMatcher)
148
+ from
149
+ else
150
+ values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.map {|state| state.name}
151
+
152
+ match[:to].filter(values).first
153
+ end
154
+
155
+ return Transition.new(object, machine, name, from, to, !custom_from_state)
156
+ end
157
+ end
158
+
159
+ # No transition matched
160
+ nil
161
+ end
162
+
163
+ # Attempts to perform the next available transition on the given object.
164
+ # If no transitions can be made, then this will return false, otherwise
165
+ # true.
166
+ #
167
+ # Any additional arguments are passed to the StateMachine::Transition#perform
168
+ # instance method.
169
+ def fire(object, *args)
170
+ machine.reset(object)
171
+
172
+ if transition = transition_for(object)
173
+ transition.perform(*args)
174
+ else
175
+ on_failure(object)
176
+ false
177
+ end
178
+ end
179
+
180
+ # Marks the object as invalid and runs any failure callbacks associated with
181
+ # this event. This should get called anytime this event fails to transition.
182
+ def on_failure(object)
183
+ state = machine.states.match!(object)
184
+ machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]])
185
+
186
+ Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false)
187
+ end
188
+
189
+ # Resets back to the initial state of the event, with no branches / known
190
+ # states associated. This allows you to redefine an event in situations
191
+ # where you either are re-using an existing state machine implementation
192
+ # or are subclassing machines.
193
+ def reset
194
+ @branches = []
195
+ @known_states = []
196
+ end
197
+
198
+ # Draws a representation of this event on the given graph. This will
199
+ # create 1 or more edges on the graph for each branch (i.e. transition)
200
+ # configured.
201
+ #
202
+ # Configuration options:
203
+ # * <tt>:human_name</tt> - Whether to use the event's human name for the
204
+ # node's label that gets drawn on the graph
205
+ def draw(graph, options = {})
206
+ valid_states = machine.states.by_priority.map {|state| state.name}
207
+ branches.each do |branch|
208
+ branch.draw(graph, options[:human_name] ? human_name : name, valid_states)
209
+ end
210
+
211
+ true
212
+ end
213
+
214
+ # Generates a nicely formatted description of this event's contents.
215
+ #
216
+ # For example,
217
+ #
218
+ # event = StateMachine::Event.new(machine, :park)
219
+ # event.transition all - :idling => :parked, :idling => same
220
+ # event # => #<StateMachine::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
221
+ def inspect
222
+ transitions = branches.map do |branch|
223
+ branch.state_requirements.map do |state_requirement|
224
+ "#{state_requirement[:from].description} => #{state_requirement[:to].description}"
225
+ end * ', '
226
+ end
227
+
228
+ "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>"
229
+ end
230
+
231
+ protected
232
+ # Add the various instance methods that can transition the object using
233
+ # the current event
234
+ def add_actions
235
+ # Checks whether the event can be fired on the current object
236
+ machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args|
237
+ machine.event(name).can_fire?(object, *args)
238
+ end
239
+
240
+ # Gets the next transition that would be performed if the event were
241
+ # fired now
242
+ machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args|
243
+ machine.event(name).transition_for(object, *args)
244
+ end
245
+
246
+ # Fires the event
247
+ machine.define_helper(:instance, qualified_name) do |machine, object, *args|
248
+ machine.event(name).fire(object, *args)
249
+ end
250
+
251
+ # Fires the event, raising an exception if it fails
252
+ machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args|
253
+ object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition.new(object, machine, name))
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,141 @@
1
+ require 'state_machine/node_collection'
2
+
3
+ module StateMachine
4
+ # Represents a collection of events in a state machine
5
+ class EventCollection < NodeCollection
6
+ def initialize(machine) #:nodoc:
7
+ super(machine, :index => [:name, :qualified_name])
8
+ end
9
+
10
+ # Gets the list of events that can be fired on the given object.
11
+ #
12
+ # Valid requirement options:
13
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
14
+ # are specified, then this will be the object's current state.
15
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
16
+ # specified, then this will match any to state.
17
+ # * <tt>:on</tt> - One or more events that fire the transition. If none
18
+ # are specified, then this will match any event.
19
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
20
+ # conditionals defined for each one. Default is true.
21
+ #
22
+ # == Examples
23
+ #
24
+ # class Vehicle
25
+ # state_machine :initial => :parked do
26
+ # event :park do
27
+ # transition :idling => :parked
28
+ # end
29
+ #
30
+ # event :ignite do
31
+ # transition :parked => :idling
32
+ # end
33
+ # end
34
+ # end
35
+ #
36
+ # events = Vehicle.state_machine(:state).events
37
+ #
38
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
39
+ # events.valid_for(vehicle) # => [#<StateMachine::Event name=:ignite transitions=[:parked => :idling]>]
40
+ #
41
+ # vehicle.state = 'idling'
42
+ # events.valid_for(vehicle) # => [#<StateMachine::Event name=:park transitions=[:idling => :parked]>]
43
+ def valid_for(object, requirements = {})
44
+ match(requirements).select {|event| event.can_fire?(object, requirements)}
45
+ end
46
+
47
+ # Gets the list of transitions that can be run on the given object.
48
+ #
49
+ # Valid requirement options:
50
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
51
+ # are specified, then this will be the object's current state.
52
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
53
+ # specified, then this will match any to state.
54
+ # * <tt>:on</tt> - One or more events that fire the transition. If none
55
+ # are specified, then this will match any event.
56
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
57
+ # conditionals defined for each one. Default is true.
58
+ #
59
+ # == Examples
60
+ #
61
+ # class Vehicle
62
+ # state_machine :initial => :parked do
63
+ # event :park do
64
+ # transition :idling => :parked
65
+ # end
66
+ #
67
+ # event :ignite do
68
+ # transition :parked => :idling
69
+ # end
70
+ # end
71
+ # end
72
+ #
73
+ # events = Vehicle.state_machine.events
74
+ #
75
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
76
+ # events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
77
+ #
78
+ # vehicle.state = 'idling'
79
+ # events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
80
+ #
81
+ # # Search for explicit transitions regardless of the current state
82
+ # events.transitions_for(vehicle, :from => :parked) # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
83
+ def transitions_for(object, requirements = {})
84
+ match(requirements).map {|event| event.transition_for(object, requirements)}.compact
85
+ end
86
+
87
+ # Gets the transition that should be performed for the event stored in the
88
+ # given object's event attribute. This also takes an additional parameter
89
+ # for automatically invalidating the object if the event or transition are
90
+ # invalid. By default, this is turned off.
91
+ #
92
+ # *Note* that if a transition has already been generated for the event, then
93
+ # that transition will be used.
94
+ #
95
+ # == Examples
96
+ #
97
+ # class Vehicle < ActiveRecord::Base
98
+ # state_machine :initial => :parked do
99
+ # event :ignite do
100
+ # transition :parked => :idling
101
+ # end
102
+ # end
103
+ # end
104
+ #
105
+ # vehicle = Vehicle.new # => #<Vehicle id: nil, state: "parked">
106
+ # events = Vehicle.state_machine.events
107
+ #
108
+ # vehicle.state_event = nil
109
+ # events.attribute_transition_for(vehicle) # => nil # Event isn't defined
110
+ #
111
+ # vehicle.state_event = 'invalid'
112
+ # events.attribute_transition_for(vehicle) # => false # Event is invalid
113
+ #
114
+ # vehicle.state_event = 'ignite'
115
+ # events.attribute_transition_for(vehicle) # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
116
+ def attribute_transition_for(object, invalidate = false)
117
+ return unless machine.action
118
+
119
+ result = machine.read(object, :event_transition) || if event_name = machine.read(object, :event)
120
+ if event = self[event_name.to_sym, :name]
121
+ event.transition_for(object) || begin
122
+ # No valid transition: invalidate
123
+ machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).human_name(object.class)]]) if invalidate
124
+ false
125
+ end
126
+ else
127
+ # Event is unknown: invalidate
128
+ machine.invalidate(object, :event, :invalid) if invalidate
129
+ false
130
+ end
131
+ end
132
+
133
+ result
134
+ end
135
+
136
+ private
137
+ def match(requirements) #:nodoc:
138
+ requirements && requirements[:on] ? [fetch(requirements.delete(:on))] : self
139
+ end
140
+ end
141
+ end