aasm 5.0.8

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 (262) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.gitignore +20 -0
  6. data/.travis.yml +100 -0
  7. data/API +34 -0
  8. data/Appraisals +71 -0
  9. data/CHANGELOG.md +431 -0
  10. data/CODE_OF_CONDUCT.md +13 -0
  11. data/CONTRIBUTING.md +24 -0
  12. data/Dockerfile +44 -0
  13. data/Gemfile +6 -0
  14. data/Gemfile.lock_old +151 -0
  15. data/HOWTO +12 -0
  16. data/LICENSE +20 -0
  17. data/PLANNED_CHANGES.md +11 -0
  18. data/README.md +1439 -0
  19. data/README_FROM_VERSION_3_TO_4.md +240 -0
  20. data/Rakefile +31 -0
  21. data/TESTING.md +25 -0
  22. data/aasm.gemspec +37 -0
  23. data/callbacks.txt +51 -0
  24. data/docker-compose.yml +40 -0
  25. data/gemfiles/norails.gemfile +10 -0
  26. data/gemfiles/rails_3.2.gemfile +14 -0
  27. data/gemfiles/rails_4.2.gemfile +16 -0
  28. data/gemfiles/rails_4.2_mongoid_5.gemfile +11 -0
  29. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  30. data/gemfiles/rails_5.0.gemfile +13 -0
  31. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  32. data/gemfiles/rails_5.1.gemfile +13 -0
  33. data/gemfiles/rails_5.2.gemfile +13 -0
  34. data/lib/aasm.rb +23 -0
  35. data/lib/aasm/aasm.rb +208 -0
  36. data/lib/aasm/base.rb +271 -0
  37. data/lib/aasm/configuration.rb +45 -0
  38. data/lib/aasm/core/event.rb +172 -0
  39. data/lib/aasm/core/invoker.rb +129 -0
  40. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  41. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  42. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  43. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  44. data/lib/aasm/core/state.rb +90 -0
  45. data/lib/aasm/core/transition.rb +83 -0
  46. data/lib/aasm/dsl_helper.rb +30 -0
  47. data/lib/aasm/errors.rb +21 -0
  48. data/lib/aasm/instance_base.rb +133 -0
  49. data/lib/aasm/localizer.rb +54 -0
  50. data/lib/aasm/minitest.rb +5 -0
  51. data/lib/aasm/minitest/allow_event.rb +13 -0
  52. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  53. data/lib/aasm/minitest/have_state.rb +13 -0
  54. data/lib/aasm/minitest/transition_from.rb +21 -0
  55. data/lib/aasm/minitest_spec.rb +15 -0
  56. data/lib/aasm/persistence.rb +54 -0
  57. data/lib/aasm/persistence/active_record_persistence.rb +165 -0
  58. data/lib/aasm/persistence/base.rb +78 -0
  59. data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  60. data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  61. data/lib/aasm/persistence/mongoid_persistence.rb +126 -0
  62. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  63. data/lib/aasm/persistence/orm.rb +150 -0
  64. data/lib/aasm/persistence/plain_persistence.rb +26 -0
  65. data/lib/aasm/persistence/redis_persistence.rb +112 -0
  66. data/lib/aasm/persistence/sequel_persistence.rb +83 -0
  67. data/lib/aasm/rspec.rb +5 -0
  68. data/lib/aasm/rspec/allow_event.rb +26 -0
  69. data/lib/aasm/rspec/allow_transition_to.rb +26 -0
  70. data/lib/aasm/rspec/have_state.rb +22 -0
  71. data/lib/aasm/rspec/transition_from.rb +36 -0
  72. data/lib/aasm/state_machine.rb +53 -0
  73. data/lib/aasm/state_machine_store.rb +76 -0
  74. data/lib/aasm/version.rb +3 -0
  75. data/lib/generators/aasm/aasm_generator.rb +16 -0
  76. data/lib/generators/aasm/orm_helpers.rb +41 -0
  77. data/lib/generators/active_record/aasm_generator.rb +40 -0
  78. data/lib/generators/active_record/templates/migration.rb +8 -0
  79. data/lib/generators/active_record/templates/migration_existing.rb +5 -0
  80. data/lib/generators/mongoid/aasm_generator.rb +28 -0
  81. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  82. data/lib/motion-aasm.rb +37 -0
  83. data/spec/database.rb +59 -0
  84. data/spec/database.yml +3 -0
  85. data/spec/en.yml +12 -0
  86. data/spec/en_deprecated_style.yml +10 -0
  87. data/spec/generators/active_record_generator_spec.rb +53 -0
  88. data/spec/generators/mongoid_generator_spec.rb +31 -0
  89. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  90. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
  91. data/spec/models/active_record/complex_active_record_example.rb +37 -0
  92. data/spec/models/active_record/derivate_new_dsl.rb +7 -0
  93. data/spec/models/active_record/false_state.rb +35 -0
  94. data/spec/models/active_record/gate.rb +39 -0
  95. data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
  96. data/spec/models/active_record/invalid_persistor.rb +29 -0
  97. data/spec/models/active_record/localizer_test_model.rb +34 -0
  98. data/spec/models/active_record/no_direct_assignment.rb +21 -0
  99. data/spec/models/active_record/no_scope.rb +21 -0
  100. data/spec/models/active_record/persisted_state.rb +12 -0
  101. data/spec/models/active_record/person.rb +23 -0
  102. data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
  103. data/spec/models/active_record/reader.rb +7 -0
  104. data/spec/models/active_record/readme_job.rb +21 -0
  105. data/spec/models/active_record/silent_persistor.rb +29 -0
  106. data/spec/models/active_record/simple_new_dsl.rb +32 -0
  107. data/spec/models/active_record/thief.rb +29 -0
  108. data/spec/models/active_record/transactor.rb +124 -0
  109. data/spec/models/active_record/transient.rb +6 -0
  110. data/spec/models/active_record/validator.rb +118 -0
  111. data/spec/models/active_record/with_enum.rb +39 -0
  112. data/spec/models/active_record/with_enum_without_column.rb +38 -0
  113. data/spec/models/active_record/with_false_enum.rb +31 -0
  114. data/spec/models/active_record/with_true_enum.rb +39 -0
  115. data/spec/models/active_record/work.rb +3 -0
  116. data/spec/models/active_record/worker.rb +2 -0
  117. data/spec/models/active_record/writer.rb +6 -0
  118. data/spec/models/basic_two_state_machines_example.rb +25 -0
  119. data/spec/models/callbacks/basic.rb +98 -0
  120. data/spec/models/callbacks/basic_multiple.rb +75 -0
  121. data/spec/models/callbacks/guard_within_block.rb +67 -0
  122. data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
  123. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +66 -0
  124. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
  125. data/spec/models/callbacks/private_method.rb +44 -0
  126. data/spec/models/callbacks/private_method_multiple.rb +44 -0
  127. data/spec/models/callbacks/with_args.rb +62 -0
  128. data/spec/models/callbacks/with_args_multiple.rb +61 -0
  129. data/spec/models/callbacks/with_state_arg.rb +34 -0
  130. data/spec/models/callbacks/with_state_arg_multiple.rb +29 -0
  131. data/spec/models/complex_example.rb +222 -0
  132. data/spec/models/conversation.rb +93 -0
  133. data/spec/models/default_state.rb +12 -0
  134. data/spec/models/double_definer.rb +21 -0
  135. data/spec/models/dynamoid/complex_dynamoid_example.rb +37 -0
  136. data/spec/models/dynamoid/dynamoid_multiple.rb +18 -0
  137. data/spec/models/dynamoid/dynamoid_simple.rb +18 -0
  138. data/spec/models/foo.rb +106 -0
  139. data/spec/models/foo_callback_multiple.rb +45 -0
  140. data/spec/models/guard_arguments_check.rb +17 -0
  141. data/spec/models/guard_with_params.rb +24 -0
  142. data/spec/models/guard_with_params_multiple.rb +18 -0
  143. data/spec/models/guardian.rb +58 -0
  144. data/spec/models/guardian_multiple.rb +48 -0
  145. data/spec/models/guardian_without_from_specified.rb +18 -0
  146. data/spec/models/initial_state_proc.rb +31 -0
  147. data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
  148. data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
  149. data/spec/models/mongoid/mongoid_relationships.rb +26 -0
  150. data/spec/models/mongoid/no_scope_mongoid.rb +21 -0
  151. data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
  152. data/spec/models/mongoid/simple_mongoid.rb +23 -0
  153. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +25 -0
  154. data/spec/models/mongoid/validator_mongoid.rb +100 -0
  155. data/spec/models/multi_transitioner.rb +34 -0
  156. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
  157. data/spec/models/namespaced_multiple_example.rb +42 -0
  158. data/spec/models/no_initial_state.rb +25 -0
  159. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  160. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  161. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  162. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  163. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  164. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  165. data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
  166. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  167. data/spec/models/not_auto_loaded/process.rb +21 -0
  168. data/spec/models/parametrised_event.rb +42 -0
  169. data/spec/models/parametrised_event_multiple.rb +29 -0
  170. data/spec/models/process_with_new_dsl.rb +31 -0
  171. data/spec/models/provided_state.rb +24 -0
  172. data/spec/models/redis/complex_redis_example.rb +40 -0
  173. data/spec/models/redis/redis_multiple.rb +20 -0
  174. data/spec/models/redis/redis_simple.rb +20 -0
  175. data/spec/models/sequel/complex_sequel_example.rb +46 -0
  176. data/spec/models/sequel/invalid_persistor.rb +52 -0
  177. data/spec/models/sequel/sequel_multiple.rb +25 -0
  178. data/spec/models/sequel/sequel_simple.rb +26 -0
  179. data/spec/models/sequel/silent_persistor.rb +50 -0
  180. data/spec/models/sequel/transactor.rb +112 -0
  181. data/spec/models/sequel/validator.rb +93 -0
  182. data/spec/models/sequel/worker.rb +12 -0
  183. data/spec/models/silencer.rb +27 -0
  184. data/spec/models/simple_custom_example.rb +53 -0
  185. data/spec/models/simple_example.rb +23 -0
  186. data/spec/models/simple_example_with_guard_args.rb +17 -0
  187. data/spec/models/simple_multiple_example.rb +42 -0
  188. data/spec/models/state_machine_with_failed_event.rb +20 -0
  189. data/spec/models/states_on_one_line_example.rb +8 -0
  190. data/spec/models/sub_class.rb +41 -0
  191. data/spec/models/sub_class_with_more_states.rb +18 -0
  192. data/spec/models/sub_classing.rb +3 -0
  193. data/spec/models/super_class.rb +46 -0
  194. data/spec/models/this_name_better_not_be_in_use.rb +11 -0
  195. data/spec/models/valid_state_name.rb +23 -0
  196. data/spec/spec_helper.rb +36 -0
  197. data/spec/spec_helpers/active_record.rb +8 -0
  198. data/spec/spec_helpers/dynamoid.rb +35 -0
  199. data/spec/spec_helpers/mongoid.rb +26 -0
  200. data/spec/spec_helpers/nobrainer.rb +15 -0
  201. data/spec/spec_helpers/redis.rb +18 -0
  202. data/spec/spec_helpers/remove_warnings.rb +1 -0
  203. data/spec/spec_helpers/sequel.rb +7 -0
  204. data/spec/unit/abstract_class_spec.rb +27 -0
  205. data/spec/unit/api_spec.rb +100 -0
  206. data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
  207. data/spec/unit/callback_multiple_spec.rb +304 -0
  208. data/spec/unit/callbacks_spec.rb +521 -0
  209. data/spec/unit/complex_example_spec.rb +93 -0
  210. data/spec/unit/complex_multiple_example_spec.rb +115 -0
  211. data/spec/unit/edge_cases_spec.rb +16 -0
  212. data/spec/unit/event_multiple_spec.rb +73 -0
  213. data/spec/unit/event_naming_spec.rb +16 -0
  214. data/spec/unit/event_spec.rb +394 -0
  215. data/spec/unit/exception_spec.rb +11 -0
  216. data/spec/unit/guard_arguments_check_spec.rb +9 -0
  217. data/spec/unit/guard_multiple_spec.rb +60 -0
  218. data/spec/unit/guard_spec.rb +89 -0
  219. data/spec/unit/guard_with_params_multiple_spec.rb +10 -0
  220. data/spec/unit/guard_with_params_spec.rb +14 -0
  221. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  222. data/spec/unit/initial_state_multiple_spec.rb +15 -0
  223. data/spec/unit/initial_state_spec.rb +12 -0
  224. data/spec/unit/inspection_multiple_spec.rb +201 -0
  225. data/spec/unit/inspection_spec.rb +149 -0
  226. data/spec/unit/invoker_spec.rb +189 -0
  227. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  228. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  229. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  230. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  231. data/spec/unit/localizer_spec.rb +78 -0
  232. data/spec/unit/memory_leak_spec.rb +38 -0
  233. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
  234. data/spec/unit/namespaced_multiple_example_spec.rb +75 -0
  235. data/spec/unit/new_dsl_spec.rb +12 -0
  236. data/spec/unit/override_warning_spec.rb +94 -0
  237. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +618 -0
  238. data/spec/unit/persistence/active_record_persistence_spec.rb +773 -0
  239. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +135 -0
  240. data/spec/unit/persistence/dynamoid_persistence_spec.rb +84 -0
  241. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +200 -0
  242. data/spec/unit/persistence/mongoid_persistence_spec.rb +165 -0
  243. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  244. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  245. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  246. data/spec/unit/persistence/redis_persistence_spec.rb +53 -0
  247. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +148 -0
  248. data/spec/unit/persistence/sequel_persistence_spec.rb +368 -0
  249. data/spec/unit/readme_spec.rb +41 -0
  250. data/spec/unit/reloading_spec.rb +15 -0
  251. data/spec/unit/rspec_matcher_spec.rb +88 -0
  252. data/spec/unit/simple_custom_example_spec.rb +39 -0
  253. data/spec/unit/simple_example_spec.rb +57 -0
  254. data/spec/unit/simple_multiple_example_spec.rb +91 -0
  255. data/spec/unit/state_spec.rb +89 -0
  256. data/spec/unit/states_on_one_line_example_spec.rb +16 -0
  257. data/spec/unit/subclassing_multiple_spec.rb +74 -0
  258. data/spec/unit/subclassing_spec.rb +46 -0
  259. data/spec/unit/transition_spec.rb +436 -0
  260. data/test/minitest_helper.rb +57 -0
  261. data/test/unit/minitest_matcher_test.rb +80 -0
  262. metadata +609 -0
@@ -0,0 +1,54 @@
1
+ module AASM
2
+ class Localizer
3
+ def human_event_name(klass, event)
4
+ checklist = ancestors_list(klass).inject([]) do |list, ancestor|
5
+ list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
6
+ list
7
+ end
8
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
9
+ end
10
+
11
+ def human_state_name(klass, state)
12
+ checklist = ancestors_list(klass).inject([]) do |list, ancestor|
13
+ list << item_for(klass, state, ancestor)
14
+ list << item_for(klass, state, ancestor, :old_style => true)
15
+ list
16
+ end
17
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
18
+ end
19
+
20
+ private
21
+
22
+ def item_for(klass, state, ancestor, options={})
23
+ separator = options[:old_style] ? '.' : '/'
24
+ :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm(state.state_machine.name).attribute_name}#{separator}#{state}"
25
+ end
26
+
27
+ def translate_queue(checklist)
28
+ (0...(checklist.size-1)).each do |i|
29
+ begin
30
+ return I18n.translate(checklist.shift, :raise => true)
31
+ rescue I18n::MissingTranslationData
32
+ # that's okay
33
+ end
34
+ end
35
+ nil
36
+ end
37
+
38
+ # added for rails 2.x compatibility
39
+ def i18n_scope(klass)
40
+ klass.respond_to?(:i18n_scope) ? klass.i18n_scope : :activerecord
41
+ end
42
+
43
+ # added for rails < 3.0.3 compatibility
44
+ def i18n_klass(klass)
45
+ klass.model_name.respond_to?(:i18n_key) ? klass.model_name.i18n_key : klass.name.underscore
46
+ end
47
+
48
+ def ancestors_list(klass)
49
+ klass.ancestors.select do |ancestor|
50
+ ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
51
+ end
52
+ end
53
+ end
54
+ end # AASM
@@ -0,0 +1,5 @@
1
+ Minitest = MiniTest unless defined? Minitest
2
+ # relative-require all minitest_spec files
3
+ Dir[File.dirname(__FILE__) + '/minitest/*.rb'].each do |file|
4
+ require 'aasm/minitest/' + File.basename(file, File.extname(file))
5
+ end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_event_allowed(object, event, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).may_fire_event?(event),
5
+ "Expected that the event :#{event} would be allowed (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_event_allowed(object, event, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).may_fire_event?(event),
11
+ "Expected that the event :#{event} would not be allowed (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_transition_to_allowed(object, to_state, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).states(permitted: true).include?(to_state),
5
+ "Expected that the state :#{to_state} would be reachable (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_transition_to_allowed(object, to_state, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).states(permitted: true).include?(to_state),
11
+ "Expected that the state :#{to_state} would be reachable (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_have_state(object, state, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).current_state == state,
5
+ "Expected that :#{object.aasm(state_machine_name).current_state} would be :#{state} (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_have_state(object, state, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).current_state == state,
11
+ "Expected that :#{object.aasm(state_machine_name).current_state} would be :#{state} (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module Minitest::Assertions
2
+ def assert_transitions_from(object, from_state, *args)
3
+ options = args.first
4
+ options[:on] ||= :default
5
+ assert _transitions_from?(object, from_state, args, options),
6
+ "Expected transition state to :#{options[:to]} from :#{from_state} on event :#{options[:on_event]}, (on :#{options[:on]})"
7
+ end
8
+
9
+ def refute_transitions_from(object, from_state, *args)
10
+ options = args.first
11
+ options[:on] ||= :default
12
+ refute _transitions_from?(object, from_state, args, options),
13
+ "Expected transition state to :#{options[:to]} from :#{from_state} on event :#{options[:on_event]}, (on :#{options[:on]})"
14
+ end
15
+
16
+ def _transitions_from?(object, from_state, args, options)
17
+ state_machine_name = options[:on]
18
+ object.aasm(state_machine_name).current_state = from_state.to_sym
19
+ object.send(options[:on_event], *args) && options[:to].to_sym == object.aasm(state_machine_name).current_state
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ require 'aasm/minitest'
2
+
3
+ module Minitest::Expectations
4
+ AASM.infect_an_assertion :assert_transitions_from, :must_transition_from, :do_not_flip
5
+ AASM.infect_an_assertion :refute_transitions_from, :wont_transition_from, :do_not_flip
6
+
7
+ AASM.infect_an_assertion :assert_transition_to_allowed, :must_allow_transition_to, :do_not_flip
8
+ AASM.infect_an_assertion :refute_transition_to_allowed, :wont_allow_transition_to, :do_not_flip
9
+
10
+ AASM.infect_an_assertion :assert_have_state, :must_have_state, :do_not_flip
11
+ AASM.infect_an_assertion :refute_have_state, :wont_have_state, :do_not_flip
12
+
13
+ AASM.infect_an_assertion :assert_event_allowed, :must_allow_event, :do_not_flip
14
+ AASM.infect_an_assertion :refute_event_allowed, :wont_allow_event, :do_not_flip
15
+ end
@@ -0,0 +1,54 @@
1
+ module AASM
2
+ module Persistence
3
+ class << self
4
+
5
+ def load_persistence(base)
6
+ # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
7
+ hierarchy = base.ancestors.map {|klass| klass.to_s}
8
+
9
+ if hierarchy.include?("ActiveRecord::Base")
10
+ require_persistence :active_record
11
+ include_persistence base, :active_record
12
+ elsif hierarchy.include?("Mongoid::Document")
13
+ require_persistence :mongoid
14
+ include_persistence base, :mongoid
15
+ elsif hierarchy.include?("NoBrainer::Document")
16
+ require_persistence :no_brainer
17
+ include_persistence base, :no_brainer
18
+ elsif hierarchy.include?("Sequel::Model")
19
+ require_persistence :sequel
20
+ include_persistence base, :sequel
21
+ elsif hierarchy.include?("Dynamoid::Document")
22
+ require_persistence :dynamoid
23
+ include_persistence base, :dynamoid
24
+ elsif hierarchy.include?("Redis::Objects")
25
+ require_persistence :redis
26
+ include_persistence base, :redis
27
+ elsif hierarchy.include?("CDQManagedObject")
28
+ include_persistence base, :core_data_query
29
+ else
30
+ include_persistence base, :plain
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def require_persistence(type)
37
+ require File.join(File.dirname(__FILE__), 'persistence', "#{type}_persistence")
38
+ end
39
+
40
+ def include_persistence(base, type)
41
+ base.send(:include, constantize("#{capitalize(type)}Persistence"))
42
+ end
43
+
44
+ def capitalize(string_or_symbol)
45
+ string_or_symbol.to_s.split('_').map {|segment| segment[0].upcase + segment[1..-1]}.join('')
46
+ end
47
+
48
+ def constantize(string)
49
+ AASM::Persistence.const_get(string)
50
+ end
51
+
52
+ end # class << self
53
+ end
54
+ end # AASM
@@ -0,0 +1,165 @@
1
+ require 'aasm/persistence/orm'
2
+ module AASM
3
+ module Persistence
4
+ module ActiveRecordPersistence
5
+ # This method:
6
+ #
7
+ # * extends the model with ClassMethods
8
+ # * includes InstanceMethods
9
+ #
10
+ # Adds
11
+ #
12
+ # after_initialize :aasm_ensure_initial_state
13
+ #
14
+ # As a result, it doesn't matter when you define your methods - the following 2 are equivalent
15
+ #
16
+ # class Foo < ActiveRecord::Base
17
+ # def aasm_write_state(state)
18
+ # "bar"
19
+ # end
20
+ # include AASM
21
+ # end
22
+ #
23
+ # class Foo < ActiveRecord::Base
24
+ # include AASM
25
+ # def aasm_write_state(state)
26
+ # "bar"
27
+ # end
28
+ # end
29
+ #
30
+ def self.included(base)
31
+ base.send(:include, AASM::Persistence::Base)
32
+ base.send(:include, AASM::Persistence::ORM)
33
+ base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
34
+ base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
35
+
36
+ base.after_initialize :aasm_ensure_initial_state
37
+
38
+ # ensure state is in the list of states
39
+ base.validate :aasm_validate_states
40
+ end
41
+
42
+ module ClassMethods
43
+ def aasm_create_scope(state_machine_name, scope_name)
44
+ if ActiveRecord::VERSION::MAJOR >= 3
45
+ conditions = { aasm(state_machine_name).attribute_name => scope_name.to_s }
46
+ class_eval do
47
+ scope scope_name, lambda { where(table_name => conditions) }
48
+ end
49
+ else
50
+ conditions = {
51
+ table_name => { aasm(state_machine_name).attribute_name => scope_name.to_s }
52
+ }
53
+ class_eval do
54
+ named_scope scope_name, :conditions => conditions
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+
62
+ private
63
+
64
+ def aasm_raise_invalid_record
65
+ raise ActiveRecord::RecordInvalid.new(self)
66
+ end
67
+
68
+ def aasm_save
69
+ self.save
70
+ end
71
+
72
+ def aasm_update_column(attribute_name, value)
73
+ self.class.unscoped.where(self.class.primary_key => self.id).update_all(attribute_name => value) == 1
74
+ end
75
+
76
+ def aasm_read_attribute(name)
77
+ read_attribute(name)
78
+ end
79
+
80
+ def aasm_write_attribute(name, value)
81
+ write_attribute(name, value)
82
+ end
83
+
84
+ def aasm_transaction(requires_new, requires_lock)
85
+ self.class.transaction(:requires_new => requires_new) do
86
+ lock!(requires_lock) if requires_lock
87
+ yield
88
+ end
89
+ end
90
+
91
+ def aasm_enum(name=:default)
92
+ case AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
93
+ when false then nil
94
+ when true then aasm_guess_enum_method(name)
95
+ when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
96
+ else AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
97
+ end
98
+ end
99
+
100
+ def aasm_column_looks_like_enum(name=:default)
101
+ column_name = self.class.aasm(name).attribute_name.to_s
102
+ column = self.class.columns_hash[column_name]
103
+ raise NoMethodError.new("undefined method '#{column_name}' for #{self.class}") if column.nil?
104
+ column.type == :integer
105
+ end
106
+
107
+ def aasm_guess_enum_method(name=:default)
108
+ self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
109
+ end
110
+
111
+ def aasm_raw_attribute_value(state, name=:default)
112
+ if aasm_enum(name)
113
+ self.class.send(aasm_enum(name))[state]
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ # Ensures that if the aasm_state column is nil and the record is new
120
+ # then the initial state gets populated after initialization
121
+ #
122
+ # foo = Foo.new
123
+ # foo.aasm_state # => "open" (where :open is the initial state)
124
+ #
125
+ #
126
+ # foo = Foo.find(:first)
127
+ # foo.aasm_state # => 1
128
+ # foo.aasm_state = nil
129
+ # foo.valid?
130
+ # foo.aasm_state # => nil
131
+ #
132
+ def aasm_ensure_initial_state
133
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
134
+ # checking via respond_to? does not work in Rails <= 3
135
+ # if respond_to?(self.class.aasm(state_machine_name).attribute_name) && send(self.class.aasm(state_machine_name).attribute_name).blank? # Rails 4
136
+ if aasm_column_is_blank?(state_machine_name)
137
+ aasm(state_machine_name).enter_initial_state
138
+ end
139
+ end
140
+ end
141
+
142
+ def aasm_column_is_blank?(state_machine_name)
143
+ attribute_name = self.class.aasm(state_machine_name).attribute_name
144
+ attribute_names.include?(attribute_name.to_s) &&
145
+ (send(attribute_name).respond_to?(:empty?) ? !!send(attribute_name).empty? : !send(attribute_name))
146
+ end
147
+
148
+ def aasm_validate_states
149
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
150
+ unless aasm_skipping_validations(state_machine_name)
151
+ if aasm_invalid_state?(state_machine_name)
152
+ self.errors.add(AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.column , "is invalid")
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def aasm_invalid_state?(state_machine_name)
159
+ aasm(state_machine_name).current_state && !aasm(state_machine_name).states.include?(aasm(state_machine_name).current_state)
160
+ end
161
+ end # InstanceMethods
162
+
163
+ end
164
+ end # Persistence
165
+ end # AASM
@@ -0,0 +1,78 @@
1
+ module AASM
2
+ module Persistence
3
+ module Base
4
+
5
+ def self.included(base) #:nodoc:
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ # Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
10
+ #
11
+ # If it's a new record, and the aasm state column is blank it returns the initial state
12
+ # (example provided here for ActiveRecord, but it's true for Mongoid as well):
13
+ #
14
+ # class Foo < ActiveRecord::Base
15
+ # include AASM
16
+ # aasm :column => :status do
17
+ # state :opened
18
+ # state :closed
19
+ # end
20
+ # end
21
+ #
22
+ # foo = Foo.new
23
+ # foo.current_state # => :opened
24
+ # foo.close
25
+ # foo.current_state # => :closed
26
+ #
27
+ # foo = Foo.find(1)
28
+ # foo.current_state # => :opened
29
+ # foo.aasm_state = nil
30
+ # foo.current_state # => nil
31
+ #
32
+ # NOTE: intended to be called from an event
33
+ #
34
+ # This allows for nil aasm states - be sure to add validation to your model
35
+ def aasm_read_state(name=:default)
36
+ state = send(self.class.aasm(name).attribute_name)
37
+ if !state || state.empty?
38
+ aasm_new_record? ? aasm(name).determine_state_name(self.class.aasm(name).initial_state) : nil
39
+ else
40
+ state.to_sym
41
+ end
42
+ end
43
+
44
+ def aasm_new_record?
45
+ new_record?
46
+ end
47
+
48
+ module ClassMethods
49
+ def aasm_column(attribute_name=nil)
50
+ warn "[DEPRECATION] aasm_column is deprecated. Use aasm.attribute_name instead"
51
+ aasm.attribute_name(attribute_name)
52
+ end
53
+ end # ClassMethods
54
+
55
+ end # Base
56
+ end # Persistence
57
+
58
+ class Base
59
+ # make sure to create a (named) scope for each state
60
+ def state_with_scope(*args)
61
+ names = state_without_scope(*args)
62
+ names.each { |name| create_scope(name) if create_scope?(name) }
63
+ end
64
+ alias_method :state_without_scope, :state
65
+ alias_method :state, :state_with_scope
66
+
67
+ private
68
+
69
+ def create_scope?(name)
70
+ @state_machine.config.create_scopes && !@klass.respond_to?(name) && @klass.respond_to?(:aasm_create_scope)
71
+ end
72
+
73
+ def create_scope(name)
74
+ @klass.aasm_create_scope(@name, name)
75
+ end
76
+ end # Base
77
+
78
+ end # AASM
@@ -0,0 +1,94 @@
1
+ module AASM
2
+ module Persistence
3
+ module CoreDataQueryPersistence
4
+ # This method:
5
+ #
6
+ # * extends the model with ClassMethods
7
+ # * includes InstanceMethods
8
+ #
9
+ # Adds
10
+ #
11
+ # after_initialize :aasm_ensure_initial_state
12
+ #
13
+
14
+ def self.included(base)
15
+ base.send(:include, AASM::Persistence::Base)
16
+ base.send(:include, AASM::Persistence::CoreDataQueryPersistence::InstanceMethods)
17
+ base.extend AASM::Persistence::CoreDataQueryPersistence::ClassMethods
18
+
19
+ base.after_initialize :aasm_ensure_initial_state
20
+ end
21
+
22
+ module ClassMethods
23
+ def aasm_create_scope(state_machine_name, scope_name)
24
+ scope(scope_name.to_sym, lambda { where(aasm(state_machine_name).attribute_name.to_sym).eq(scope_name.to_s) })
25
+ end
26
+ end
27
+
28
+ module InstanceMethods
29
+
30
+ # Writes <tt>state</tt> to the state column and persists it to the database
31
+ # using update_attribute (which bypasses validation)
32
+ #
33
+ # foo = Foo.find(1)
34
+ # foo.aasm.current_state # => :opened
35
+ # foo.close!
36
+ # foo.aasm.current_state # => :closed
37
+ # Foo.find(1).aasm.current_state # => :closed
38
+ #
39
+ # NOTE: intended to be called from an event
40
+ def aasm_write_state(state, name=:default)
41
+ raise "Cowardly refusing to save the current CoreDataQuery context"
42
+ aasm_write_state_without_persistence(state, name)
43
+ end
44
+
45
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
46
+ #
47
+ # foo = Foo.find(1)
48
+ # foo.aasm.current_state # => :opened
49
+ # foo.close
50
+ # foo.aasm.current_state # => :closed
51
+ # Foo.find(1).aasm.current_state # => :opened
52
+ # foo.save
53
+ # foo.aasm.current_state # => :closed
54
+ # Foo.find(1).aasm.current_state # => :closed
55
+ #
56
+ # NOTE: intended to be called from an event
57
+ def aasm_write_state_without_persistence(state, name=:default)
58
+ write_attribute(self.class.aasm(name).attribute_name, state.to_s)
59
+ end
60
+
61
+ private
62
+
63
+ # Ensures that if the aasm_state column is nil and the record is new
64
+ # that the initial state gets populated before validation on create
65
+ #
66
+ # foo = Foo.new
67
+ # foo.aasm_state # => nil
68
+ # foo.valid?
69
+ # foo.aasm_state # => "open" (where :open is the initial state)
70
+ #
71
+ #
72
+ # foo = Foo.find(:first)
73
+ # foo.aasm_state # => 1
74
+ # foo.aasm_state = nil
75
+ # foo.valid?
76
+ # foo.aasm_state # => nil
77
+ #
78
+ def aasm_ensure_initial_state
79
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
80
+ next if !send(self.class.aasm(state_machine_name).attribute_name) || send(self.class.aasm(state_machine_name).attribute_name).empty?
81
+ send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s)
82
+ end
83
+ end
84
+ end # InstanceMethods
85
+
86
+ # module NamedScopeMethods
87
+ # def aasm_state_with_named_scope name, options = {}
88
+ # aasm_state_without_named_scope name, options
89
+ # self.named_scope name, :conditions => { "#{table_name}.#{self.aasm.attribute_name}" => name.to_s} unless self.respond_to?(name)
90
+ # end
91
+ # end
92
+ end
93
+ end # Persistence
94
+ end # AASM