aasm 2.1.1 → 5.2.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 (275) 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 +82 -0
  7. data/API +34 -0
  8. data/Appraisals +67 -0
  9. data/CHANGELOG.md +453 -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/{MIT-LICENSE → LICENSE} +1 -1
  17. data/PLANNED_CHANGES.md +11 -0
  18. data/README.md +1524 -0
  19. data/README_FROM_VERSION_3_TO_4.md +240 -0
  20. data/Rakefile +20 -84
  21. data/TESTING.md +25 -0
  22. data/aasm.gemspec +37 -0
  23. data/docker-compose.yml +40 -0
  24. data/gemfiles/norails.gemfile +10 -0
  25. data/gemfiles/rails_4.2.gemfile +17 -0
  26. data/gemfiles/rails_4.2_mongoid_5.gemfile +12 -0
  27. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  28. data/gemfiles/rails_5.0.gemfile +14 -0
  29. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  30. data/gemfiles/rails_5.1.gemfile +14 -0
  31. data/gemfiles/rails_5.2.gemfile +14 -0
  32. data/lib/aasm/aasm.rb +160 -137
  33. data/lib/aasm/base.rb +290 -0
  34. data/lib/aasm/configuration.rb +48 -0
  35. data/lib/aasm/core/event.rb +177 -0
  36. data/lib/aasm/core/invoker.rb +129 -0
  37. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  38. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  39. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  40. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  41. data/lib/aasm/core/state.rb +91 -0
  42. data/lib/aasm/core/transition.rb +83 -0
  43. data/lib/aasm/dsl_helper.rb +32 -0
  44. data/lib/aasm/errors.rb +21 -0
  45. data/lib/aasm/instance_base.rb +133 -0
  46. data/lib/aasm/localizer.rb +64 -0
  47. data/lib/aasm/minitest/allow_event.rb +13 -0
  48. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  49. data/lib/aasm/minitest/have_state.rb +13 -0
  50. data/lib/aasm/minitest/transition_from.rb +21 -0
  51. data/lib/aasm/minitest.rb +5 -0
  52. data/lib/aasm/minitest_spec.rb +15 -0
  53. data/lib/aasm/persistence/active_record_persistence.rb +108 -173
  54. data/lib/aasm/persistence/base.rb +89 -0
  55. data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  56. data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  57. data/lib/aasm/persistence/mongoid_persistence.rb +126 -0
  58. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  59. data/lib/aasm/persistence/orm.rb +154 -0
  60. data/lib/aasm/persistence/plain_persistence.rb +26 -0
  61. data/lib/aasm/persistence/redis_persistence.rb +112 -0
  62. data/lib/aasm/persistence/sequel_persistence.rb +83 -0
  63. data/lib/aasm/persistence.rb +48 -10
  64. data/lib/aasm/rspec/allow_event.rb +26 -0
  65. data/lib/aasm/rspec/allow_transition_to.rb +26 -0
  66. data/lib/aasm/rspec/have_state.rb +22 -0
  67. data/lib/aasm/rspec/transition_from.rb +36 -0
  68. data/lib/aasm/rspec.rb +5 -0
  69. data/lib/aasm/state_machine.rb +40 -22
  70. data/lib/aasm/state_machine_store.rb +76 -0
  71. data/lib/aasm/version.rb +3 -0
  72. data/lib/aasm.rb +21 -1
  73. data/lib/generators/aasm/aasm_generator.rb +16 -0
  74. data/lib/generators/aasm/orm_helpers.rb +41 -0
  75. data/lib/generators/active_record/aasm_generator.rb +40 -0
  76. data/lib/generators/active_record/templates/migration.rb +8 -0
  77. data/lib/generators/active_record/templates/migration_existing.rb +5 -0
  78. data/lib/generators/mongoid/aasm_generator.rb +28 -0
  79. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  80. data/lib/motion-aasm.rb +37 -0
  81. data/spec/database.rb +57 -0
  82. data/spec/database.yml +3 -0
  83. data/spec/en.yml +9 -0
  84. data/spec/generators/active_record_generator_spec.rb +53 -0
  85. data/spec/generators/mongoid_generator_spec.rb +31 -0
  86. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  87. data/spec/localizer_test_model_deprecated_style.yml +13 -0
  88. data/spec/localizer_test_model_new_style.yml +11 -0
  89. data/spec/models/active_record/active_record_callback.rb +93 -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 +42 -0
  98. data/spec/models/active_record/namespaced.rb +16 -0
  99. data/spec/models/active_record/no_direct_assignment.rb +21 -0
  100. data/spec/models/active_record/no_scope.rb +21 -0
  101. data/spec/models/active_record/persisted_state.rb +12 -0
  102. data/spec/models/active_record/person.rb +23 -0
  103. data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
  104. data/spec/models/active_record/reader.rb +7 -0
  105. data/spec/models/active_record/readme_job.rb +21 -0
  106. data/spec/models/active_record/silent_persistor.rb +29 -0
  107. data/spec/models/active_record/simple_new_dsl.rb +32 -0
  108. data/spec/models/active_record/thief.rb +29 -0
  109. data/spec/models/active_record/timestamp_example.rb +16 -0
  110. data/spec/models/active_record/transactor.rb +124 -0
  111. data/spec/models/active_record/transient.rb +6 -0
  112. data/spec/models/active_record/validator.rb +118 -0
  113. data/spec/models/active_record/with_enum.rb +39 -0
  114. data/spec/models/active_record/with_enum_without_column.rb +38 -0
  115. data/spec/models/active_record/with_false_enum.rb +31 -0
  116. data/spec/models/active_record/with_true_enum.rb +39 -0
  117. data/spec/models/active_record/work.rb +3 -0
  118. data/spec/models/active_record/worker.rb +2 -0
  119. data/spec/models/active_record/writer.rb +6 -0
  120. data/spec/models/basic_two_state_machines_example.rb +25 -0
  121. data/spec/models/callbacks/basic.rb +98 -0
  122. data/spec/models/callbacks/basic_multiple.rb +75 -0
  123. data/spec/models/callbacks/guard_within_block.rb +67 -0
  124. data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
  125. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +66 -0
  126. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
  127. data/spec/models/callbacks/private_method.rb +44 -0
  128. data/spec/models/callbacks/private_method_multiple.rb +44 -0
  129. data/spec/models/callbacks/with_args.rb +62 -0
  130. data/spec/models/callbacks/with_args_multiple.rb +61 -0
  131. data/spec/models/callbacks/with_state_arg.rb +34 -0
  132. data/spec/models/callbacks/with_state_arg_multiple.rb +29 -0
  133. data/spec/models/complex_example.rb +222 -0
  134. data/spec/models/conversation.rb +93 -0
  135. data/spec/models/default_state.rb +12 -0
  136. data/spec/models/double_definer.rb +21 -0
  137. data/spec/models/dynamoid/complex_dynamoid_example.rb +37 -0
  138. data/spec/models/dynamoid/dynamoid_multiple.rb +18 -0
  139. data/spec/models/dynamoid/dynamoid_simple.rb +18 -0
  140. data/spec/models/foo.rb +106 -0
  141. data/spec/models/foo_callback_multiple.rb +45 -0
  142. data/spec/models/guard_arguments_check.rb +17 -0
  143. data/spec/models/guard_with_params.rb +24 -0
  144. data/spec/models/guard_with_params_multiple.rb +18 -0
  145. data/spec/models/guardian.rb +58 -0
  146. data/spec/models/guardian_multiple.rb +48 -0
  147. data/spec/models/guardian_without_from_specified.rb +18 -0
  148. data/spec/models/initial_state_proc.rb +31 -0
  149. data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
  150. data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
  151. data/spec/models/mongoid/mongoid_relationships.rb +26 -0
  152. data/spec/models/mongoid/no_scope_mongoid.rb +21 -0
  153. data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
  154. data/spec/models/mongoid/simple_mongoid.rb +23 -0
  155. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +25 -0
  156. data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
  157. data/spec/models/mongoid/validator_mongoid.rb +100 -0
  158. data/spec/models/multi_transitioner.rb +34 -0
  159. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
  160. data/spec/models/namespaced_multiple_example.rb +42 -0
  161. data/spec/models/no_initial_state.rb +25 -0
  162. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  163. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  164. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  165. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  166. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  167. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  168. data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
  169. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  170. data/spec/models/not_auto_loaded/process.rb +21 -0
  171. data/spec/models/parametrised_event.rb +42 -0
  172. data/spec/models/parametrised_event_multiple.rb +29 -0
  173. data/spec/models/process_with_new_dsl.rb +31 -0
  174. data/spec/models/provided_state.rb +24 -0
  175. data/spec/models/redis/complex_redis_example.rb +40 -0
  176. data/spec/models/redis/redis_multiple.rb +20 -0
  177. data/spec/models/redis/redis_simple.rb +20 -0
  178. data/spec/models/sequel/complex_sequel_example.rb +46 -0
  179. data/spec/models/sequel/invalid_persistor.rb +52 -0
  180. data/spec/models/sequel/sequel_multiple.rb +25 -0
  181. data/spec/models/sequel/sequel_simple.rb +26 -0
  182. data/spec/models/sequel/silent_persistor.rb +50 -0
  183. data/spec/models/sequel/transactor.rb +112 -0
  184. data/spec/models/sequel/validator.rb +93 -0
  185. data/spec/models/sequel/worker.rb +12 -0
  186. data/spec/models/silencer.rb +27 -0
  187. data/spec/models/simple_custom_example.rb +53 -0
  188. data/spec/models/simple_example.rb +23 -0
  189. data/spec/models/simple_example_with_guard_args.rb +17 -0
  190. data/spec/models/simple_multiple_example.rb +42 -0
  191. data/spec/models/state_machine_with_failed_event.rb +20 -0
  192. data/spec/models/states_on_one_line_example.rb +8 -0
  193. data/spec/models/sub_class.rb +41 -0
  194. data/spec/models/sub_class_with_more_states.rb +18 -0
  195. data/spec/models/sub_classing.rb +3 -0
  196. data/spec/models/super_class.rb +46 -0
  197. data/spec/models/this_name_better_not_be_in_use.rb +11 -0
  198. data/spec/models/timestamps_example.rb +19 -0
  199. data/spec/models/timestamps_with_named_machine_example.rb +13 -0
  200. data/spec/models/valid_state_name.rb +23 -0
  201. data/spec/spec_helper.rb +41 -0
  202. data/spec/spec_helpers/active_record.rb +8 -0
  203. data/spec/spec_helpers/dynamoid.rb +35 -0
  204. data/spec/spec_helpers/mongoid.rb +26 -0
  205. data/spec/spec_helpers/nobrainer.rb +15 -0
  206. data/spec/spec_helpers/redis.rb +18 -0
  207. data/spec/spec_helpers/remove_warnings.rb +1 -0
  208. data/spec/spec_helpers/sequel.rb +7 -0
  209. data/spec/unit/abstract_class_spec.rb +27 -0
  210. data/spec/unit/api_spec.rb +104 -0
  211. data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
  212. data/spec/unit/callback_multiple_spec.rb +304 -0
  213. data/spec/unit/callbacks_spec.rb +521 -0
  214. data/spec/unit/complex_example_spec.rb +93 -0
  215. data/spec/unit/complex_multiple_example_spec.rb +115 -0
  216. data/spec/unit/edge_cases_spec.rb +16 -0
  217. data/spec/unit/event_multiple_spec.rb +73 -0
  218. data/spec/unit/event_naming_spec.rb +16 -0
  219. data/spec/unit/event_spec.rb +394 -0
  220. data/spec/unit/exception_spec.rb +11 -0
  221. data/spec/unit/guard_arguments_check_spec.rb +9 -0
  222. data/spec/unit/guard_multiple_spec.rb +60 -0
  223. data/spec/unit/guard_spec.rb +89 -0
  224. data/spec/unit/guard_with_params_multiple_spec.rb +10 -0
  225. data/spec/unit/guard_with_params_spec.rb +14 -0
  226. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  227. data/spec/unit/initial_state_multiple_spec.rb +15 -0
  228. data/spec/unit/initial_state_spec.rb +12 -0
  229. data/spec/unit/inspection_multiple_spec.rb +205 -0
  230. data/spec/unit/inspection_spec.rb +153 -0
  231. data/spec/unit/invoker_spec.rb +189 -0
  232. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  233. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  234. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  235. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  236. data/spec/unit/localizer_spec.rb +109 -0
  237. data/spec/unit/memory_leak_spec.rb +38 -0
  238. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
  239. data/spec/unit/namespaced_multiple_example_spec.rb +75 -0
  240. data/spec/unit/new_dsl_spec.rb +12 -0
  241. data/spec/unit/override_warning_spec.rb +94 -0
  242. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +635 -0
  243. data/spec/unit/persistence/active_record_persistence_spec.rb +852 -0
  244. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +135 -0
  245. data/spec/unit/persistence/dynamoid_persistence_spec.rb +84 -0
  246. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +200 -0
  247. data/spec/unit/persistence/mongoid_persistence_spec.rb +177 -0
  248. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  249. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  250. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  251. data/spec/unit/persistence/redis_persistence_spec.rb +53 -0
  252. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +148 -0
  253. data/spec/unit/persistence/sequel_persistence_spec.rb +368 -0
  254. data/spec/unit/readme_spec.rb +41 -0
  255. data/spec/unit/reloading_spec.rb +15 -0
  256. data/spec/unit/rspec_matcher_spec.rb +88 -0
  257. data/spec/unit/simple_custom_example_spec.rb +39 -0
  258. data/spec/unit/simple_example_spec.rb +57 -0
  259. data/spec/unit/simple_multiple_example_spec.rb +91 -0
  260. data/spec/unit/state_spec.rb +105 -0
  261. data/spec/unit/states_on_one_line_example_spec.rb +16 -0
  262. data/spec/unit/subclassing_multiple_spec.rb +74 -0
  263. data/spec/unit/subclassing_spec.rb +46 -0
  264. data/spec/unit/timestamps_spec.rb +32 -0
  265. data/spec/unit/transition_spec.rb +436 -0
  266. data/test/minitest_helper.rb +57 -0
  267. data/test/unit/minitest_matcher_test.rb +80 -0
  268. metadata +607 -60
  269. data/CHANGELOG +0 -33
  270. data/README.rdoc +0 -122
  271. data/TODO +0 -9
  272. data/doc/jamis.rb +0 -591
  273. data/lib/aasm/event.rb +0 -76
  274. data/lib/aasm/state.rb +0 -35
  275. data/lib/aasm/state_transition.rb +0 -36
@@ -0,0 +1,126 @@
1
+ require 'aasm/persistence/orm'
2
+ module AASM
3
+ module Persistence
4
+ module MongoidPersistence
5
+ # This method:
6
+ #
7
+ # * extends the model with ClassMethods
8
+ # * includes InstanceMethods
9
+ #
10
+ # Adds
11
+ #
12
+ # before_validation :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
17
+ # include Mongoid::Document
18
+ # def aasm_write_state(state)
19
+ # "bar"
20
+ # end
21
+ # include AASM
22
+ # end
23
+ #
24
+ # class Foo
25
+ # include Mongoid::Document
26
+ # include AASM
27
+ # def aasm_write_state(state)
28
+ # "bar"
29
+ # end
30
+ # end
31
+ #
32
+ def self.included(base)
33
+ base.send(:include, AASM::Persistence::Base)
34
+ base.send(:include, AASM::Persistence::ORM)
35
+ base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
36
+ base.extend AASM::Persistence::MongoidPersistence::ClassMethods
37
+
38
+ base.after_initialize :aasm_ensure_initial_state
39
+ end
40
+
41
+ module ClassMethods
42
+ def aasm_create_scope(state_machine_name, scope_name)
43
+ scope_options = lambda {
44
+ send(
45
+ :where,
46
+ { aasm(state_machine_name).attribute_name.to_sym => scope_name.to_s }
47
+ )
48
+ }
49
+ send(:scope, scope_name, scope_options)
50
+ end
51
+ end
52
+
53
+ module InstanceMethods
54
+
55
+ private
56
+
57
+ def aasm_save
58
+ self.save
59
+ end
60
+
61
+ def aasm_raise_invalid_record
62
+ raise Mongoid::Errors::Validations.new(self)
63
+ end
64
+
65
+ def aasm_supports_transactions?
66
+ false
67
+ end
68
+
69
+ def aasm_update_column(attribute_name, value)
70
+ if Mongoid::VERSION.to_f >= 4
71
+ set(Hash[attribute_name, value])
72
+ else
73
+ set(attribute_name, value)
74
+ end
75
+
76
+ true
77
+ end
78
+
79
+ def aasm_read_attribute(name)
80
+ read_attribute(name)
81
+ end
82
+
83
+ def aasm_write_attribute(name, value)
84
+ write_attribute(name, value)
85
+ end
86
+
87
+ # Ensures that if the aasm_state column is nil and the record is new
88
+ # that the initial state gets populated before validation on create
89
+ #
90
+ # foo = Foo.new
91
+ # foo.aasm_state # => nil
92
+ # foo.valid?
93
+ # foo.aasm_state # => "open" (where :open is the initial state)
94
+ #
95
+ #
96
+ # foo = Foo.find(:first)
97
+ # foo.aasm_state # => 1
98
+ # foo.aasm_state = nil
99
+ # foo.valid?
100
+ # foo.aasm_state # => nil
101
+ #
102
+ def aasm_ensure_initial_state
103
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
104
+ attribute_name = self.class.aasm(state_machine_name).attribute_name.to_s
105
+ # Do not load initial state when object attributes are not loaded,
106
+ # mongoid has_many relationship does not load child object attributes when
107
+ # only ids are loaded, for example parent.child_ids will not load child object attributes.
108
+ # This feature is introduced in mongoid > 4.
109
+ if attribute_names.include?(attribute_name) && !attributes[attribute_name] || attributes[attribute_name].empty?
110
+ # attribute_missing? is defined in mongoid > 4
111
+ return if Mongoid::VERSION.to_f >= 4 && attribute_missing?(attribute_name)
112
+ send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s)
113
+ end
114
+ end
115
+ end
116
+ end # InstanceMethods
117
+
118
+ # module NamedScopeMethods
119
+ # def aasm_state_with_named_scope name, options = {}
120
+ # aasm_state_without_named_scope name, options
121
+ # self.named_scope name, :conditions => { "#{table_name}.#{self.aasm.attribute_name}" => name.to_s} unless self.respond_to?(name)
122
+ # end
123
+ # end
124
+ end
125
+ end # Persistence
126
+ end # AASM
@@ -0,0 +1,105 @@
1
+ require 'aasm/persistence/orm'
2
+ module AASM
3
+ module Persistence
4
+ module NoBrainerPersistence
5
+ # This method:
6
+ #
7
+ # * extends the model with ClassMethods
8
+ # * includes InstanceMethods
9
+ #
10
+ # Adds
11
+ #
12
+ # before_validation :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
17
+ # include NoBrainer::Document
18
+ # def aasm_write_state(state)
19
+ # "bar"
20
+ # end
21
+ # include AASM
22
+ # end
23
+ #
24
+ # class Foo
25
+ # include NoBrainer::Document
26
+ # include AASM
27
+ # def aasm_write_state(state)
28
+ # "bar"
29
+ # end
30
+ # end
31
+ #
32
+ def self.included(base)
33
+ base.send(:include, AASM::Persistence::Base)
34
+ base.send(:include, AASM::Persistence::ORM)
35
+ base.send(:include, AASM::Persistence::NoBrainerPersistence::InstanceMethods)
36
+ base.extend AASM::Persistence::NoBrainerPersistence::ClassMethods
37
+
38
+ base.after_initialize :aasm_ensure_initial_state
39
+ end
40
+
41
+ module ClassMethods
42
+ def aasm_create_scope(state_machine_name, scope_name)
43
+ scope_options = lambda {
44
+ where(aasm(state_machine_name).attribute_name.to_sym => scope_name.to_s)
45
+ }
46
+ send(:scope, scope_name, scope_options)
47
+ end
48
+ end
49
+
50
+ module InstanceMethods
51
+
52
+ private
53
+
54
+ def aasm_save
55
+ self.save
56
+ end
57
+
58
+ def aasm_raise_invalid_record
59
+ raise NoBrainer::Error::DocumentInvalid.new(self)
60
+ end
61
+
62
+ def aasm_supports_transactions?
63
+ false
64
+ end
65
+
66
+ def aasm_update_column(attribute_name, value)
67
+ write_attribute(attribute_name, value)
68
+ save(validate: false)
69
+
70
+ true
71
+ end
72
+
73
+ def aasm_read_attribute(name)
74
+ read_attribute(name)
75
+ end
76
+
77
+ def aasm_write_attribute(name, value)
78
+ write_attribute(name, value)
79
+ end
80
+
81
+ # Ensures that if the aasm_state column is nil and the record is new
82
+ # that the initial state gets populated before validation on create
83
+ #
84
+ # foo = Foo.new
85
+ # foo.aasm_state # => nil
86
+ # foo.valid?
87
+ # foo.aasm_state # => "open" (where :open is the initial state)
88
+ #
89
+ #
90
+ # foo = Foo.find(:first)
91
+ # foo.aasm_state # => 1
92
+ # foo.aasm_state = nil
93
+ # foo.valid?
94
+ # foo.aasm_state # => nil
95
+ #
96
+ def aasm_ensure_initial_state
97
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |name|
98
+ aasm_column = self.class.aasm(name).attribute_name
99
+ aasm(name).enter_initial_state if !read_attribute(aasm_column) || read_attribute(aasm_column).empty?
100
+ end
101
+ end
102
+ end # InstanceMethods
103
+ end
104
+ end # Persistence
105
+ end # AASM
@@ -0,0 +1,154 @@
1
+ module AASM
2
+ module Persistence
3
+ # This module adds transactional support for any database that supports it.
4
+ # This includes rollback capability and rollback/commit callbacks.
5
+ module ORM
6
+
7
+ # Writes <tt>state</tt> to the state column and persists it to the database
8
+ #
9
+ # foo = Foo.find(1)
10
+ # foo.aasm.current_state # => :opened
11
+ # foo.close!
12
+ # foo.aasm.current_state # => :closed
13
+ # Foo.find(1).aasm.current_state # => :closed
14
+ #
15
+ # NOTE: intended to be called from an event
16
+ def aasm_write_state(state, name=:default)
17
+ attribute_name = self.class.aasm(name).attribute_name
18
+ old_value = aasm_read_attribute(attribute_name)
19
+ aasm_write_state_attribute state, name
20
+
21
+ success = if aasm_skipping_validations(name)
22
+ aasm_update_column(attribute_name, aasm_raw_attribute_value(state, name))
23
+ else
24
+ aasm_save
25
+ end
26
+
27
+ unless success
28
+ aasm_rollback(name, old_value)
29
+ aasm_raise_invalid_record if aasm_whiny_persistence(name)
30
+ end
31
+
32
+ success
33
+ end
34
+
35
+ # Writes <tt>state</tt> to the state field, but does not persist it to the database
36
+ #
37
+ # foo = Foo.find(1)
38
+ # foo.aasm.current_state # => :opened
39
+ # foo.close
40
+ # foo.aasm.current_state # => :closed
41
+ # Foo.find(1).aasm.current_state # => :opened
42
+ # foo.save
43
+ # foo.aasm.current_state # => :closed
44
+ # Foo.find(1).aasm.current_state # => :closed
45
+ #
46
+ # NOTE: intended to be called from an event
47
+ def aasm_write_state_without_persistence(state, name=:default)
48
+ aasm_write_state_attribute(state, name)
49
+ end
50
+
51
+ private
52
+
53
+ # Save the record and return true if it succeeded/false otherwise.
54
+ def aasm_save
55
+ raise("Define #aasm_save_without_error in the AASM Persistence class.")
56
+ end
57
+
58
+ def aasm_raise_invalid_record
59
+ raise("Define #aasm_raise_invalid_record in the AASM Persistence class.")
60
+ end
61
+
62
+ # Update only the column without running validations.
63
+ def aasm_update_column(attribute_name, value)
64
+ raise("Define #aasm_update_column in the AASM Persistence class.")
65
+ end
66
+
67
+ def aasm_read_attribute(name)
68
+ raise("Define #aasm_read_attribute the AASM Persistence class.")
69
+ end
70
+
71
+ def aasm_write_attribute(name, value)
72
+ raise("Define #aasm_write_attribute in the AASM Persistence class.")
73
+ end
74
+
75
+ # Returns true or false if transaction completed successfully.
76
+ def aasm_transaction(requires_new, requires_lock)
77
+ raise("Define #aasm_transaction the AASM Persistence class.")
78
+ end
79
+
80
+ def aasm_supports_transactions?
81
+ true
82
+ end
83
+
84
+ def aasm_execute_after_commit
85
+ yield
86
+ end
87
+
88
+ def aasm_write_state_attribute(state, name=:default)
89
+ aasm_write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
90
+ end
91
+
92
+ def aasm_raw_attribute_value(state, _name=:default)
93
+ state.to_s
94
+ end
95
+
96
+ def aasm_rollback(name, old_value)
97
+ aasm_write_attribute(self.class.aasm(name).attribute_name, old_value)
98
+ false
99
+ end
100
+
101
+ def aasm_whiny_persistence(state_machine_name)
102
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_persistence
103
+ end
104
+
105
+ def aasm_skipping_validations(state_machine_name)
106
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
107
+ end
108
+
109
+ def use_transactions?(state_machine_name)
110
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.use_transactions
111
+ end
112
+
113
+ def requires_new?(state_machine_name)
114
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction
115
+ end
116
+
117
+ def requires_lock?(state_machine_name)
118
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_lock
119
+ end
120
+
121
+ # Returns true if event was fired successfully and transaction completed.
122
+ def aasm_fire_event(state_machine_name, name, options, *args, &block)
123
+ return super unless aasm_supports_transactions? && options[:persist]
124
+
125
+ event = self.class.aasm(state_machine_name).state_machine.events[name]
126
+ event.fire_callbacks(:before_transaction, self, *args)
127
+ event.fire_global_callbacks(:before_all_transactions, self, *args)
128
+
129
+ begin
130
+ success = if options[:persist] && use_transactions?(state_machine_name)
131
+ aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do
132
+ super
133
+ end
134
+ else
135
+ super
136
+ end
137
+
138
+ if success && !(event.options.keys & [:after_commit, :after_all_commits]).empty?
139
+ aasm_execute_after_commit do
140
+ event.fire_callbacks(:after_commit, self, *args)
141
+ event.fire_global_callbacks(:after_all_commits, self, *args)
142
+ end
143
+ end
144
+
145
+ success
146
+ ensure
147
+ event.fire_callbacks(:after_transaction, self, *args)
148
+ event.fire_global_callbacks(:after_all_transactions, self, *args)
149
+ end
150
+ end
151
+
152
+ end # Transactional
153
+ end # Persistence
154
+ end # AASM
@@ -0,0 +1,26 @@
1
+ module AASM
2
+ module Persistence
3
+ module PlainPersistence
4
+
5
+ # may be overwritten by persistence mixins
6
+ def aasm_read_state(name=:default)
7
+ # all the following lines behave like @current_state ||= aasm(name).enter_initial_state
8
+ current = aasm(name).instance_variable_defined?("@current_state_#{name}") &&
9
+ aasm(name).instance_variable_get("@current_state_#{name}")
10
+ return current if current
11
+ aasm(name).instance_variable_set("@current_state_#{name}", aasm(name).enter_initial_state)
12
+ end
13
+
14
+ # may be overwritten by persistence mixins
15
+ def aasm_write_state(new_state, name=:default)
16
+ true
17
+ end
18
+
19
+ # may be overwritten by persistence mixins
20
+ def aasm_write_state_without_persistence(new_state, name=:default)
21
+ aasm(name).instance_variable_set("@current_state_#{name}", new_state)
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,112 @@
1
+ module AASM
2
+ module Persistence
3
+ module RedisPersistence
4
+
5
+ def self.included(base)
6
+ base.send(:include, AASM::Persistence::Base)
7
+ base.send(:include, AASM::Persistence::RedisPersistence::InstanceMethods)
8
+ end
9
+
10
+ module InstanceMethods
11
+ # Initialize with default values
12
+ #
13
+ # Redis::Objects removes the key from Redis when set to `nil`
14
+ def initialize(*args)
15
+ super
16
+ aasm_ensure_initial_state
17
+ end
18
+ # Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
19
+ #
20
+ # If it's a new record, and the aasm state column is blank it returns the initial state
21
+ #
22
+ # class Foo
23
+ # include Redis::Objects
24
+ # include AASM
25
+ # aasm :column => :status do
26
+ # state :opened
27
+ # state :closed
28
+ # end
29
+ # end
30
+ #
31
+ # foo = Foo.new
32
+ # foo.current_state # => :opened
33
+ # foo.close
34
+ # foo.current_state # => :closed
35
+ #
36
+ # foo = Foo[1]
37
+ # foo.current_state # => :opened
38
+ # foo.aasm_state = nil
39
+ # foo.current_state # => nil
40
+ #
41
+ # NOTE: intended to be called from an event
42
+ #
43
+ # This allows for nil aasm states - be sure to add validation to your model
44
+ def aasm_read_state(name=:default)
45
+ state = send(self.class.aasm(name).attribute_name)
46
+
47
+ if state.value.nil?
48
+ nil
49
+ else
50
+ state.value.to_sym
51
+ end
52
+ end
53
+
54
+ # Ensures that if the aasm_state column is nil and the record is new
55
+ # that the initial state gets populated before validation on create
56
+ #
57
+ # foo = Foo.new
58
+ # foo.aasm_state # => nil
59
+ # foo.valid?
60
+ # foo.aasm_state # => "open" (where :open is the initial state)
61
+ #
62
+ #
63
+ # foo = Foo.find(:first)
64
+ # foo.aasm_state # => 1
65
+ # foo.aasm_state = nil
66
+ # foo.valid?
67
+ # foo.aasm_state # => nil
68
+ #
69
+ def aasm_ensure_initial_state
70
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |name|
71
+ aasm_column = self.class.aasm(name).attribute_name
72
+ aasm(name).enter_initial_state if !send(aasm_column).value || send(aasm_column).value.empty?
73
+ end
74
+ end
75
+
76
+ # Writes <tt>state</tt> to the state column and persists it to the database
77
+ #
78
+ # foo = Foo[1]
79
+ # foo.aasm.current_state # => :opened
80
+ # foo.close!
81
+ # foo.aasm.current_state # => :closed
82
+ # Foo[1].aasm.current_state # => :closed
83
+ #
84
+ # NOTE: intended to be called from an event
85
+ def aasm_write_state(state, name=:default)
86
+ aasm_column = self.class.aasm(name).attribute_name
87
+ send("#{aasm_column}").value = state
88
+ end
89
+
90
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
91
+ # (but actually it still does)
92
+ #
93
+ # With Redis::Objects it's not possible to skip persisting - it's not an ORM,
94
+ # it does not operate like an AR model and does not know how to postpone changes.
95
+ #
96
+ # foo = Foo[1]
97
+ # foo.aasm.current_state # => :opened
98
+ # foo.close
99
+ # foo.aasm.current_state # => :closed
100
+ # Foo[1].aasm.current_state # => :opened
101
+ # foo.save
102
+ # foo.aasm.current_state # => :closed
103
+ # Foo[1].aasm.current_state # => :closed
104
+ #
105
+ # NOTE: intended to be called from an event
106
+ def aasm_write_state_without_persistence(state, name=:default)
107
+ aasm_write_state(state, name)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,83 @@
1
+ require 'aasm/persistence/orm'
2
+ module AASM
3
+ module Persistence
4
+ module SequelPersistence
5
+ def self.included(base)
6
+ base.send(:include, AASM::Persistence::Base)
7
+ base.send(:include, AASM::Persistence::ORM)
8
+ base.send(:include, AASM::Persistence::SequelPersistence::InstanceMethods)
9
+ end
10
+
11
+ module InstanceMethods
12
+ def before_validation
13
+ aasm_ensure_initial_state
14
+ super
15
+ end
16
+
17
+ def before_create
18
+ super
19
+ end
20
+
21
+ def aasm_raise_invalid_record
22
+ raise Sequel::ValidationFailed.new(self)
23
+ end
24
+
25
+ def aasm_new_record?
26
+ new?
27
+ end
28
+
29
+ # Returns nil if fails silently
30
+ # http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-save
31
+ def aasm_save
32
+ !save(raise_on_failure: false).nil?
33
+ end
34
+
35
+ def aasm_read_attribute(name)
36
+ send(name)
37
+ end
38
+
39
+ def aasm_write_attribute(name, value)
40
+ send("#{name}=", value)
41
+ end
42
+
43
+ def aasm_transaction(requires_new, requires_lock)
44
+ self.class.db.transaction(savepoint: requires_new) do
45
+ if requires_lock
46
+ # http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-lock-21
47
+ requires_lock.is_a?(String) ? lock!(requires_lock) : lock!
48
+ end
49
+ yield
50
+ end
51
+ end
52
+
53
+ def aasm_update_column(attribute_name, value)
54
+ this.update(attribute_name => value)
55
+ end
56
+
57
+ # Ensures that if the aasm_state column is nil and the record is new
58
+ # that the initial state gets populated before validation on create
59
+ #
60
+ # foo = Foo.new
61
+ # foo.aasm_state # => nil
62
+ # foo.valid?
63
+ # foo.aasm_state # => "open" (where :open is the initial state)
64
+ #
65
+ #
66
+ # foo = Foo.find(:first)
67
+ # foo.aasm_state # => 1
68
+ # foo.aasm_state = nil
69
+ # foo.valid?
70
+ # foo.aasm_state # => nil
71
+ #
72
+ def aasm_ensure_initial_state
73
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
74
+ aasm(state_machine_name).enter_initial_state if
75
+ (new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
76
+ send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,16 +1,54 @@
1
1
  module AASM
2
2
  module Persistence
3
+ class << self
3
4
 
4
- # Checks to see this class or any of it's superclasses inherit from
5
- # ActiveRecord::Base and if so includes ActiveRecordPersistence
6
- def self.set_persistence(base)
7
- # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
8
- hierarchy = base.ancestors.map {|klass| klass.to_s}
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}
9
8
 
10
- if hierarchy.include?("ActiveRecord::Base")
11
- require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
12
- base.send(:include, AASM::Persistence::ActiveRecordPersistence)
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
13
32
  end
14
- 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
15
53
  end
16
- end
54
+ end # AASM
@@ -0,0 +1,26 @@
1
+ RSpec::Matchers.define :allow_event do |event|
2
+ match do |obj|
3
+ @state_machine_name ||= :default
4
+ obj.aasm(@state_machine_name).may_fire_event?(event, *@args)
5
+ end
6
+
7
+ chain :on do |state_machine_name|
8
+ @state_machine_name = state_machine_name
9
+ end
10
+
11
+ chain :with do |*args|
12
+ @args = args
13
+ end
14
+
15
+ description do
16
+ "allow event #{expected} (on :#{@state_machine_name})"
17
+ end
18
+
19
+ failure_message do |obj|
20
+ "expected that the event :#{expected} would be allowed (on :#{@state_machine_name})"
21
+ end
22
+
23
+ failure_message_when_negated do |obj|
24
+ "expected that the event :#{expected} would not be allowed (on :#{@state_machine_name})"
25
+ end
26
+ end