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,4 @@
1
+ filename = "#{File.dirname(__FILE__)}/../active_model/locale.rb"
2
+ translations = eval(IO.read(File.expand_path(filename)), binding, filename)
3
+ translations[:en][:mongo_mapper] = translations[:en].delete(:activemodel)
4
+ translations
@@ -0,0 +1,89 @@
1
+ module StateMachine
2
+ module Integrations #:nodoc:
3
+ module MongoMapper
4
+ version '0.5.x - 0.6.x' do
5
+ def self.active?
6
+ !defined?(::MongoMapper::Plugins)
7
+ end
8
+
9
+ def filter_attributes(object, attributes)
10
+ attributes
11
+ end
12
+
13
+ def define_state_initializer
14
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
15
+ def initialize(*args)
16
+ attrs, * = args
17
+ attrs && attrs.stringify_keys.key?('_id') ? super : self.class.state_machines.initialize_states(self, :static => :force) { super }
18
+ end
19
+ end_eval
20
+ end
21
+ end
22
+
23
+ version '0.5.x - 0.7.x' do
24
+ def self.active?
25
+ !defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-7]\./
26
+ end
27
+
28
+ def define_scope(name, scope)
29
+ lambda {|model, values| model.all(scope.call(values))}
30
+ end
31
+ end
32
+
33
+ version '0.5.x - 0.8.x' do
34
+ def self.active?
35
+ !defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-8]\./
36
+ end
37
+
38
+ def invalidate(object, attribute, message, values = [])
39
+ object.errors.add(self.attribute(attribute), generate_message(message, values))
40
+ end
41
+
42
+ def define_state_accessor
43
+ owner_class.key(attribute, String) unless owner_class.keys.include?(attribute)
44
+
45
+ name = self.name
46
+ owner_class.validates_each(attribute, :logic => lambda {|*|
47
+ machine = self.class.state_machine(name)
48
+ machine.invalidate(self, :state, :invalid) unless machine.states.match(self)
49
+ })
50
+ end
51
+
52
+ def load_locale
53
+ end
54
+
55
+ def supports_observers?
56
+ false
57
+ end
58
+
59
+ def supports_validations?
60
+ true
61
+ end
62
+
63
+ def callback_terminator
64
+ end
65
+
66
+ def translate(klass, key, value)
67
+ value.to_s.humanize.downcase
68
+ end
69
+ end
70
+
71
+ version '0.7.x - 0.8.3' do
72
+ def self.active?
73
+ # Only 0.8.x and up has a Version string available, so Plugins is used
74
+ # to detect when 0.7.x is active
75
+ defined?(::MongoMapper::Plugins) && (!defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.(7|8\.[0-3])\./)
76
+ end
77
+
78
+ def define_state_initializer
79
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
80
+ def initialize(*args)
81
+ from_db = args[1]
82
+ from_db ? super : self.class.state_machines.initialize_states(self, :static => :force) { super }
83
+ end
84
+ end_eval
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,465 @@
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 update_document(*)
430
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
431
+ end
432
+
433
+ def upsert(*)
434
+ self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
435
+ end
436
+ end_eval
437
+ else
438
+ super
439
+ end
440
+ end
441
+
442
+ # Runs state events around the machine's :save action
443
+ def around_save(object)
444
+ object.class.state_machines.transitions(object, action).perform { yield }
445
+ end
446
+
447
+ # Creates a scope for finding records *with* a particular state or
448
+ # states for the attribute
449
+ def create_with_scope(name)
450
+ define_scope(name, lambda {|values| {attribute => {'$in' => values}}})
451
+ end
452
+
453
+ # Creates a scope for finding records *without* a particular state or
454
+ # states for the attribute
455
+ def create_without_scope(name)
456
+ define_scope(name, lambda {|values| {attribute => {'$nin' => values}}})
457
+ end
458
+
459
+ # Defines a new scope with the given name
460
+ def define_scope(name, scope)
461
+ lambda {|model, values| model.criteria.where(scope.call(values))}
462
+ end
463
+ end
464
+ end
465
+ end