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,635 @@
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) {MultipleGate.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, :left) } }
26
+
27
+ let(:column_name) { "value" }
28
+ let(:columns_hash) { Hash[column_name, column] }
29
+
30
+ before :each do
31
+ allow(gate.class.aasm(:left)).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, :left) } }
54
+
55
+ before :each do
56
+ allow(gate.class.aasm(:left)).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) { MultipleWithEnum.new }
67
+
68
+ it "returns whatever value was set in AASM config" do
69
+ expect(with_enum.send(:aasm_enum, :left)).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) { MultipleWithTrueEnum.new }
75
+ before :each do
76
+ allow(MultipleWithTrueEnum.aasm(:left)).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, :left)).to eq :values
81
+ end
82
+ end
83
+
84
+ context "when AASM enum setting is explicitly disabled" do
85
+ let(:with_false_enum) { MultipleWithFalseEnum.new }
86
+
87
+ it "returns nil" do
88
+ expect(with_false_enum.send(:aasm_enum, :left)).to be_nil
89
+ end
90
+ end
91
+
92
+ context "when AASM enum setting is not enabled" do
93
+ before :each do
94
+ allow(MultipleGate.aasm(:left)).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).with(:left).and_return(true)
100
+ end
101
+
102
+ it "infers enum method name from pluralized column name" do
103
+ expect(gate.send(:aasm_enum, :left)).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, :left)).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(:multiple_with_enum_without_column) {MultipleWithEnumWithoutColumn.new}
124
+
125
+ it "should raise NoMethodError for transitions" do
126
+ expect{multiple_with_enum_without_column.send(:view, :left)}.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(MultipleGate).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(MultipleGate).to receive_message_chain(:unscoped, :where)
160
+ .and_return(obj)
161
+
162
+ gate.aasm_write_state state_sym, :left
163
+
164
+ expect(obj).to have_received(:update_all)
165
+ .with(Hash[gate.class.aasm(:left).attribute_name, state_code])
166
+ end
167
+ end
168
+
169
+ context "when AASM is not skipping validations" do
170
+ it "delegates state update to the helper method" do
171
+ # Let's pretend that validation is passed
172
+ allow(gate).to receive(:save).and_return(true)
173
+
174
+ gate.aasm_write_state state_sym, :left
175
+
176
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :left)
177
+ expect(gate).to_not have_received :write_attribute
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "aasm_write_state_without_persistence" do
183
+ it "delegates state update to the helper method" do
184
+ gate.aasm_write_state_without_persistence state_sym, :left
185
+
186
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :left)
187
+ expect(gate).to_not have_received :write_attribute
188
+ end
189
+ end
190
+
191
+ describe "aasm_raw_attribute_value" do
192
+ it "converts state symbol to state code" do
193
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
194
+ .to eq state_code
195
+ end
196
+ end
197
+ end
198
+
199
+ context "when AASM is configured to use string field" do
200
+ let(:state_sym) { :running }
201
+
202
+ before :each do
203
+ allow(gate).to receive(:aasm_enum).and_return(nil)
204
+ end
205
+
206
+ describe "aasm_raw_attribute_value" do
207
+ it "converts state symbol to string" do
208
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
209
+ .to eq state_sym.to_s
210
+ end
211
+ end
212
+ end
213
+
214
+ describe "aasm_write_attribute helper method" do
215
+ let(:sym) { :sym }
216
+ let(:value) { 42 }
217
+
218
+ before :each do
219
+ allow(gate).to receive(:write_attribute)
220
+ allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
221
+
222
+ gate.send(:aasm_write_state_attribute, sym, :left)
223
+ end
224
+
225
+ it "generates attribute value using a helper method" do
226
+ expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :left)
227
+ end
228
+
229
+ it "writes attribute to the model" do
230
+ expect(gate).to have_received(:write_attribute).with(:aasm_state, value)
231
+ end
232
+ end
233
+
234
+ it "should return the initial state when new and the aasm field is nil" do
235
+ expect(gate.aasm(:left).current_state).to eq(:opened)
236
+ end
237
+
238
+ it "should return the aasm column when new and the aasm field is not nil" do
239
+ gate.aasm_state = "closed"
240
+ expect(gate.aasm(:left).current_state).to eq(:closed)
241
+ end
242
+
243
+ it "should return the aasm column when not new and the aasm.attribute_name is not nil" do
244
+ allow(gate).to receive(:new_record?).and_return(false)
245
+ gate.aasm_state = "state"
246
+ expect(gate.aasm(:left).current_state).to eq(:state)
247
+ end
248
+
249
+ it "should allow a nil state" do
250
+ allow(gate).to receive(:new_record?).and_return(false)
251
+ gate.aasm_state = nil
252
+ expect(gate.aasm(:left).current_state).to be_nil
253
+ end
254
+
255
+ context 'on initialization' do
256
+ it "should initialize the aasm state" do
257
+ expect(MultipleGate.new.aasm_state).to eql 'opened'
258
+ expect(MultipleGate.new.aasm(:left).current_state).to eql :opened
259
+ end
260
+
261
+ it "should not initialize the aasm state if it has not been loaded" do
262
+ # we have to create a gate in the database, for which we only want to
263
+ # load the id, and not the state
264
+ gate = MultipleGate.create!
265
+
266
+ # then we just load the gate ids
267
+ MultipleGate.select(:id).where(id: gate.id).first
268
+ end
269
+ end
270
+
271
+ end
272
+
273
+ if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
274
+ describe "direct state column access" do
275
+ it "accepts false states" do
276
+ f = MultipleFalseState.create!
277
+ expect(f.aasm_state).to eql false
278
+ expect {
279
+ f.aasm(:left).events.map(&:name)
280
+ }.to_not raise_error
281
+ end
282
+ end
283
+ end
284
+
285
+ describe 'subclasses' do
286
+ it "should have the same states as its parent class" do
287
+ expect(MultipleDerivateNewDsl.aasm(:left).states).to eq(MultipleSimpleNewDsl.aasm(:left).states)
288
+ end
289
+
290
+ it "should have the same events as its parent class" do
291
+ expect(MultipleDerivateNewDsl.aasm(:left).events).to eq(MultipleSimpleNewDsl.aasm(:left).events)
292
+ end
293
+
294
+ it "should have the same column as its parent even for the new dsl" do
295
+ expect(MultipleSimpleNewDsl.aasm(:left).attribute_name).to eq(:status)
296
+ expect(MultipleDerivateNewDsl.aasm(:left).attribute_name).to eq(:status)
297
+ end
298
+ end
299
+
300
+ describe "named scopes with the new DSL" do
301
+ context "Does not already respond_to? the scope name" do
302
+ it "should add a scope for each state" do
303
+ expect(MultipleSimpleNewDsl).to respond_to(:unknown_scope)
304
+ expect(MultipleSimpleNewDsl).to respond_to(:another_unknown_scope)
305
+
306
+ expect(MultipleSimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
307
+ expect(MultipleSimpleNewDsl.another_unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
308
+ end
309
+ end
310
+
311
+ context "Already respond_to? the scope name" do
312
+ it "should not add a scope" do
313
+ expect(MultipleSimpleNewDsl).to respond_to(:new)
314
+ expect(MultipleSimpleNewDsl.new.class).to eq(MultipleSimpleNewDsl)
315
+ end
316
+ end
317
+
318
+ it "does not create scopes if requested" do
319
+ expect(MultipleNoScope).not_to respond_to(:pending)
320
+ end
321
+
322
+ context "result of scope" do
323
+ let!(:dsl1) { MultipleSimpleNewDsl.create!(status: :new) }
324
+ let!(:dsl2) { MultipleSimpleNewDsl.create!(status: :unknown_scope) }
325
+
326
+ after do
327
+ MultipleSimpleNewDsl.destroy_all
328
+ end
329
+
330
+ it "created scope works as where(name: :scope_name)" do
331
+ expect(MultipleSimpleNewDsl.unknown_scope).to contain_exactly(dsl2)
332
+ end
333
+ end
334
+
335
+ context "when namespeced" do
336
+ it "add namespaced scopes" do
337
+ expect(MultipleNamespaced).to respond_to(:car_unsold)
338
+ expect(MultipleNamespaced).to respond_to(:car_sold)
339
+
340
+ expect(MultipleNamespaced.car_unsold.is_a?(ActiveRecord::Relation)).to be_truthy
341
+ expect(MultipleNamespaced.car_sold.is_a?(ActiveRecord::Relation)).to be_truthy
342
+ end
343
+ it "add unnamespaced scopes" do
344
+ expect(MultipleNamespaced).to respond_to(:unsold)
345
+ expect(MultipleNamespaced).to respond_to(:sold)
346
+
347
+ expect(MultipleNamespaced.unsold.is_a?(ActiveRecord::Relation)).to be_truthy
348
+ expect(MultipleNamespaced.sold.is_a?(ActiveRecord::Relation)).to be_truthy
349
+ end
350
+ end
351
+ end # scopes
352
+
353
+ describe "direct assignment" do
354
+ it "is allowed by default" do
355
+ obj = MultipleNoScope.create
356
+ expect(obj.aasm_state.to_sym).to eql :pending
357
+
358
+ obj.aasm_state = :running
359
+ expect(obj.aasm_state.to_sym).to eql :running
360
+ end
361
+
362
+ it "is forbidden if configured" do
363
+ obj = MultipleNoDirectAssignment.create
364
+ expect(obj.aasm_state.to_sym).to eql :pending
365
+
366
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
367
+ expect(obj.aasm_state.to_sym).to eql :pending
368
+ end
369
+
370
+ it 'can be turned off and on again' do
371
+ obj = MultipleNoDirectAssignment.create
372
+ expect(obj.aasm_state.to_sym).to eql :pending
373
+
374
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
375
+ expect(obj.aasm_state.to_sym).to eql :pending
376
+
377
+ # allow it temporarily
378
+ MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = false
379
+ obj.aasm_state = :running
380
+ expect(obj.aasm_state.to_sym).to eql :running
381
+
382
+ # and forbid it again
383
+ MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = true
384
+ expect {obj.aasm_state = :pending}.to raise_error(AASM::NoDirectAssignmentError)
385
+ expect(obj.aasm_state.to_sym).to eql :running
386
+ end
387
+ end # direct assignment
388
+
389
+ describe 'initial states' do
390
+ it 'should support conditions' do
391
+ expect(MultipleThief.new(:skilled => true).aasm(:left).current_state).to eq(:rich)
392
+ expect(MultipleThief.new(:skilled => false).aasm(:left).current_state).to eq(:jailed)
393
+ end
394
+ end
395
+
396
+ describe 'transitions with persistence' do
397
+
398
+ it "should work for valid models" do
399
+ valid_object = MultipleValidator.create(:name => 'name')
400
+ expect(valid_object).to be_sleeping
401
+ valid_object.status = :running
402
+ expect(valid_object).to be_running
403
+ end
404
+
405
+ it 'should not store states for invalid models' do
406
+ validator = MultipleValidator.create(:name => 'name')
407
+ expect(validator).to be_valid
408
+ expect(validator).to be_sleeping
409
+
410
+ validator.name = nil
411
+ expect(validator).not_to be_valid
412
+ expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid)
413
+ expect(validator).to be_sleeping
414
+
415
+ validator.reload
416
+ expect(validator).not_to be_running
417
+ expect(validator).to be_sleeping
418
+
419
+ validator.name = 'another name'
420
+ expect(validator).to be_valid
421
+ expect(validator.run!).to be_truthy
422
+ expect(validator).to be_running
423
+
424
+ validator.reload
425
+ expect(validator).to be_running
426
+ expect(validator).not_to be_sleeping
427
+ end
428
+
429
+ it 'should not store states for invalid models silently if configured' do
430
+ validator = MultipleSilentPersistor.create(:name => 'name')
431
+ expect(validator).to be_valid
432
+ expect(validator).to be_sleeping
433
+
434
+ validator.name = nil
435
+ expect(validator).not_to be_valid
436
+ expect(validator.run!).to be_falsey
437
+ expect(validator).to be_sleeping
438
+
439
+ validator.reload
440
+ expect(validator).not_to be_running
441
+ expect(validator).to be_sleeping
442
+
443
+ validator.name = 'another name'
444
+ expect(validator).to be_valid
445
+ expect(validator.run!).to be_truthy
446
+ expect(validator).to be_running
447
+
448
+ validator.reload
449
+ expect(validator).to be_running
450
+ expect(validator).not_to be_sleeping
451
+ end
452
+
453
+ it 'should store states for invalid models if configured' do
454
+ persistor = MultipleInvalidPersistor.create(:name => 'name')
455
+ expect(persistor).to be_valid
456
+ expect(persistor).to be_sleeping
457
+
458
+ persistor.name = nil
459
+ expect(persistor).not_to be_valid
460
+ expect(persistor.run!).to be_truthy
461
+ expect(persistor).to be_running
462
+
463
+ persistor = MultipleInvalidPersistor.find(persistor.id)
464
+ persistor.valid?
465
+ expect(persistor).to be_valid
466
+ expect(persistor).to be_running
467
+ expect(persistor).not_to be_sleeping
468
+
469
+ persistor.reload
470
+ expect(persistor).to be_running
471
+ expect(persistor).not_to be_sleeping
472
+ end
473
+
474
+ describe 'transactions' do
475
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
476
+ let(:transactor) { MultipleTransactor.create!(:name => 'transactor', :worker => worker) }
477
+
478
+ it 'should rollback all changes' do
479
+ expect(transactor).to be_sleeping
480
+ expect(worker.status).to eq('sleeping')
481
+
482
+ expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
483
+ expect(transactor).to be_running
484
+ expect(worker.reload.status).to eq('sleeping')
485
+ end
486
+
487
+ context "nested transactions" do
488
+ it "should rollback all changes in nested transaction" do
489
+ expect(transactor).to be_sleeping
490
+ expect(worker.status).to eq('sleeping')
491
+
492
+ Worker.transaction do
493
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
494
+ end
495
+
496
+ expect(transactor).to be_running
497
+ expect(worker.reload.status).to eq('sleeping')
498
+ end
499
+
500
+ it "should only rollback changes in the main transaction not the nested one" do
501
+ # change configuration to not require new transaction
502
+ AASM::StateMachineStore[MultipleTransactor][:left].config.requires_new_transaction = false
503
+
504
+ expect(transactor).to be_sleeping
505
+ expect(worker.status).to eq('sleeping')
506
+
507
+ Worker.transaction do
508
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
509
+ end
510
+
511
+ expect(transactor).to be_running
512
+ expect(worker.reload.status).to eq('running')
513
+ end
514
+ end
515
+
516
+ describe "after_commit callback" do
517
+ it "should fire :after_commit if transaction was successful" do
518
+ validator = MultipleValidator.create(:name => 'name')
519
+ expect(validator).to be_sleeping
520
+
521
+ validator.run!
522
+ expect(validator).to be_running
523
+ expect(validator.name).to eq("name changed")
524
+
525
+ validator.sleep!("sleeper")
526
+ expect(validator).to be_sleeping
527
+ expect(validator.name).to eq("sleeper")
528
+ end
529
+
530
+ it "should not fire :after_commit if transaction failed" do
531
+ validator = MultipleValidator.create(:name => 'name')
532
+ expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
533
+ expect(validator.name).to eq("name")
534
+ end
535
+
536
+ it "should not fire if not saving" do
537
+ validator = MultipleValidator.create(:name => 'name')
538
+ expect(validator).to be_sleeping
539
+ validator.run
540
+ expect(validator).to be_running
541
+ expect(validator.name).to eq("name")
542
+ end
543
+
544
+ end
545
+
546
+ context "when not persisting" do
547
+ it 'should not rollback all changes' do
548
+ expect(transactor).to be_sleeping
549
+ expect(worker.status).to eq('sleeping')
550
+
551
+ # Notice here we're calling "run" and not "run!" with a bang.
552
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
553
+ expect(transactor).to be_running
554
+ expect(worker.reload.status).to eq('running')
555
+ end
556
+
557
+ it 'should not create a database transaction' do
558
+ expect(transactor.class).not_to receive(:transaction)
559
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
560
+ end
561
+ end
562
+ end
563
+ end
564
+
565
+ describe "invalid states with persistence" do
566
+ it "should not store states" do
567
+ validator = MultipleValidator.create(:name => 'name')
568
+ validator.status = 'invalid_state'
569
+ expect(validator.save).to be_falsey
570
+ expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
571
+
572
+ validator.reload
573
+ expect(validator).to be_sleeping
574
+ end
575
+
576
+ it "should store invalid states if configured" do
577
+ persistor = MultipleInvalidPersistor.create(:name => 'name')
578
+ persistor.status = 'invalid_state'
579
+ expect(persistor.save).to be_truthy
580
+
581
+ persistor.reload
582
+ expect(persistor.status).to eq('invalid_state')
583
+ end
584
+ end
585
+
586
+ describe "complex example" do
587
+ it "works" do
588
+ record = ComplexActiveRecordExample.new
589
+ expect_aasm_states record, :one, :alpha
590
+
591
+ record.save!
592
+ expect_aasm_states record, :one, :alpha
593
+ record.reload
594
+ expect_aasm_states record, :one, :alpha
595
+
596
+ record.increment!
597
+ expect_aasm_states record, :two, :alpha
598
+ record.reload
599
+ expect_aasm_states record, :two, :alpha
600
+
601
+ record.level_up!
602
+ expect_aasm_states record, :two, :beta
603
+ record.reload
604
+ expect_aasm_states record, :two, :beta
605
+
606
+ record.increment!
607
+ expect { record.increment! }.to raise_error(AASM::InvalidTransition)
608
+ expect_aasm_states record, :three, :beta
609
+ record.reload
610
+ expect_aasm_states record, :three, :beta
611
+
612
+ record.level_up!
613
+ expect_aasm_states record, :three, :gamma
614
+ record.reload
615
+ expect_aasm_states record, :three, :gamma
616
+
617
+ record.level_down # without saving
618
+ expect_aasm_states record, :three, :beta
619
+ record.reload
620
+ expect_aasm_states record, :three, :gamma
621
+
622
+ record.level_down # without saving
623
+ expect_aasm_states record, :three, :beta
624
+ record.reset!
625
+ expect_aasm_states record, :one, :beta
626
+ end
627
+
628
+ def expect_aasm_states(record, left_state, right_state)
629
+ expect(record.aasm(:left).current_state).to eql left_state.to_sym
630
+ expect(record.left).to eql left_state.to_s
631
+ expect(record.aasm(:right).current_state).to eql right_state.to_sym
632
+ expect(record.right).to eql right_state.to_s
633
+ end
634
+ end
635
+ end