state_machine_deuxito 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (310) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +8 -0
  4. data/.yardopts +5 -0
  5. data/Appraisals +12 -0
  6. data/CHANGELOG.md +502 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +20 -0
  9. data/README.md +8 -0
  10. data/Rakefile +41 -0
  11. data/TODO.md +2 -0
  12. data/examples/AutoShop_state.png +0 -0
  13. data/examples/Car_state.png +0 -0
  14. data/examples/Gemfile +5 -0
  15. data/examples/Gemfile.lock +14 -0
  16. data/examples/TrafficLight_state.png +0 -0
  17. data/examples/Vehicle_state.png +0 -0
  18. data/examples/auto_shop.rb +13 -0
  19. data/examples/car.rb +21 -0
  20. data/examples/doc/AutoShop.html +2856 -0
  21. data/examples/doc/AutoShop_state.png +0 -0
  22. data/examples/doc/Car.html +919 -0
  23. data/examples/doc/Car_state.png +0 -0
  24. data/examples/doc/TrafficLight.html +2230 -0
  25. data/examples/doc/TrafficLight_state.png +0 -0
  26. data/examples/doc/Vehicle.html +7921 -0
  27. data/examples/doc/Vehicle_state.png +0 -0
  28. data/examples/doc/_index.html +136 -0
  29. data/examples/doc/class_list.html +47 -0
  30. data/examples/doc/css/common.css +1 -0
  31. data/examples/doc/css/full_list.css +55 -0
  32. data/examples/doc/css/style.css +322 -0
  33. data/examples/doc/file_list.html +46 -0
  34. data/examples/doc/frames.html +13 -0
  35. data/examples/doc/index.html +136 -0
  36. data/examples/doc/js/app.js +205 -0
  37. data/examples/doc/js/full_list.js +173 -0
  38. data/examples/doc/js/jquery.js +16 -0
  39. data/examples/doc/method_list.html +734 -0
  40. data/examples/doc/top-level-namespace.html +105 -0
  41. data/examples/merb-rest/controller.rb +51 -0
  42. data/examples/merb-rest/model.rb +28 -0
  43. data/examples/merb-rest/view_edit.html.erb +24 -0
  44. data/examples/merb-rest/view_index.html.erb +23 -0
  45. data/examples/merb-rest/view_new.html.erb +13 -0
  46. data/examples/merb-rest/view_show.html.erb +17 -0
  47. data/examples/rails-rest/controller.rb +43 -0
  48. data/examples/rails-rest/migration.rb +7 -0
  49. data/examples/rails-rest/model.rb +23 -0
  50. data/examples/rails-rest/view__form.html.erb +34 -0
  51. data/examples/rails-rest/view_edit.html.erb +6 -0
  52. data/examples/rails-rest/view_index.html.erb +25 -0
  53. data/examples/rails-rest/view_new.html.erb +5 -0
  54. data/examples/rails-rest/view_show.html.erb +19 -0
  55. data/examples/traffic_light.rb +9 -0
  56. data/examples/vehicle.rb +33 -0
  57. data/gemfiles/4.2.11.1.gemfile +9 -0
  58. data/gemfiles/4.2.11.1.gemfile.lock +57 -0
  59. data/gemfiles/active_model_3.0.0.gemfile +7 -0
  60. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  61. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  62. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  63. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  64. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  65. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  66. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  67. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  68. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  69. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  70. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  71. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  72. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  73. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  74. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  75. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  76. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  77. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  78. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  79. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  80. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  81. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  82. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  83. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  84. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  85. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  86. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  87. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  88. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  89. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  90. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  91. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  92. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  93. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  94. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  95. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  96. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  97. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  98. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  99. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  100. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  101. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  102. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  103. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  104. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  105. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  106. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  107. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  108. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  109. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  110. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  111. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  112. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  113. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  114. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  115. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  116. data/gemfiles/default.gemfile +5 -0
  117. data/gemfiles/default.gemfile.lock +30 -0
  118. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  119. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  120. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  121. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  122. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  123. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  124. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  125. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  126. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  127. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  128. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  129. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  130. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  131. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  132. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  133. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  134. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  135. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  136. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  137. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  138. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  139. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  140. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  141. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  142. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  143. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  144. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  145. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  146. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  147. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  148. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  149. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  150. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  151. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  152. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  153. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  154. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  155. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  156. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  157. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  158. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  159. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  160. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  161. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  162. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  163. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  164. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  165. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  166. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  167. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  168. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  169. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  170. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  171. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  172. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  173. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  174. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  175. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  176. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  177. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  178. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  179. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  180. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  181. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  182. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  183. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  184. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  185. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  186. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  187. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  188. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  189. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  190. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  191. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  192. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  193. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  194. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  195. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  196. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  197. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  198. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  199. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  200. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  201. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  202. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  203. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  204. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  205. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  206. data/init.rb +1 -0
  207. data/lib/state_machine.rb +8 -0
  208. data/lib/state_machine/assertions.rb +36 -0
  209. data/lib/state_machine/branch.rb +225 -0
  210. data/lib/state_machine/callback.rb +236 -0
  211. data/lib/state_machine/core.rb +12 -0
  212. data/lib/state_machine/core_ext.rb +2 -0
  213. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  214. data/lib/state_machine/error.rb +13 -0
  215. data/lib/state_machine/eval_helpers.rb +87 -0
  216. data/lib/state_machine/event.rb +257 -0
  217. data/lib/state_machine/event_collection.rb +141 -0
  218. data/lib/state_machine/extensions.rb +149 -0
  219. data/lib/state_machine/graph.rb +92 -0
  220. data/lib/state_machine/helper_module.rb +17 -0
  221. data/lib/state_machine/initializers.rb +4 -0
  222. data/lib/state_machine/initializers/merb.rb +1 -0
  223. data/lib/state_machine/initializers/rails.rb +25 -0
  224. data/lib/state_machine/integrations.rb +121 -0
  225. data/lib/state_machine/integrations/active_model.rb +585 -0
  226. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  227. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  228. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  229. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  230. data/lib/state_machine/integrations/active_record.rb +552 -0
  231. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  232. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  233. data/lib/state_machine/integrations/base.rb +100 -0
  234. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  235. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  236. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  237. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  238. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  239. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  240. data/lib/state_machine/integrations/mongoid.rb +465 -0
  241. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  242. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  243. data/lib/state_machine/integrations/sequel.rb +486 -0
  244. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  245. data/lib/state_machine/machine.rb +2292 -0
  246. data/lib/state_machine/machine_collection.rb +86 -0
  247. data/lib/state_machine/macro_methods.rb +522 -0
  248. data/lib/state_machine/matcher.rb +123 -0
  249. data/lib/state_machine/matcher_helpers.rb +54 -0
  250. data/lib/state_machine/node_collection.rb +222 -0
  251. data/lib/state_machine/path.rb +120 -0
  252. data/lib/state_machine/path_collection.rb +90 -0
  253. data/lib/state_machine/state.rb +297 -0
  254. data/lib/state_machine/state_collection.rb +112 -0
  255. data/lib/state_machine/state_context.rb +138 -0
  256. data/lib/state_machine/transition.rb +470 -0
  257. data/lib/state_machine/transition_collection.rb +245 -0
  258. data/lib/state_machine/version.rb +3 -0
  259. data/lib/state_machine/yard.rb +8 -0
  260. data/lib/state_machine/yard/handlers.rb +12 -0
  261. data/lib/state_machine/yard/handlers/base.rb +32 -0
  262. data/lib/state_machine/yard/handlers/event.rb +25 -0
  263. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  264. data/lib/state_machine/yard/handlers/state.rb +25 -0
  265. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  266. data/lib/state_machine/yard/templates.rb +3 -0
  267. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  268. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  269. data/lib/tasks/state_machine.rake +1 -0
  270. data/lib/tasks/state_machine.rb +30 -0
  271. data/lib/yard-state_machine.rb +2 -0
  272. data/state_machine.gemspec +21 -0
  273. data/test/files/en.yml +17 -0
  274. data/test/files/switch.rb +15 -0
  275. data/test/functional/state_machine_test.rb +1067 -0
  276. data/test/test_helper.rb +8 -0
  277. data/test/unit/assertions_test.rb +40 -0
  278. data/test/unit/branch_test.rb +969 -0
  279. data/test/unit/callback_test.rb +704 -0
  280. data/test/unit/error_test.rb +43 -0
  281. data/test/unit/eval_helpers_test.rb +270 -0
  282. data/test/unit/event_collection_test.rb +398 -0
  283. data/test/unit/event_test.rb +1196 -0
  284. data/test/unit/graph_test.rb +98 -0
  285. data/test/unit/helper_module_test.rb +17 -0
  286. data/test/unit/integrations/active_model_test.rb +1245 -0
  287. data/test/unit/integrations/active_record_test.rb +2551 -0
  288. data/test/unit/integrations/base_test.rb +104 -0
  289. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  290. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  291. data/test/unit/integrations/mongoid_test.rb +2309 -0
  292. data/test/unit/integrations/sequel_test.rb +1896 -0
  293. data/test/unit/integrations_test.rb +83 -0
  294. data/test/unit/invalid_event_test.rb +20 -0
  295. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  296. data/test/unit/invalid_transition_test.rb +115 -0
  297. data/test/unit/machine_collection_test.rb +603 -0
  298. data/test/unit/machine_test.rb +3431 -0
  299. data/test/unit/matcher_helpers_test.rb +37 -0
  300. data/test/unit/matcher_test.rb +155 -0
  301. data/test/unit/node_collection_test.rb +362 -0
  302. data/test/unit/path_collection_test.rb +266 -0
  303. data/test/unit/path_test.rb +485 -0
  304. data/test/unit/state_collection_test.rb +352 -0
  305. data/test/unit/state_context_test.rb +441 -0
  306. data/test/unit/state_machine_test.rb +31 -0
  307. data/test/unit/state_test.rb +1101 -0
  308. data/test/unit/transition_collection_test.rb +2168 -0
  309. data/test/unit/transition_test.rb +1558 -0
  310. metadata +451 -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 = '0.0.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