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,773 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(ActiveRecord)
4
+
5
+ Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each do |f|
6
+ require File.expand_path(f)
7
+ end
8
+
9
+ load_schema
10
+
11
+ # if you want to see the statements while running the spec enable the following line
12
+ # require 'logger'
13
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
14
+
15
+ describe "instance methods" do
16
+ let(:gate) {Gate.new}
17
+
18
+ it "should respond to aasm persistence methods" do
19
+ expect(gate).to respond_to(:aasm_read_state)
20
+ expect(gate).to respond_to(:aasm_write_state)
21
+ expect(gate).to respond_to(:aasm_write_state_without_persistence)
22
+ end
23
+
24
+ describe "aasm_column_looks_like_enum" do
25
+ subject { lambda{ gate.send(:aasm_column_looks_like_enum) } }
26
+
27
+ let(:column_name) { "value" }
28
+ let(:columns_hash) { Hash[column_name, column] }
29
+
30
+ before :each do
31
+ allow(gate.class.aasm).to receive(:attribute_name).and_return(column_name.to_sym)
32
+ allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
33
+ end
34
+
35
+ context "when AASM column has integer type" do
36
+ let(:column) { double(Object, type: :integer) }
37
+
38
+ it "returns true" do
39
+ expect(subject.call).to be_truthy
40
+ end
41
+ end
42
+
43
+ context "when AASM column has string type" do
44
+ let(:column) { double(Object, type: :string) }
45
+
46
+ it "returns false" do
47
+ expect(subject.call).to be_falsey
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "aasm_guess_enum_method" do
53
+ subject { lambda{ gate.send(:aasm_guess_enum_method) } }
54
+
55
+ before :each do
56
+ allow(gate.class.aasm).to receive(:attribute_name).and_return(:value)
57
+ end
58
+
59
+ it "pluralizes AASM column name" do
60
+ expect(subject.call).to eq :values
61
+ end
62
+ end
63
+
64
+ describe "aasm_enum" do
65
+ context "when AASM enum setting contains an explicit enum method name" do
66
+ let(:with_enum) { WithEnum.new }
67
+
68
+ it "returns whatever value was set in AASM config" do
69
+ expect(with_enum.send(:aasm_enum)).to eq :test
70
+ end
71
+ end
72
+
73
+ context "when AASM enum setting is simply set to true" do
74
+ let(:with_true_enum) { WithTrueEnum.new }
75
+ before :each do
76
+ allow(WithTrueEnum.aasm).to receive(:attribute_name).and_return(:value)
77
+ end
78
+
79
+ it "infers enum method name from pluralized column name" do
80
+ expect(with_true_enum.send(:aasm_enum)).to eq :values
81
+ end
82
+ end
83
+
84
+ context "when AASM enum setting is explicitly disabled" do
85
+ let(:with_false_enum) { WithFalseEnum.new }
86
+
87
+ it "returns nil" do
88
+ expect(with_false_enum.send(:aasm_enum)).to be_nil
89
+ end
90
+ end
91
+
92
+ context "when AASM enum setting is not enabled" do
93
+ before :each do
94
+ allow(Gate.aasm).to receive(:attribute_name).and_return(:value)
95
+ end
96
+
97
+ context "when AASM column looks like enum" do
98
+ before :each do
99
+ allow(gate).to receive(:aasm_column_looks_like_enum).and_return(true)
100
+ end
101
+
102
+ it "infers enum method name from pluralized column name" do
103
+ expect(gate.send(:aasm_enum)).to eq :values
104
+ end
105
+ end
106
+
107
+ context "when AASM column doesn't look like enum'" do
108
+ before :each do
109
+ allow(gate).to receive(:aasm_column_looks_like_enum)
110
+ .and_return(false)
111
+ end
112
+
113
+ it "returns nil, as we're not using enum" do
114
+ expect(gate.send(:aasm_enum)).to be_nil
115
+ end
116
+ end
117
+ end
118
+
119
+ if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 1 # won't work with Rails <= 4.1
120
+ # Enum are introduced from Rails 4.1, therefore enum syntax will not work on Rails <= 4.1
121
+ context "when AASM enum setting is not enabled and aasm column not present" do
122
+
123
+ let(:with_enum_without_column) {WithEnumWithoutColumn.new}
124
+
125
+ it "should raise NoMethodError for transitions" do
126
+ expect{with_enum_without_column.send(:view)}.to raise_error(NoMethodError, /undefined method .status./)
127
+ end
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ context "when AASM is configured to use enum" do
135
+ let(:state_sym) { :running }
136
+ let(:state_code) { 2 }
137
+ let(:enum_name) { :states }
138
+ let(:enum) { Hash[state_sym, state_code] }
139
+
140
+ before :each do
141
+ allow(gate).to receive(:aasm_enum).and_return(enum_name)
142
+ allow(gate).to receive(:aasm_write_state_attribute)
143
+ allow(gate).to receive(:write_attribute)
144
+
145
+ allow(Gate).to receive(enum_name).and_return(enum)
146
+ end
147
+
148
+ describe "aasm_write_state" do
149
+ context "when AASM is configured to skip validations on save" do
150
+ before :each do
151
+ allow(gate).to receive(:aasm_skipping_validations).and_return(true)
152
+ end
153
+
154
+ it "passes state code instead of state symbol to update_all" do
155
+ # stub_chain does not allow us to give expectations on call
156
+ # parameters in the middle of the chain, so we need to use
157
+ # intermediate object instead.
158
+ obj = double(Object, update_all: 1)
159
+ allow(Gate).to receive_message_chain(:unscoped, :where).and_return(obj)
160
+
161
+ gate.aasm_write_state state_sym
162
+
163
+ expect(obj).to have_received(:update_all)
164
+ .with(Hash[gate.class.aasm.attribute_name, state_code])
165
+ end
166
+
167
+ it "searches model outside of default_scope when update_all" do
168
+ # stub_chain does not allow us to give expectations on call
169
+ # parameters in the middle of the chain, so we need to use
170
+ # intermediate object instead.
171
+ unscoped = double(Object, update_all: 1)
172
+ scoped = double(Object, update_all: 1)
173
+
174
+ allow(Gate).to receive(:unscoped).and_return(unscoped)
175
+ allow(Gate).to receive(:where).and_return(scoped)
176
+ allow(unscoped).to receive(:where).and_return(unscoped)
177
+
178
+ gate.aasm_write_state state_sym
179
+
180
+ expect(unscoped).to have_received(:update_all)
181
+ .with(Hash[gate.class.aasm.attribute_name, state_code])
182
+ expect(scoped).to_not have_received(:update_all)
183
+ .with(Hash[gate.class.aasm.attribute_name, state_code])
184
+ end
185
+ end
186
+
187
+ context "when AASM is not skipping validations" do
188
+ it "delegates state update to the helper method" do
189
+ # Let's pretend that validation is passed
190
+ allow(gate).to receive(:save).and_return(true)
191
+
192
+ gate.aasm_write_state state_sym
193
+
194
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
195
+ expect(gate).to_not have_received :write_attribute
196
+ end
197
+ end
198
+ end
199
+
200
+ describe "aasm_write_state_without_persistence" do
201
+ it "delegates state update to the helper method" do
202
+ gate.aasm_write_state_without_persistence state_sym
203
+
204
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
205
+ expect(gate).to_not have_received :write_attribute
206
+ end
207
+ end
208
+
209
+ describe "aasm_raw_attribute_value" do
210
+ it "converts state symbol to state code" do
211
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
212
+ .to eq state_code
213
+ end
214
+ end
215
+ end
216
+
217
+ context "when AASM is configured to use string field" do
218
+ let(:state_sym) { :running }
219
+
220
+ before :each do
221
+ allow(gate).to receive(:aasm_enum).and_return(nil)
222
+ end
223
+
224
+ describe "aasm_raw_attribute_value" do
225
+ it "converts state symbol to string" do
226
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
227
+ .to eq state_sym.to_s
228
+ end
229
+ end
230
+ end
231
+
232
+ describe "aasm_write_attribute helper method" do
233
+ let(:sym) { :sym }
234
+ let(:value) { 42 }
235
+
236
+ before :each do
237
+ allow(gate).to receive(:write_attribute)
238
+ allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
239
+
240
+ gate.send(:aasm_write_state_attribute, sym)
241
+ end
242
+
243
+ it "generates attribute value using a helper method" do
244
+ expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :default)
245
+ end
246
+
247
+ it "writes attribute to the model" do
248
+ expect(gate).to have_received(:write_attribute).with(:aasm_state, value)
249
+ end
250
+ end
251
+
252
+ it "should return the initial state when new and the aasm field is nil" do
253
+ expect(gate.aasm.current_state).to eq(:opened)
254
+ end
255
+
256
+ it "should return the aasm column when new and the aasm field is not nil" do
257
+ gate.aasm_state = "closed"
258
+ expect(gate.aasm.current_state).to eq(:closed)
259
+ end
260
+
261
+ it "should return the aasm column when not new and the aasm.attribute_name is not nil" do
262
+ allow(gate).to receive(:new_record?).and_return(false)
263
+ gate.aasm_state = "state"
264
+ expect(gate.aasm.current_state).to eq(:state)
265
+ end
266
+
267
+ it "should allow a nil state" do
268
+ allow(gate).to receive(:new_record?).and_return(false)
269
+ gate.aasm_state = nil
270
+ expect(gate.aasm.current_state).to be_nil
271
+ end
272
+
273
+ context 'on initialization' do
274
+ it "should initialize the aasm state" do
275
+ expect(Gate.new.aasm_state).to eql 'opened'
276
+ expect(Gate.new.aasm.current_state).to eql :opened
277
+ end
278
+
279
+ it "should not initialize the aasm state if it has not been loaded" do
280
+ # we have to create a gate in the database, for which we only want to
281
+ # load the id, and not the state
282
+ gate = Gate.create!
283
+
284
+ # then we just load the gate ids
285
+ Gate.select(:id).where(id: gate.id).first
286
+ end
287
+ end
288
+
289
+ end
290
+
291
+ if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
292
+ describe "direct state column access" do
293
+ it "accepts false states" do
294
+ f = FalseState.create!
295
+ expect(f.aasm_state).to eql false
296
+ expect {
297
+ f.aasm.events.map(&:name)
298
+ }.to_not raise_error
299
+ end
300
+ end
301
+ end
302
+
303
+ describe 'subclasses' do
304
+ it "should have the same states as its parent class" do
305
+ expect(DerivateNewDsl.aasm.states).to eq(SimpleNewDsl.aasm.states)
306
+ end
307
+
308
+ it "should have the same events as its parent class" do
309
+ expect(DerivateNewDsl.aasm.events).to eq(SimpleNewDsl.aasm.events)
310
+ end
311
+
312
+ it "should have the same column as its parent even for the new dsl" do
313
+ expect(SimpleNewDsl.aasm.attribute_name).to eq(:status)
314
+ expect(DerivateNewDsl.aasm.attribute_name).to eq(:status)
315
+ end
316
+ end
317
+
318
+ describe "named scopes with the new DSL" do
319
+ context "Does not already respond_to? the scope name" do
320
+ it "should add a scope for each state" do
321
+ expect(SimpleNewDsl).to respond_to(:unknown_scope)
322
+ expect(SimpleNewDsl).to respond_to(:another_unknown_scope)
323
+
324
+ expect(SimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
325
+ expect(SimpleNewDsl.another_unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
326
+ end
327
+ end
328
+
329
+ context "Already respond_to? the scope name" do
330
+ it "should not add a scope" do
331
+ expect(SimpleNewDsl).to respond_to(:new)
332
+ expect(SimpleNewDsl.new.class).to eq(SimpleNewDsl)
333
+ end
334
+ end
335
+
336
+ # Scopes on abstract classes didn't work until Rails 5.
337
+ #
338
+ # Reference:
339
+ # https://github.com/rails/rails/issues/10658
340
+ if ActiveRecord::VERSION::MAJOR >= 5
341
+ context "For a descendant of an abstract model" do
342
+ it "should add the scope without the table_name" do
343
+ expect(ImplementedAbstractClassDsl).to respond_to(:unknown_scope)
344
+ expect(ImplementedAbstractClassDsl).to respond_to(:another_unknown_scope)
345
+
346
+ expect(ImplementedAbstractClassDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
347
+ expect(ImplementedAbstractClassDsl.another_unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
348
+ end
349
+ end
350
+ end
351
+
352
+ it "does not create scopes if requested" do
353
+ expect(NoScope).not_to respond_to(:pending)
354
+ end
355
+
356
+ context "result of scope" do
357
+ let!(:dsl1) { SimpleNewDsl.create!(status: :new) }
358
+ let!(:dsl2) { SimpleNewDsl.create!(status: :unknown_scope) }
359
+
360
+ after do
361
+ SimpleNewDsl.destroy_all
362
+ end
363
+
364
+ it "created scope works as where(name: :scope_name)" do
365
+ expect(SimpleNewDsl.unknown_scope).to contain_exactly(dsl2)
366
+ end
367
+ end
368
+ end # scopes
369
+
370
+ describe "direct assignment" do
371
+ it "is allowed by default" do
372
+ obj = NoScope.create
373
+ expect(obj.aasm_state.to_sym).to eql :pending
374
+
375
+ obj.aasm_state = :running
376
+ expect(obj.aasm_state.to_sym).to eql :running
377
+ end
378
+
379
+ it "is forbidden if configured" do
380
+ obj = NoDirectAssignment.create
381
+ expect(obj.aasm_state.to_sym).to eql :pending
382
+
383
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
384
+ expect(obj.aasm_state.to_sym).to eql :pending
385
+ end
386
+
387
+ it 'can be turned off and on again' do
388
+ obj = NoDirectAssignment.create
389
+ expect(obj.aasm_state.to_sym).to eql :pending
390
+
391
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
392
+ expect(obj.aasm_state.to_sym).to eql :pending
393
+
394
+ # allow it temporarily
395
+ NoDirectAssignment.aasm.state_machine.config.no_direct_assignment = false
396
+ obj.aasm_state = :running
397
+ expect(obj.aasm_state.to_sym).to eql :running
398
+
399
+ # and forbid it again
400
+ NoDirectAssignment.aasm.state_machine.config.no_direct_assignment = true
401
+ expect {obj.aasm_state = :pending}.to raise_error(AASM::NoDirectAssignmentError)
402
+ expect(obj.aasm_state.to_sym).to eql :running
403
+ end
404
+ end # direct assignment
405
+
406
+ describe 'initial states' do
407
+
408
+ it 'should support conditions' do
409
+ expect(Thief.new(:skilled => true).aasm.current_state).to eq(:rich)
410
+ expect(Thief.new(:skilled => false).aasm.current_state).to eq(:jailed)
411
+ end
412
+ end
413
+
414
+ describe 'transitions with persistence' do
415
+
416
+ it "should work for valid models" do
417
+ valid_object = Validator.create(:name => 'name')
418
+ expect(valid_object).to be_sleeping
419
+ valid_object.status = :running
420
+ expect(valid_object).to be_running
421
+ end
422
+
423
+ it 'should not store states for invalid models' do
424
+ validator = Validator.create(:name => 'name')
425
+ expect(validator).to be_valid
426
+ expect(validator).to be_sleeping
427
+
428
+ validator.name = nil
429
+ expect(validator).not_to be_valid
430
+ expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid)
431
+ expect(validator).to be_sleeping
432
+
433
+ validator.reload
434
+ expect(validator).not_to be_running
435
+ expect(validator).to be_sleeping
436
+
437
+ validator.name = 'another name'
438
+ expect(validator).to be_valid
439
+ expect(validator.run!).to be_truthy
440
+ expect(validator).to be_running
441
+
442
+ validator.reload
443
+ expect(validator).to be_running
444
+ expect(validator).not_to be_sleeping
445
+ end
446
+
447
+ it 'should not store states for invalid models silently if configured' do
448
+ validator = SilentPersistor.create(:name => 'name')
449
+ expect(validator).to be_valid
450
+ expect(validator).to be_sleeping
451
+
452
+ validator.name = nil
453
+ expect(validator).not_to be_valid
454
+ expect(validator.run!).to be_falsey
455
+ expect(validator).to be_sleeping
456
+
457
+ validator.reload
458
+ expect(validator).not_to be_running
459
+ expect(validator).to be_sleeping
460
+
461
+ validator.name = 'another name'
462
+ expect(validator).to be_valid
463
+ expect(validator.run!).to be_truthy
464
+ expect(validator).to be_running
465
+
466
+ validator.reload
467
+ expect(validator).to be_running
468
+ expect(validator).not_to be_sleeping
469
+ end
470
+
471
+ it 'should store states for invalid models if configured' do
472
+ persistor = InvalidPersistor.create(:name => 'name')
473
+ expect(persistor).to be_valid
474
+ expect(persistor).to be_sleeping
475
+
476
+ persistor.name = nil
477
+ expect(persistor).not_to be_valid
478
+ expect(persistor.run!).to be_truthy
479
+ expect(persistor).to be_running
480
+
481
+ persistor = InvalidPersistor.find(persistor.id)
482
+ persistor.valid?
483
+ expect(persistor).to be_valid
484
+ expect(persistor).to be_running
485
+ expect(persistor).not_to be_sleeping
486
+
487
+ persistor.reload
488
+ expect(persistor).to be_running
489
+ expect(persistor).not_to be_sleeping
490
+ end
491
+
492
+ describe 'pessimistic locking' do
493
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
494
+
495
+ subject { transactor.run! }
496
+
497
+ context 'no lock' do
498
+ let(:transactor) { NoLockTransactor.create!(:name => 'no_lock_transactor', :worker => worker) }
499
+
500
+ it 'should not invoke lock!' do
501
+ expect(transactor).to_not receive(:lock!)
502
+ subject
503
+ end
504
+ end
505
+
506
+ context 'a default lock' do
507
+ let(:transactor) { LockTransactor.create!(:name => 'lock_transactor', :worker => worker) }
508
+
509
+ it 'should invoke lock! with true' do
510
+ expect(transactor).to receive(:lock!).with(true).and_call_original
511
+ subject
512
+ end
513
+ end
514
+
515
+ context 'a FOR UPDATE NOWAIT lock' do
516
+ let(:transactor) { LockNoWaitTransactor.create!(:name => 'lock_no_wait_transactor', :worker => worker) }
517
+
518
+ it 'should invoke lock! with FOR UPDATE NOWAIT' do
519
+ expect(transactor).to receive(:lock!).with('FOR UPDATE NOWAIT').and_call_original
520
+ subject
521
+ end
522
+ end
523
+ end
524
+
525
+ describe 'without transactions' do
526
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
527
+ let(:no_transactor) { NoTransactor.create!(:name => 'transactor', :worker => worker) }
528
+
529
+ it 'should not rollback all changes' do
530
+ expect(no_transactor).to be_sleeping
531
+ expect(worker.status).to eq('sleeping')
532
+
533
+ expect {no_transactor.run!}.to raise_error(StandardError, 'failed on purpose')
534
+ expect(no_transactor).to be_running
535
+ expect(worker.reload.status).to eq('running')
536
+ end
537
+ end
538
+
539
+ describe 'transactions' do
540
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
541
+ let(:transactor) { Transactor.create!(:name => 'transactor', :worker => worker) }
542
+
543
+ it 'should rollback all changes' do
544
+ expect(transactor).to be_sleeping
545
+ expect(worker.status).to eq('sleeping')
546
+
547
+ expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
548
+ expect(transactor).to be_running
549
+ expect(worker.reload.status).to eq('sleeping')
550
+ end
551
+
552
+ context "nested transactions" do
553
+ it "should rollback all changes in nested transaction" do
554
+ expect(transactor).to be_sleeping
555
+ expect(worker.status).to eq('sleeping')
556
+
557
+ Worker.transaction do
558
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
559
+ end
560
+
561
+ expect(transactor).to be_running
562
+ expect(worker.reload.status).to eq('sleeping')
563
+ end
564
+
565
+ it "should only rollback changes in the main transaction not the nested one" do
566
+ # change configuration to not require new transaction
567
+ AASM::StateMachineStore[Transactor][:default].config.requires_new_transaction = false
568
+
569
+ expect(transactor).to be_sleeping
570
+ expect(worker.status).to eq('sleeping')
571
+
572
+ Worker.transaction do
573
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
574
+ end
575
+
576
+ expect(transactor).to be_running
577
+ expect(worker.reload.status).to eq('running')
578
+ end
579
+ end
580
+
581
+ describe "after_commit callback" do
582
+ it "should fire :after_commit if transaction was successful" do
583
+ validator = Validator.create(:name => 'name')
584
+ expect(validator).to be_sleeping
585
+
586
+ validator.run!
587
+ expect(validator).to be_running
588
+ expect(validator.name).to eq("name changed")
589
+
590
+ validator.sleep!("sleeper")
591
+ expect(validator).to be_sleeping
592
+ expect(validator.name).to eq("sleeper")
593
+ end
594
+
595
+ it "should not fire :after_commit if transaction failed" do
596
+ validator = Validator.create(:name => 'name')
597
+ expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
598
+ expect(validator.name).to eq("name")
599
+ end
600
+
601
+ it "should not fire :after_commit if validation failed when saving object" do
602
+ validator = Validator.create(:name => 'name')
603
+ validator.invalid = true
604
+ expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid, 'Invalid record')
605
+ expect(validator).to be_sleeping
606
+ expect(validator.name).to eq("name")
607
+ end
608
+
609
+ it "should not fire if not saving" do
610
+ validator = Validator.create(:name => 'name')
611
+ expect(validator).to be_sleeping
612
+ validator.run
613
+ expect(validator).to be_running
614
+ expect(validator.name).to eq("name")
615
+ end
616
+ end
617
+
618
+ describe 'before and after transaction callbacks' do
619
+ [:after, :before].each do |event_type|
620
+ describe "#{event_type}_transaction callback" do
621
+ it "should fire :#{event_type}_transaction if transaction was successful" do
622
+ validator = Validator.create(:name => 'name')
623
+ expect(validator).to be_sleeping
624
+
625
+ expect { validator.run! }.to change { validator.send("#{event_type}_transaction_performed_on_run") }.from(nil).to(true)
626
+ expect(validator).to be_running
627
+ end
628
+
629
+ it "should fire :#{event_type}_transaction if transaction failed" do
630
+ validator = Validator.create(:name => 'name')
631
+ expect do
632
+ begin
633
+ validator.fail!
634
+ rescue => ignored
635
+ end
636
+ end.to change { validator.send("#{event_type}_transaction_performed_on_fail") }.from(nil).to(true)
637
+ expect(validator).to_not be_running
638
+ end
639
+
640
+ it "should not fire :#{event_type}_transaction if not saving" do
641
+ validator = Validator.create(:name => 'name')
642
+ expect(validator).to be_sleeping
643
+ expect { validator.run }.to_not change { validator.send("#{event_type}_transaction_performed_on_run") }
644
+ expect(validator).to be_running
645
+ expect(validator.name).to eq("name")
646
+ end
647
+ end
648
+ end
649
+ end
650
+
651
+ describe 'before and after all transactions callbacks' do
652
+ [:after, :before].each do |event_type|
653
+ describe "#{event_type}_all_transactions callback" do
654
+ it "should fire :#{event_type}_all_transactions if transaction was successful" do
655
+ validator = Validator.create(:name => 'name')
656
+ expect(validator).to be_sleeping
657
+
658
+ expect { validator.run! }.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
659
+ expect(validator).to be_running
660
+ end
661
+
662
+ it "should fire :#{event_type}_all_transactions if transaction failed" do
663
+ validator = Validator.create(:name => 'name')
664
+ expect do
665
+ begin
666
+ validator.fail!
667
+ rescue => ignored
668
+ end
669
+ end.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
670
+ expect(validator).to_not be_running
671
+ end
672
+
673
+ it "should not fire :#{event_type}_all_transactions if not saving" do
674
+ validator = Validator.create(:name => 'name')
675
+ expect(validator).to be_sleeping
676
+ expect { validator.run }.to_not change { validator.send("#{event_type}_all_transactions_performed") }
677
+ expect(validator).to be_running
678
+ expect(validator.name).to eq("name")
679
+ end
680
+ end
681
+ end
682
+ end
683
+
684
+ context "when not persisting" do
685
+ it 'should not rollback all changes' do
686
+ expect(transactor).to be_sleeping
687
+ expect(worker.status).to eq('sleeping')
688
+
689
+ # Notice here we're calling "run" and not "run!" with a bang.
690
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
691
+ expect(transactor).to be_running
692
+ expect(worker.reload.status).to eq('running')
693
+ end
694
+
695
+ it 'should not create a database transaction' do
696
+ expect(transactor.class).not_to receive(:transaction)
697
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
698
+ end
699
+ end
700
+ end
701
+ end
702
+
703
+ describe "invalid states with persistence" do
704
+
705
+ it "should not store states" do
706
+ validator = Validator.create(:name => 'name')
707
+ validator.status = 'invalid_state'
708
+ expect(validator.save).to be_falsey
709
+ expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
710
+
711
+ validator.reload
712
+ expect(validator).to be_sleeping
713
+ end
714
+
715
+ it "should store invalid states if configured" do
716
+ persistor = InvalidPersistor.create(:name => 'name')
717
+ persistor.status = 'invalid_state'
718
+ expect(persistor.save).to be_truthy
719
+
720
+ persistor.reload
721
+ expect(persistor.status).to eq('invalid_state')
722
+ end
723
+
724
+ end
725
+
726
+ describe 'basic example with two state machines' do
727
+ let(:example) { BasicActiveRecordTwoStateMachinesExample.new }
728
+
729
+ it 'should initialise properly' do
730
+ expect(example.aasm(:search).current_state).to eql :initialised
731
+ expect(example.aasm(:sync).current_state).to eql :unsynced
732
+ end
733
+ end
734
+
735
+ describe 'testing the README examples' do
736
+ it 'Usage' do
737
+ job = ReadmeJob.new
738
+
739
+ expect(job.sleeping?).to eql true
740
+ expect(job.may_run?).to eql true
741
+
742
+ job.run
743
+
744
+ expect(job.running?).to eql true
745
+ expect(job.sleeping?).to eql false
746
+ expect(job.may_run?).to eql false
747
+
748
+ expect { job.run }.to raise_error(AASM::InvalidTransition)
749
+ end
750
+ end
751
+
752
+ describe 'testing the instance_level skip validation with _without_validation method' do
753
+ let(:example) do
754
+ obj = InstanceLevelSkipValidationExample.new(state: 'new')
755
+ obj.save(validate: false)
756
+ obj
757
+ end
758
+
759
+ it 'should be able to change the state with invalid record' do
760
+ expect(example.valid?).to be_falsey
761
+ expect(example.complete!).to be_falsey
762
+ expect(example.complete_without_validation!).to be_truthy
763
+ expect(example.state).to eq('complete')
764
+ end
765
+
766
+ it 'shouldn\'t affect the behaviour of existing method after calling _without_validation! method' do
767
+ expect(example.set_draft!).to be_falsey
768
+ expect(example.set_draft_without_validation!).to be_truthy
769
+ expect(example.state).to eq('draft')
770
+ expect(example.complete!).to be_falsey
771
+ end
772
+ end
773
+ end