state_machine_updated_for_ruby_3_2 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +72 -0
  4. data/.yardopts +5 -0
  5. data/Appraisals +491 -0
  6. data/CHANGELOG.md +506 -0
  7. data/Dockerfile +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +20 -0
  10. data/README.md +1244 -0
  11. data/Rakefile +41 -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/active_model_3.0.0.gemfile +7 -0
  58. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  59. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  60. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  61. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  62. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  63. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  64. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  65. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  66. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  67. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  68. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  69. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  70. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  71. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  72. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  73. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  74. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  75. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  76. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  77. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  78. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  79. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  80. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  81. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  82. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  83. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  84. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  85. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  86. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  87. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  88. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  89. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  90. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  91. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  92. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  93. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  94. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  95. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  96. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  97. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  98. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  99. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  100. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  101. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  102. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  103. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  104. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  105. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  106. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  107. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  108. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  109. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  110. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  111. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  112. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  113. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  114. data/gemfiles/default.gemfile +7 -0
  115. data/gemfiles/default.gemfile.lock +27 -0
  116. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  117. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  118. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  119. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  120. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  121. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  122. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  123. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  124. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  125. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  126. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  127. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  128. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  129. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  130. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  131. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  132. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  133. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  134. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  135. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  136. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  137. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  138. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  139. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  140. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  141. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  142. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  143. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  144. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  145. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  146. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  147. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  148. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  149. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  150. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  151. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  152. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  153. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  154. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  155. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  156. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  157. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  158. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  159. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  160. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  161. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  162. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  163. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  164. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  165. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  166. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  167. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  168. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  169. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  170. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  171. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  172. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  173. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  174. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  175. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  176. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  177. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  178. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  179. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  180. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  181. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  182. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  183. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  184. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  185. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  186. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  187. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  188. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  189. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  190. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  191. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  192. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  193. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  194. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  195. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  196. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  197. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  198. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  199. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  200. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  201. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  202. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  203. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  204. data/init.rb +1 -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/class/state_machine.rb +5 -0
  210. data/lib/state_machine/core_ext.rb +2 -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/merb.rb +1 -0
  219. data/lib/state_machine/initializers/rails.rb +25 -0
  220. data/lib/state_machine/initializers.rb +4 -0
  221. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  222. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  223. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  224. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  225. data/lib/state_machine/integrations/active_model.rb +585 -0
  226. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  227. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  228. data/lib/state_machine/integrations/active_record.rb +548 -0
  229. data/lib/state_machine/integrations/base.rb +100 -0
  230. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  231. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  232. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  233. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  234. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  235. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  236. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  237. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  238. data/lib/state_machine/integrations/mongoid.rb +461 -0
  239. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  240. data/lib/state_machine/integrations/sequel.rb +486 -0
  241. data/lib/state_machine/integrations.rb +121 -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/handlers/base.rb +32 -0
  257. data/lib/state_machine/yard/handlers/event.rb +25 -0
  258. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  259. data/lib/state_machine/yard/handlers/state.rb +25 -0
  260. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  261. data/lib/state_machine/yard/handlers.rb +12 -0
  262. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  263. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  264. data/lib/state_machine/yard/templates.rb +3 -0
  265. data/lib/state_machine/yard.rb +8 -0
  266. data/lib/state_machine.rb +8 -0
  267. data/lib/tasks/state_machine.rake +1 -0
  268. data/lib/tasks/state_machine.rb +30 -0
  269. data/lib/yard-state_machine.rb +2 -0
  270. data/state_machine.gemspec +23 -0
  271. data/test/files/en.yml +17 -0
  272. data/test/files/switch.rb +15 -0
  273. data/test/functional/state_machine_test.rb +1066 -0
  274. data/test/test_helper.rb +7 -0
  275. data/test/unit/assertions_test.rb +40 -0
  276. data/test/unit/branch_test.rb +969 -0
  277. data/test/unit/callback_test.rb +704 -0
  278. data/test/unit/error_test.rb +43 -0
  279. data/test/unit/eval_helpers_test.rb +272 -0
  280. data/test/unit/event_collection_test.rb +398 -0
  281. data/test/unit/event_test.rb +1196 -0
  282. data/test/unit/graph_test.rb +98 -0
  283. data/test/unit/helper_module_test.rb +17 -0
  284. data/test/unit/integrations/active_model_test.rb +1245 -0
  285. data/test/unit/integrations/active_record_test.rb +2551 -0
  286. data/test/unit/integrations/base_test.rb +104 -0
  287. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  288. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  289. data/test/unit/integrations/mongoid_test.rb +2309 -0
  290. data/test/unit/integrations/sequel_test.rb +1896 -0
  291. data/test/unit/integrations_test.rb +83 -0
  292. data/test/unit/invalid_event_test.rb +20 -0
  293. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  294. data/test/unit/invalid_transition_test.rb +115 -0
  295. data/test/unit/machine_collection_test.rb +603 -0
  296. data/test/unit/machine_test.rb +3407 -0
  297. data/test/unit/matcher_helpers_test.rb +37 -0
  298. data/test/unit/matcher_test.rb +155 -0
  299. data/test/unit/node_collection_test.rb +362 -0
  300. data/test/unit/path_collection_test.rb +266 -0
  301. data/test/unit/path_test.rb +485 -0
  302. data/test/unit/state_collection_test.rb +352 -0
  303. data/test/unit/state_context_test.rb +441 -0
  304. data/test/unit/state_machine_test.rb +31 -0
  305. data/test/unit/state_test.rb +1101 -0
  306. data/test/unit/transition_collection_test.rb +2168 -0
  307. data/test/unit/transition_test.rb +1558 -0
  308. metadata +450 -0
@@ -0,0 +1,461 @@
1
+ module StateMachine
2
+ module Integrations #:nodoc:
3
+ # Adds support for integrating state machines with Mongoid models.
4
+ #
5
+ # == Examples
6
+ #
7
+ # Below is an example of a simple state machine defined within a
8
+ # Mongoid model:
9
+ #
10
+ # class Vehicle
11
+ # include Mongoid::Document
12
+ #
13
+ # state_machine :initial => :parked do
14
+ # event :ignite do
15
+ # transition :parked => :idling
16
+ # end
17
+ # end
18
+ # end
19
+ #
20
+ # The examples in the sections below will use the above class as a
21
+ # reference.
22
+ #
23
+ # == Actions
24
+ #
25
+ # By default, the action that will be invoked when a state is transitioned
26
+ # is the +save+ action. This will cause the record to save the changes
27
+ # made to the state machine's attribute. *Note* that if any other changes
28
+ # were made to the record prior to transition, then those changes will
29
+ # be saved as well.
30
+ #
31
+ # For example,
32
+ #
33
+ # vehicle = Vehicle.create # => #<Vehicle _id: 4d70e028b876bb54d9000003, name: nil, state: "parked">
34
+ # vehicle.name = 'Ford Explorer'
35
+ # vehicle.ignite # => true
36
+ # vehicle.reload # => #<Vehicle _id: 4d70e028b876bb54d9000003, name: "Ford Explorer", state: "idling">
37
+ #
38
+ # == Events
39
+ #
40
+ # As described in StateMachine::InstanceMethods#state_machine, event
41
+ # attributes are created for every machine that allow transitions to be
42
+ # performed automatically when the object's action (in this case, :save)
43
+ # is called.
44
+ #
45
+ # In Mongoid, these automated events are run in the following order:
46
+ # * before validation - Run before callbacks and persist new states, then validate
47
+ # * before save - If validation was skipped, run before callbacks and persist new states, then save
48
+ # * after save - Run after callbacks
49
+ #
50
+ # For example,
51
+ #
52
+ # vehicle = Vehicle.create # => #<Vehicle _id: 4d70e028b876bb54d9000003, name: nil, state: "parked">
53
+ # vehicle.state_event # => nil
54
+ # vehicle.state_event = 'invalid'
55
+ # vehicle.valid? # => false
56
+ # vehicle.errors.full_messages # => ["State event is invalid"]
57
+ #
58
+ # vehicle.state_event = 'ignite'
59
+ # vehicle.valid? # => true
60
+ # vehicle.save # => true
61
+ # vehicle.state # => "idling"
62
+ # vehicle.state_event # => nil
63
+ #
64
+ # Note that this can also be done on a mass-assignment basis:
65
+ #
66
+ # vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle _id: 4d70e028b876bb54d9000003, name: nil, state: "idling">
67
+ # vehicle.state # => "idling"
68
+ #
69
+ # This technique is always used for transitioning states when the +save+
70
+ # action (which is the default) is configured for the machine.
71
+ #
72
+ # === Security implications
73
+ #
74
+ # Beware that public event attributes mean that events can be fired
75
+ # whenever mass-assignment is being used. If you want to prevent malicious
76
+ # users from tampering with events through URLs / forms, the attribute
77
+ # should be protected like so:
78
+ #
79
+ # class Vehicle
80
+ # include Mongoid::Document
81
+ #
82
+ # attr_protected :state_event
83
+ # # attr_accessible ... # Alternative technique
84
+ #
85
+ # state_machine do
86
+ # ...
87
+ # end
88
+ # end
89
+ #
90
+ # If you want to only have *some* events be able to fire via mass-assignment,
91
+ # you can build two state machines (one public and one protected) like so:
92
+ #
93
+ # class Vehicle
94
+ # include Mongoid::Document
95
+ #
96
+ # attr_protected :state_event # Prevent access to events in the first machine
97
+ #
98
+ # state_machine do
99
+ # # Define private events here
100
+ # end
101
+ #
102
+ # # Public machine targets the same state as the private machine
103
+ # state_machine :public_state, :attribute => :state do
104
+ # # Define public events here
105
+ # end
106
+ # end
107
+ #
108
+ # == Validations
109
+ #
110
+ # As mentioned in StateMachine::Machine#state, you can define behaviors,
111
+ # like validations, that only execute for certain states. One *important*
112
+ # caveat here is that, due to a constraint in Mongoid's validation
113
+ # framework, custom validators will not work as expected when defined to run
114
+ # in multiple states. For example:
115
+ #
116
+ # class Vehicle
117
+ # include Mongoid::Document
118
+ #
119
+ # state_machine do
120
+ # ...
121
+ # state :first_gear, :second_gear do
122
+ # validate :speed_is_legal
123
+ # end
124
+ # end
125
+ # end
126
+ #
127
+ # In this case, the <tt>:speed_is_legal</tt> validation will only get run
128
+ # for the <tt>:second_gear</tt> state. To avoid this, you can define your
129
+ # custom validation like so:
130
+ #
131
+ # class Vehicle
132
+ # include Mongoid::Document
133
+ #
134
+ # state_machine do
135
+ # ...
136
+ # state :first_gear, :second_gear do
137
+ # validate {|vehicle| vehicle.speed_is_legal}
138
+ # end
139
+ # end
140
+ # end
141
+ #
142
+ # == Validation errors
143
+ #
144
+ # If an event fails to successfully fire because there are no matching
145
+ # transitions for the current record, a validation error is added to the
146
+ # record's state attribute to help in determining why it failed and for
147
+ # reporting via the UI.
148
+ #
149
+ # For example,
150
+ #
151
+ # vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle _id: 4d70e028b876bb54d9000003, name: nil, state: "idling">
152
+ # vehicle.ignite # => false
153
+ # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
154
+ #
155
+ # If an event fails to fire because of a validation error on the record and
156
+ # *not* because a matching transition was not available, no error messages
157
+ # will be added to the state attribute.
158
+ #
159
+ # In addition, if you're using the <tt>ignite!</tt> version of the event,
160
+ # then the failure reason (such as the current validation errors) will be
161
+ # included in the exception that gets raised when the event fails. For
162
+ # example, assuming there's a validation on a field called +name+ on the class:
163
+ #
164
+ # vehicle = Vehicle.new
165
+ # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
166
+ #
167
+ # == Scopes
168
+ #
169
+ # To assist in filtering models with specific states, a series of basic
170
+ # scopes are defined on the model for finding records with or without a
171
+ # particular set of states.
172
+ #
173
+ # These scopes are essentially the functional equivalent of the following
174
+ # definitions:
175
+ #
176
+ # class Vehicle
177
+ # include Mongoid::Document
178
+ #
179
+ # scope :with_states, lambda {|*states| where(:state => {'$in' => states})}
180
+ # # with_states also aliased to with_state
181
+ #
182
+ # scope :without_states, lambda {|*states| where(:state => {'$nin' => states})}
183
+ # # without_states also aliased to without_state
184
+ # end
185
+ #
186
+ # *Note*, however, that the states are converted to their stored values
187
+ # before being passed into the query.
188
+ #
189
+ # Because of the way named scopes work in Mongoid, they *cannot* be
190
+ # chained.
191
+ #
192
+ # Note that states can also be referenced by the string version of their
193
+ # name:
194
+ #
195
+ # Vehicle.with_state('parked')
196
+ #
197
+ # == Callbacks
198
+ #
199
+ # All before/after transition callbacks defined for Mongoid models
200
+ # behave in the same way that other Mongoid callbacks behave. The
201
+ # object involved in the transition is passed in as an argument.
202
+ #
203
+ # For example,
204
+ #
205
+ # class Vehicle
206
+ # include Mongoid::Document
207
+ #
208
+ # state_machine :initial => :parked do
209
+ # before_transition any => :idling do |vehicle|
210
+ # vehicle.put_on_seatbelt
211
+ # end
212
+ #
213
+ # before_transition do |vehicle, transition|
214
+ # # log message
215
+ # end
216
+ #
217
+ # event :ignite do
218
+ # transition :parked => :idling
219
+ # end
220
+ # end
221
+ #
222
+ # def put_on_seatbelt
223
+ # ...
224
+ # end
225
+ # end
226
+ #
227
+ # Note, also, that the transition can be accessed by simply defining
228
+ # additional arguments in the callback block.
229
+ #
230
+ # == Observers
231
+ #
232
+ # In addition to support for Mongoid-like hooks, there is additional support
233
+ # for Mongoid observers. Because of the way Mongoid observers are designed,
234
+ # there is less flexibility around the specific transitions that can be
235
+ # hooked in. However, a large number of hooks *are* supported. For
236
+ # example, if a transition for a record's +state+ attribute changes the
237
+ # state from +parked+ to +idling+ via the +ignite+ event, the following
238
+ # observer methods are supported:
239
+ # * before/after/after_failure_to-_ignite_from_parked_to_idling
240
+ # * before/after/after_failure_to-_ignite_from_parked
241
+ # * before/after/after_failure_to-_ignite_to_idling
242
+ # * before/after/after_failure_to-_ignite
243
+ # * before/after/after_failure_to-_transition_state_from_parked_to_idling
244
+ # * before/after/after_failure_to-_transition_state_from_parked
245
+ # * before/after/after_failure_to-_transition_state_to_idling
246
+ # * before/after/after_failure_to-_transition_state
247
+ # * before/after/after_failure_to-_transition
248
+ #
249
+ # The following class shows an example of some of these hooks:
250
+ #
251
+ # class VehicleObserver < Mongoid::Observer
252
+ # def before_save(vehicle)
253
+ # # log message
254
+ # end
255
+ #
256
+ # # Callback for :ignite event *before* the transition is performed
257
+ # def before_ignite(vehicle, transition)
258
+ # # log message
259
+ # end
260
+ #
261
+ # # Callback for :ignite event *after* the transition has been performed
262
+ # def after_ignite(vehicle, transition)
263
+ # # put on seatbelt
264
+ # end
265
+ #
266
+ # # Generic transition callback *before* the transition is performed
267
+ # def after_transition(vehicle, transition)
268
+ # Audit.log(vehicle, transition)
269
+ # end
270
+ # end
271
+ #
272
+ # More flexible transition callbacks can be defined directly within the
273
+ # model as described in StateMachine::Machine#before_transition
274
+ # and StateMachine::Machine#after_transition.
275
+ #
276
+ # To define a single observer for multiple state machines:
277
+ #
278
+ # class StateMachineObserver < Mongoid::Observer
279
+ # observe Vehicle, Switch, Project
280
+ #
281
+ # def after_transition(record, transition)
282
+ # Audit.log(record, transition)
283
+ # end
284
+ # end
285
+ #
286
+ # === Callback Order
287
+ #
288
+ # Callbacks occur in the following order. Callbacks specific to state_machine
289
+ # are bolded. The remaining callbacks are part of Mongoid.
290
+ #
291
+ # * (-) save
292
+ # * (1) *before_transition*
293
+ # * (-) valid
294
+ # * (2) before_validation
295
+ # * (3) after_validation
296
+ # * (4) before_save
297
+ # * (5) before_create
298
+ # * (6) after_create
299
+ # * (7) after_save
300
+ # * (8) *after_transition*
301
+ #
302
+ # == Internationalization
303
+ #
304
+ # Any error message that is generated from performing invalid transitions
305
+ # can be localized. The following default translations are used:
306
+ #
307
+ # en:
308
+ # mongoid:
309
+ # errors:
310
+ # messages:
311
+ # invalid: "is invalid"
312
+ # # %{value} = attribute value, %{state} = Human state name
313
+ # invalid_event: "cannot transition when %{state}"
314
+ # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
315
+ # invalid_transition: "cannot transition via %{event}"
316
+ #
317
+ # You can override these for a specific model like so:
318
+ #
319
+ # en:
320
+ # mongoid:
321
+ # errors:
322
+ # models:
323
+ # user:
324
+ # invalid: "is not valid"
325
+ #
326
+ # In addition to the above, you can also provide translations for the
327
+ # various states / events in each state machine. Using the Vehicle example,
328
+ # state translations will be looked for using the following keys, where
329
+ # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
330
+ # * <tt>mongoid.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
331
+ # * <tt>mongoid.state_machines.#{model_name}.states.#{state_name}</tt>
332
+ # * <tt>mongoid.state_machines.#{machine_name}.states.#{state_name}</tt>
333
+ # * <tt>mongoid.state_machines.states.#{state_name}</tt>
334
+ #
335
+ # Event translations will be looked for using the following keys, where
336
+ # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
337
+ # * <tt>mongoid.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
338
+ # * <tt>mongoid.state_machines.#{model_name}.events.#{event_name}</tt>
339
+ # * <tt>mongoid.state_machines.#{machine_name}.events.#{event_name}</tt>
340
+ # * <tt>mongoid.state_machines.events.#{event_name}</tt>
341
+ #
342
+ # An example translation configuration might look like so:
343
+ #
344
+ # es:
345
+ # mongoid:
346
+ # state_machines:
347
+ # states:
348
+ # parked: 'estacionado'
349
+ # events:
350
+ # park: 'estacionarse'
351
+ module Mongoid
352
+ include Base
353
+ include ActiveModel
354
+
355
+ require 'state_machine/integrations/mongoid/versions'
356
+
357
+ # The default options to use for state machines using this integration
358
+ @defaults = {:action => :save}
359
+
360
+ # Classes that include Mongoid::Document will automatically use the
361
+ # Mongoid integration.
362
+ def self.matching_ancestors
363
+ %w(Mongoid::Document)
364
+ end
365
+
366
+ def self.extended(base) #:nodoc:
367
+ require 'mongoid/version'
368
+ super
369
+ end
370
+
371
+ protected
372
+ # Only runs validations on the action if using <tt>:save</tt>
373
+ def runs_validations_on_action?
374
+ action == :save
375
+ end
376
+
377
+ # Gets the db default for the machine's attribute
378
+ def owner_class_attribute_default
379
+ attribute_field && attribute_field.default_val
380
+ end
381
+
382
+ # Gets the field for this machine's attribute (if it exists)
383
+ def attribute_field
384
+ owner_class.fields[attribute.to_s] || owner_class.fields[owner_class.aliased_fields[attribute.to_s]]
385
+ end
386
+
387
+ # Defines an initialization hook into the owner class for setting the
388
+ # initial state of the machine *before* any attributes are set on the
389
+ # object
390
+ def define_state_initializer
391
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
392
+ def initialize(*)
393
+ super do |*args|
394
+ self.class.state_machines.initialize_states(self, :static => false)
395
+ yield(*args) if block_given?
396
+ end
397
+ end
398
+
399
+ def apply_pre_processed_defaults
400
+ defaults = {}
401
+ self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false, :to => defaults)
402
+ defaults.each do |attr, value|
403
+ send(:"\#{attr}=", value) unless attributes.include?(attr)
404
+ end
405
+ super
406
+ end
407
+ end_eval
408
+ end
409
+
410
+ # Skips defining reader/writer methods since this is done automatically
411
+ def define_state_accessor
412
+ owner_class.field(attribute, :type => String) unless attribute_field
413
+ super
414
+ end
415
+
416
+ # Uses around callbacks to run state events if using the :save hook
417
+ def define_action_hook
418
+ if action_hook == :save
419
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
420
+ def insert(*)
421
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super.persisted? }
422
+ self
423
+ end
424
+
425
+ def update(*)
426
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
427
+ end
428
+
429
+ def upsert(*)
430
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
431
+ end
432
+ end_eval
433
+ else
434
+ super
435
+ end
436
+ end
437
+
438
+ # Runs state events around the machine's :save action
439
+ def around_save(object)
440
+ object.class.state_machines.transitions(object, action).perform { yield }
441
+ end
442
+
443
+ # Creates a scope for finding records *with* a particular state or
444
+ # states for the attribute
445
+ def create_with_scope(name)
446
+ define_scope(name, lambda {|values| {attribute => {'$in' => values}}})
447
+ end
448
+
449
+ # Creates a scope for finding records *without* a particular state or
450
+ # states for the attribute
451
+ def create_without_scope(name)
452
+ define_scope(name, lambda {|values| {attribute => {'$nin' => values}}})
453
+ end
454
+
455
+ # Defines a new scope with the given name
456
+ def define_scope(name, scope)
457
+ lambda {|model, values| model.criteria.where(scope.call(values))}
458
+ end
459
+ end
460
+ end
461
+ end
@@ -0,0 +1,95 @@
1
+ module StateMachine
2
+ module Integrations #:nodoc:
3
+ module Sequel
4
+ version '2.8.x - 3.23.x' do
5
+ def self.active?
6
+ !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 23
7
+ end
8
+
9
+ def define_state_initializer
10
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
11
+ def initialize(*)
12
+ super do |*args|
13
+ self.class.state_machines.initialize_states(self, :static => false)
14
+ changed_columns.clear
15
+ yield(*args) if block_given?
16
+ end
17
+ end
18
+
19
+ def set(*)
20
+ self.class.state_machines.initialize_states(self, :static => :force, :dynamic => false) if values.empty?
21
+ super
22
+ end
23
+ end_eval
24
+ end
25
+
26
+ def define_validation_hook
27
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
28
+ def valid?(*args)
29
+ opts = args.first.is_a?(Hash) ? args.first : {}
30
+ yielded = false
31
+ result = self.class.state_machines.transitions(self, :save, :after => false).perform do
32
+ yielded = true
33
+ super
34
+ end
35
+
36
+ if yielded || result
37
+ result
38
+ else
39
+ #{handle_validation_failure}
40
+ end
41
+ end
42
+ end_eval
43
+ end
44
+ end
45
+
46
+ version '2.8.x - 3.13.x' do
47
+ def self.active?
48
+ !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 || ::Sequel::MAJOR == 3 && ::Sequel::MINOR <= 13
49
+ end
50
+
51
+ def handle_validation_failure
52
+ 'raise_on_save_failure ? save_failure(:validation) : result'
53
+ end
54
+
55
+ def handle_save_failure
56
+ 'save_failure(:save) if raise_on_save_failure'
57
+ end
58
+ end
59
+
60
+ version '2.8.x - 2.11.x' do
61
+ def self.active?
62
+ !defined?(::Sequel::MAJOR) || ::Sequel::MAJOR == 2 && ::Sequel::MINOR <= 11
63
+ end
64
+
65
+ def load_plugins
66
+ end
67
+
68
+ def load_inflector
69
+ end
70
+
71
+ def model_from_dataset(dataset)
72
+ dataset.model_classes[nil]
73
+ end
74
+
75
+ def define_state_accessor
76
+ name = self.name
77
+ owner_class.validates_each(attribute) do |record, attr, value|
78
+ machine = record.class.state_machine(name)
79
+ machine.invalidate(record, :state, :invalid) unless machine.states.match(record)
80
+ end
81
+ end
82
+ end
83
+
84
+ version '3.14.x - 3.23.x' do
85
+ def self.active?
86
+ defined?(::Sequel::MAJOR) && ::Sequel::MAJOR == 3 && ::Sequel::MINOR >= 14 && ::Sequel::MINOR <= 23
87
+ end
88
+
89
+ def handle_validation_failure
90
+ 'raise_on_failure?(opts) ? raise_hook_failure(:validation) : result'
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end