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,245 @@
1
+ module StateMachine
2
+ # Represents a collection of transitions in a state machine
3
+ class TransitionCollection < Array
4
+ include Assertions
5
+
6
+ # Whether to skip running the action for each transition's machine
7
+ attr_reader :skip_actions
8
+
9
+ # Whether to skip running the after callbacks
10
+ attr_reader :skip_after
11
+
12
+ # Whether transitions should wrapped around a transaction block
13
+ attr_reader :use_transaction
14
+
15
+ # Creates a new collection of transitions that can be run in parallel. Each
16
+ # transition *must* be for a different attribute.
17
+ #
18
+ # Configuration options:
19
+ # * <tt>:actions</tt> - Whether to run the action configured for each transition
20
+ # * <tt>:after</tt> - Whether to run after callbacks
21
+ # * <tt>:transaction</tt> - Whether to wrap transitions within a transaction
22
+ def initialize(transitions = [], options = {})
23
+ super(transitions)
24
+
25
+ # Determine the validity of the transitions as a whole
26
+ @valid = all?
27
+ reject! {|transition| !transition}
28
+
29
+ attributes = map {|transition| transition.attribute}.uniq
30
+ raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != length
31
+
32
+ assert_valid_keys(options, :actions, :after, :transaction)
33
+ options = {:actions => true, :after => true, :transaction => true}.merge(options)
34
+ @skip_actions = !options[:actions]
35
+ @skip_after = !options[:after]
36
+ @use_transaction = options[:transaction]
37
+ end
38
+
39
+ # Runs each of the collection's transitions in parallel.
40
+ #
41
+ # All transitions will run through the following steps:
42
+ # 1. Before callbacks
43
+ # 2. Persist state
44
+ # 3. Invoke action
45
+ # 4. After callbacks (if configured)
46
+ # 5. Rollback (if action is unsuccessful)
47
+ #
48
+ # If a block is passed to this method, that block will be called instead
49
+ # of invoking each transition's action.
50
+ def perform(&block)
51
+ reset
52
+
53
+ if valid?
54
+ if use_event_attributes? && !block_given?
55
+ each do |transition|
56
+ transition.transient = true
57
+ transition.machine.write(object, :event_transition, transition)
58
+ end
59
+
60
+ run_actions
61
+ else
62
+ within_transaction do
63
+ catch(:halt) { run_callbacks(&block) }
64
+ rollback unless success?
65
+ end
66
+ end
67
+ end
68
+
69
+ if actions.length == 1 && results.include?(actions.first)
70
+ results[actions.first]
71
+ else
72
+ success?
73
+ end
74
+ end
75
+
76
+ protected
77
+ attr_reader :results #:nodoc:
78
+
79
+ private
80
+ # Is this a valid set of transitions? If the collection was creating with
81
+ # any +false+ values for transitions, then the the collection will be
82
+ # marked as invalid.
83
+ def valid?
84
+ @valid
85
+ end
86
+
87
+ # Did each transition perform successfully? This will only be true if the
88
+ # following requirements are met:
89
+ # * No +before+ callbacks halt
90
+ # * All actions run successfully (always true if skipping actions)
91
+ def success?
92
+ @success
93
+ end
94
+
95
+ # Gets the object being transitioned
96
+ def object
97
+ first.object
98
+ end
99
+
100
+ # Gets the list of actions to run. If configured to skip actions, then
101
+ # this will return an empty collection.
102
+ def actions
103
+ empty? ? [nil] : map {|transition| transition.action}.uniq
104
+ end
105
+
106
+ # Determines whether an event attribute be used to trigger the transitions
107
+ # in this collection or whether the transitions be run directly *outside*
108
+ # of the action.
109
+ def use_event_attributes?
110
+ !skip_actions && !skip_after && actions.all? && actions.length == 1 && first.machine.action_hook?
111
+ end
112
+
113
+ # Resets any information tracked from previous attempts to perform the
114
+ # collection
115
+ def reset
116
+ @results = {}
117
+ @success = false
118
+ end
119
+
120
+ # Runs each transition's callbacks recursively. Once all before callbacks
121
+ # have been executed, the transitions will then be persisted and the
122
+ # configured actions will be run.
123
+ #
124
+ # If any transition fails to run its callbacks, :halt will be thrown.
125
+ def run_callbacks(index = 0, &block)
126
+ if transition = self[index]
127
+ throw :halt unless transition.run_callbacks(:after => !skip_after) do
128
+ run_callbacks(index + 1, &block)
129
+ {:result => results[transition.action], :success => success?}
130
+ end
131
+ else
132
+ persist
133
+ run_actions(&block)
134
+ end
135
+ end
136
+
137
+ # Transitions the current value of the object's states to those specified by
138
+ # each transition
139
+ def persist
140
+ each {|transition| transition.persist}
141
+ end
142
+
143
+ # Runs the actions for each transition. If a block is given method, then it
144
+ # will be called instead of invoking each transition's action.
145
+ #
146
+ # The results of the actions will be used to determine #success?.
147
+ def run_actions
148
+ catch_exceptions do
149
+ @success = if block_given?
150
+ result = yield
151
+ actions.each {|action| results[action] = result}
152
+ !!result
153
+ else
154
+ actions.compact.each {|action| !skip_actions && results[action] = object.send(action)}
155
+ results.values.all?
156
+ end
157
+ end
158
+ end
159
+
160
+ # Rolls back changes made to the object's states via each transition
161
+ def rollback
162
+ each {|transition| transition.rollback}
163
+ end
164
+
165
+ # Wraps the given block with a rescue handler so that any exceptions that
166
+ # occur will automatically result in the transition rolling back any changes
167
+ # that were made to the object involved.
168
+ def catch_exceptions
169
+ begin
170
+ yield
171
+ rescue Exception
172
+ rollback
173
+ raise
174
+ end
175
+ end
176
+
177
+ # Runs a block within a transaction for the object being transitioned. If
178
+ # transactions are disabled, then this is a no-op.
179
+ def within_transaction
180
+ if use_transaction && !empty?
181
+ first.within_transaction do
182
+ yield
183
+ success?
184
+ end
185
+ else
186
+ yield
187
+ end
188
+ end
189
+ end
190
+
191
+ # Represents a collection of transitions that were generated from attribute-
192
+ # based events
193
+ class AttributeTransitionCollection < TransitionCollection
194
+ def initialize(transitions = [], options = {}) #:nodoc:
195
+ super(transitions, {:transaction => false, :actions => false}.merge(options))
196
+ end
197
+
198
+ private
199
+ # Hooks into running transition callbacks so that event / event transition
200
+ # attributes can be properly updated
201
+ def run_callbacks(index = 0)
202
+ if index == 0
203
+ # Clears any traces of the event attribute to prevent it from being
204
+ # evaluated multiple times if actions are nested
205
+ each do |transition|
206
+ transition.machine.write(object, :event, nil)
207
+ transition.machine.write(object, :event_transition, nil)
208
+ end
209
+
210
+ # Rollback only if exceptions occur during before callbacks
211
+ begin
212
+ super
213
+ rescue Exception
214
+ rollback unless @before_run
215
+ raise
216
+ end
217
+
218
+ # Persists transitions on the object if partial transition was successful.
219
+ # This allows us to reference them later to complete the transition with
220
+ # after callbacks.
221
+ each {|transition| transition.machine.write(object, :event_transition, transition)} if skip_after && success?
222
+ else
223
+ super
224
+ end
225
+ end
226
+
227
+ # Tracks that before callbacks have now completed
228
+ def persist
229
+ @before_run = true
230
+ super
231
+ end
232
+
233
+ # Resets callback tracking
234
+ def reset
235
+ super
236
+ @before_run = false
237
+ end
238
+
239
+ # Resets the event attribute so it can be re-evaluated if attempted again
240
+ def rollback
241
+ super
242
+ each {|transition| transition.machine.write(object, :event, transition.event) unless transition.transient?}
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,3 @@
1
+ module StateMachine
2
+ VERSION = '1.2.1'
3
+ end
@@ -0,0 +1,8 @@
1
+ module StateMachine
2
+ # YARD plugin for automated documentation
3
+ module YARD
4
+ end
5
+ end
6
+
7
+ require 'state_machine/yard/handlers'
8
+ require 'state_machine/yard/templates'
@@ -0,0 +1,12 @@
1
+ module StateMachine
2
+ module YARD
3
+ # YARD custom handlers for integrating the state_machine DSL with the
4
+ # YARD documentation system
5
+ module Handlers
6
+ end
7
+ end
8
+ end
9
+
10
+ Dir["#{File.dirname(__FILE__)}/handlers/*.rb"].sort.each do |path|
11
+ require "state_machine/yard/handlers/#{File.basename(path)}"
12
+ end
@@ -0,0 +1,32 @@
1
+ module StateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes nodes
5
+ class Base < ::YARD::Handlers::Ruby::Base
6
+ private
7
+ # Extracts the value from the node as either a string or symbol
8
+ def extract_node_name(ast)
9
+ case ast.type
10
+ when :symbol_literal
11
+ ast.jump(:ident).source.to_sym
12
+ when :string_literal
13
+ ast.jump(:tstring_content).source
14
+ else
15
+ nil
16
+ end
17
+ end
18
+
19
+ # Extracts the values from the node as either strings or symbols.
20
+ # If the node isn't an array, it'll be converted to an array.
21
+ def extract_node_names(ast, convert_to_array = true)
22
+ if [nil, :array].include?(ast.type)
23
+ ast.children.map {|child| extract_node_name(child)}
24
+ else
25
+ node_name = extract_node_name(ast)
26
+ convert_to_array ? [node_name] : node_name
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module StateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes #event
5
+ class Event < Base
6
+ handles method_call(:event)
7
+
8
+ def process
9
+ if owner.is_a?(StateMachine::Machine)
10
+ handler = self
11
+ statement = self.statement
12
+ names = extract_node_names(statement.parameters(false))
13
+
14
+ names.each do |name|
15
+ owner.event(name) do
16
+ # Parse the block
17
+ handler.parse_block(statement.last.last, :owner => self)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,344 @@
1
+ require 'tempfile'
2
+
3
+ module StateMachine
4
+ module YARD
5
+ module Handlers
6
+ # Handles and processes #state_machine
7
+ class Machine < Base
8
+ handles method_call(:state_machine)
9
+ namespace_only
10
+
11
+ # The generated state machine
12
+ attr_reader :machine
13
+
14
+ def process
15
+ # Cross-file storage for state machines
16
+ globals.state_machines ||= Hash.new {|h, k| h[k] = {}}
17
+ namespace['state_machines'] ||= {}
18
+
19
+ # Create new machine
20
+ klass = inherited_machine ? Class.new(inherited_machine.owner_class) : Class.new { extend StateMachine::MacroMethods }
21
+ @machine = klass.state_machine(name, options) {}
22
+
23
+ # Track the state machine
24
+ globals.state_machines[namespace.name][name] = machine
25
+ namespace['state_machines'][name] = {:name => name, :description => statement.docstring}
26
+
27
+ # Parse the block
28
+ parse_block(statement.last.last, :owner => machine)
29
+
30
+ # Draw the machine for reference in the template
31
+ file = Tempfile.new(['state_machine', '.png'])
32
+ begin
33
+ if machine.draw(:name => File.basename(file.path, '.png'), :path => File.dirname(file.path), :orientation => 'landscape')
34
+ namespace['state_machines'][name][:image] = file.read
35
+ end
36
+ ensure
37
+ # Clean up tempfile
38
+ file.close
39
+ file.unlink
40
+ end
41
+
42
+ # Define auto-generated methods
43
+ define_macro_methods
44
+ define_state_methods
45
+ define_event_methods
46
+ end
47
+
48
+ protected
49
+ # Extracts the machine name's
50
+ def name
51
+ @name ||= begin
52
+ ast = statement.parameters.first
53
+ if ast && [:symbol_literal, :string_literal].include?(ast.type)
54
+ extract_node_name(ast)
55
+ else
56
+ :state
57
+ end
58
+ end
59
+ end
60
+
61
+ # Extracts the machine options. Note that this will only extract a
62
+ # subset of the options supported.
63
+ def options
64
+ @options ||= begin
65
+ options = {}
66
+ ast = statement.parameters(false).last
67
+
68
+ if !inherited_machine && ast && ![:symbol_literal, :string_literal].include?(ast.type)
69
+ ast.children.each do |assoc|
70
+ # Only extract important options
71
+ key = extract_node_name(assoc[0])
72
+ next unless [:initial, :attribute, :namespace, :action].include?(key)
73
+
74
+ value = extract_node_name(assoc[1])
75
+ options[key] = value
76
+ end
77
+ end
78
+
79
+ options
80
+ end
81
+ end
82
+
83
+ # Gets the machine that was inherited from a superclass. This also
84
+ # ensures each ancestor has been loaded prior to looking up their definitions.
85
+ def inherited_machine
86
+ @inherited_machine ||= begin
87
+ namespace.inheritance_tree.each do |ancestor|
88
+ begin
89
+ ensure_loaded!(ancestor)
90
+ rescue ::YARD::Handlers::NamespaceMissingError
91
+ # Ignore: just means that we can't access an ancestor
92
+ end
93
+ end
94
+
95
+ # Find the first ancestor that has the machine
96
+ loaded_superclasses.detect do |superclass|
97
+ if superclass != namespace
98
+ machine = globals.state_machines[superclass.name][name]
99
+ break machine if machine
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # Gets members of this class's superclasses have already been loaded
106
+ # by YARD
107
+ def loaded_superclasses
108
+ namespace.inheritance_tree.select {|ancestor| ancestor.is_a?(::YARD::CodeObjects::ClassObject)}
109
+ end
110
+
111
+ # Gets a list of all attributes for the current class, including those
112
+ # that are inherited
113
+ def instance_attributes
114
+ attributes = {}
115
+ loaded_superclasses.each {|superclass| attributes.merge!(superclass.instance_attributes)}
116
+ attributes
117
+ end
118
+
119
+ # Gets the type of ORM integration being used based on the list of
120
+ # ancestors (including mixins)
121
+ def integration
122
+ @integration ||= Integrations.match_ancestors(namespace.inheritance_tree(true).map {|ancestor| ancestor.path})
123
+ end
124
+
125
+ # Gets the class type being used to define states. Default is "Symbol".
126
+ def state_type
127
+ @state_type ||= machine.states.any? ? machine.states.map {|state| state.name}.compact.first.class.to_s : 'Symbol'
128
+ end
129
+
130
+ # Gets the class type being used to define events. Default is "Symbol".
131
+ def event_type
132
+ @event_type ||= machine.events.any? ? machine.events.first.name.class.to_s : 'Symbol'
133
+ end
134
+
135
+ # Defines auto-generated macro methods for the given machine
136
+ def define_macro_methods
137
+ return if inherited_machine
138
+
139
+ # Human state name lookup
140
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}", :class))
141
+ m.docstring = [
142
+ "Gets the humanized name for the given state.",
143
+ "@param [#{state_type}] state The state to look up",
144
+ "@return [String] The human state name"
145
+ ]
146
+ m.parameters = ["state"]
147
+
148
+ # Human event name lookup
149
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:event_name)}", :class))
150
+ m.docstring = [
151
+ "Gets the humanized name for the given event.",
152
+ "@param [#{event_type}] event The event to look up",
153
+ "@return [String] The human event name"
154
+ ]
155
+ m.parameters = ["event"]
156
+
157
+ # Only register attributes when the accessor isn't explicitly defined
158
+ # by the class / superclass *and* isn't defined by inference from the
159
+ # ORM being used
160
+ unless integration || instance_attributes.include?(machine.attribute.to_sym)
161
+ attribute = machine.attribute
162
+ namespace.attributes[:instance][attribute] = {}
163
+
164
+ # Machine attribute getter
165
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
166
+ namespace.attributes[:instance][attribute][:read] = m
167
+ m.docstring = [
168
+ "Gets the current attribute value for the machine",
169
+ "@return The attribute value"
170
+ ]
171
+
172
+ # Machine attribute setter
173
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
174
+ namespace.attributes[:instance][attribute][:write] = m
175
+ m.docstring = [
176
+ "Sets the current value for the machine",
177
+ "@param new_#{attribute} The new value to set"
178
+ ]
179
+ m.parameters = ["new_#{attribute}"]
180
+ end
181
+
182
+ if integration && integration.defaults[:action] && !options.include?(:action) || options[:action]
183
+ attribute = "#{machine.name}_event"
184
+ namespace.attributes[:instance][attribute] = {}
185
+
186
+ # Machine event attribute getter
187
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
188
+ namespace.attributes[:instance][attribute][:read] = m
189
+ m.docstring = [
190
+ "Gets the current event attribute value for the machine",
191
+ "@return The event attribute value"
192
+ ]
193
+
194
+ # Machine event attribute setter
195
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
196
+ namespace.attributes[:instance][attribute][:write] = m
197
+ m.docstring = [
198
+ "Sets the current value for the machine",
199
+ "@param new_#{attribute} The new value to set"
200
+ ]
201
+ m.parameters = ["new_#{attribute}"]
202
+ end
203
+
204
+ # Presence query
205
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{machine.name}?"))
206
+ m.docstring = [
207
+ "Checks the given state name against the current state.",
208
+ "@param [#{state_type}] state_name The name of the state to check",
209
+ "@return [Boolean] True if they are the same state, otherwise false",
210
+ "@raise [IndexError] If the state name is invalid"
211
+ ]
212
+ m.parameters = ["state_name"]
213
+
214
+ # Internal state name
215
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:name)))
216
+ m.docstring = [
217
+ "Gets the internal name of the state for the current value.",
218
+ "@return [#{state_type}] The internal name of the state"
219
+ ]
220
+
221
+ # Human state name
222
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}"))
223
+ m.docstring = [
224
+ "Gets the human-readable name of the state for the current value.",
225
+ "@return [String] The human-readable state name"
226
+ ]
227
+
228
+ # Available events
229
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:events)))
230
+ m.docstring = [
231
+ "Gets the list of events that can be fired on the current #{machine.name} (uses the *unqualified* event names)",
232
+ "@param [Hash] requirements The transition requirements to test against",
233
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
234
+ "@option requirements [#{state_type}] :to One or more target states",
235
+ "@option requirements [#{event_type}] :on One or more events that fire the transition",
236
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
237
+ "@return [Array<#{event_type}>] The list of event names"
238
+ ]
239
+ m.parameters = [["requirements", "{}"]]
240
+
241
+ # Available transitions
242
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:transitions)))
243
+ m.docstring = [
244
+ "Gets the list of transitions that can be made for the current #{machine.name}",
245
+ "@param [Hash] requirements The transition requirements to test against",
246
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
247
+ "@option requirements [#{state_type}] :to One or more target states",
248
+ "@option requirements [#{event_type}] :on One or more events that fire the transition",
249
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
250
+ "@return [Array<StateMachine::Transition>] The available transitions"
251
+ ]
252
+ m.parameters = [["requirements", "{}"]]
253
+
254
+ # Available transition paths
255
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:paths)))
256
+ m.docstring = [
257
+ "Gets the list of sequences of transitions that can be run for the current #{machine.name}",
258
+ "@param [Hash] requirements The transition requirements to test against",
259
+ "@option requirements [#{state_type}] :from (the current state) The initial state",
260
+ "@option requirements [#{state_type}] :to The target state",
261
+ "@option requirements [Boolean] :deep Whether to enable deep searches for the target state",
262
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
263
+ "@return [StateMachine::PathCollection] The collection of paths"
264
+ ]
265
+ m.parameters = [["requirements", "{}"]]
266
+
267
+ # Generic event fire
268
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "fire_#{machine.attribute(:event)}"))
269
+ m.docstring = [
270
+ "Fires an arbitrary #{machine.name} event with the given argument list",
271
+ "@param [#{event_type}] event The name of the event to fire",
272
+ "@param args Optional arguments to include in the transition",
273
+ "@return [Boolean] +true+ if the event succeeds, otherwise +false+"
274
+ ]
275
+ m.parameters = ["event", "*args"]
276
+ end
277
+
278
+ # Defines auto-generated event methods for the given machine
279
+ def define_event_methods
280
+ machine.events.each do |event|
281
+ next if inherited_machine && inherited_machine.events[event.name]
282
+
283
+ # Event query
284
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "can_#{event.qualified_name}?"))
285
+ m.docstring = [
286
+ "Checks whether #{event.name.inspect} can be fired.",
287
+ "@param [Hash] requirements The transition requirements to test against",
288
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
289
+ "@option requirements [#{state_type}] :to One or more target states",
290
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
291
+ "@return [Boolean] +true+ if #{event.name.inspect} can be fired, otherwise +false+"
292
+ ]
293
+ m.parameters = [["requirements", "{}"]]
294
+
295
+ # Event transition
296
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}_transition"))
297
+ m.docstring = [
298
+ "Gets the next transition that would be performed if #{event.name.inspect} were to be fired.",
299
+ "@param [Hash] requirements The transition requirements to test against",
300
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
301
+ "@option requirements [#{state_type}] :to One or more target states",
302
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
303
+ "@return [StateMachine::Transition] The transition that would be performed or +nil+"
304
+ ]
305
+ m.parameters = [["requirements", "{}"]]
306
+
307
+ # Fire event
308
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, event.qualified_name))
309
+ m.docstring = [
310
+ "Fires the #{event.name.inspect} event.",
311
+ "@param [Array] args Optional arguments to include in transition callbacks",
312
+ "@return [Boolean] +true+ if the transition succeeds, otherwise +false+"
313
+ ]
314
+ m.parameters = ["*args"]
315
+
316
+ # Fire event (raises exception)
317
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}!"))
318
+ m.docstring = [
319
+ "Fires the #{event.name.inspect} event, raising an exception if it fails.",
320
+ "@param [Array] args Optional arguments to include in transition callbacks",
321
+ "@return [Boolean] +true+ if the transition succeeds",
322
+ "@raise [StateMachine::InvalidTransition] If the transition fails"
323
+ ]
324
+ m.parameters = ["*args"]
325
+ end
326
+ end
327
+
328
+ # Defines auto-generated state methods for the given machine
329
+ def define_state_methods
330
+ machine.states.each do |state|
331
+ next if inherited_machine && inherited_machine.states[state.name] || !state.name
332
+
333
+ # State query
334
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{state.qualified_name}?"))
335
+ m.docstring = [
336
+ "Checks whether #{state.name.inspect} is the current state.",
337
+ "@return [Boolean] +true+ if this is the current state, otherwise +false+"
338
+ ]
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end