aasm 4.11.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 (194) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.travis.yml +56 -23
  5. data/Appraisals +67 -0
  6. data/CHANGELOG.md +112 -0
  7. data/CONTRIBUTING.md +24 -0
  8. data/Dockerfile +44 -0
  9. data/Gemfile +3 -21
  10. data/Gemfile.lock_old +151 -0
  11. data/LICENSE +1 -1
  12. data/README.md +540 -139
  13. data/Rakefile +6 -1
  14. data/TESTING.md +25 -0
  15. data/aasm.gemspec +5 -0
  16. data/docker-compose.yml +40 -0
  17. data/gemfiles/norails.gemfile +10 -0
  18. data/gemfiles/rails_4.2.gemfile +13 -11
  19. data/gemfiles/rails_4.2_mongoid_5.gemfile +8 -11
  20. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  21. data/gemfiles/rails_5.0.gemfile +11 -18
  22. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  23. data/gemfiles/rails_5.1.gemfile +14 -0
  24. data/gemfiles/rails_5.2.gemfile +14 -0
  25. data/lib/aasm/aasm.rb +40 -29
  26. data/lib/aasm/base.rb +61 -11
  27. data/lib/aasm/configuration.rb +10 -0
  28. data/lib/aasm/core/event.rb +45 -37
  29. data/lib/aasm/core/invoker.rb +129 -0
  30. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  31. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  32. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  33. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  34. data/lib/aasm/core/state.rb +22 -13
  35. data/lib/aasm/core/transition.rb +17 -69
  36. data/lib/aasm/dsl_helper.rb +24 -22
  37. data/lib/aasm/errors.rb +4 -6
  38. data/lib/aasm/instance_base.rb +22 -4
  39. data/lib/aasm/localizer.rb +13 -3
  40. data/lib/aasm/minitest/allow_event.rb +13 -0
  41. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  42. data/lib/aasm/minitest/have_state.rb +13 -0
  43. data/lib/aasm/minitest/transition_from.rb +21 -0
  44. data/lib/aasm/minitest.rb +5 -0
  45. data/lib/aasm/minitest_spec.rb +15 -0
  46. data/lib/aasm/persistence/active_record_persistence.rb +49 -105
  47. data/lib/aasm/persistence/base.rb +20 -5
  48. data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
  49. data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
  50. data/lib/aasm/persistence/mongoid_persistence.rb +26 -32
  51. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  52. data/lib/aasm/persistence/orm.rb +154 -0
  53. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  54. data/lib/aasm/persistence/redis_persistence.rb +16 -11
  55. data/lib/aasm/persistence/sequel_persistence.rb +36 -64
  56. data/lib/aasm/persistence.rb +3 -3
  57. data/lib/aasm/rspec/allow_event.rb +5 -1
  58. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  59. data/lib/aasm/rspec/transition_from.rb +5 -1
  60. data/lib/aasm/state_machine.rb +4 -2
  61. data/lib/aasm/state_machine_store.rb +5 -2
  62. data/lib/aasm/version.rb +1 -1
  63. data/lib/aasm.rb +5 -2
  64. data/lib/generators/aasm/orm_helpers.rb +6 -0
  65. data/lib/generators/active_record/aasm_generator.rb +3 -1
  66. data/lib/generators/active_record/templates/migration.rb +1 -1
  67. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  68. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  69. data/lib/motion-aasm.rb +3 -1
  70. data/spec/database.rb +20 -7
  71. data/spec/en.yml +0 -3
  72. data/spec/generators/active_record_generator_spec.rb +49 -40
  73. data/spec/generators/mongoid_generator_spec.rb +4 -6
  74. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  75. data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
  76. data/spec/localizer_test_model_new_style.yml +11 -0
  77. data/spec/models/active_record/active_record_callback.rb +93 -0
  78. data/spec/models/active_record/complex_active_record_example.rb +5 -1
  79. data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
  80. data/spec/models/{invalid_persistor.rb → active_record/invalid_persistor.rb} +0 -2
  81. data/spec/models/active_record/localizer_test_model.rb +11 -3
  82. data/spec/models/active_record/namespaced.rb +16 -0
  83. data/spec/models/active_record/person.rb +23 -0
  84. data/spec/models/{silent_persistor.rb → active_record/silent_persistor.rb} +0 -2
  85. data/spec/models/active_record/simple_new_dsl.rb +15 -0
  86. data/spec/models/active_record/timestamp_example.rb +16 -0
  87. data/spec/models/{transactor.rb → active_record/transactor.rb} +25 -2
  88. data/spec/models/{validator.rb → active_record/validator.rb} +0 -2
  89. data/spec/models/active_record/work.rb +3 -0
  90. data/spec/models/{worker.rb → active_record/worker.rb} +0 -0
  91. data/spec/models/callbacks/basic.rb +5 -2
  92. data/spec/models/callbacks/with_state_arg.rb +5 -1
  93. data/spec/models/callbacks/with_state_arg_multiple.rb +4 -1
  94. data/spec/models/default_state.rb +1 -1
  95. data/spec/models/guard_arguments_check.rb +17 -0
  96. data/spec/models/guard_with_params.rb +1 -1
  97. data/spec/models/guardian_without_from_specified.rb +18 -0
  98. data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
  99. data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
  100. data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
  101. data/spec/models/mongoid/validator_mongoid.rb +100 -0
  102. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
  103. data/spec/models/namespaced_multiple_example.rb +14 -0
  104. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  105. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  106. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  107. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  108. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  109. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  110. data/spec/models/{mongo_mapper/simple_mongo_mapper.rb → nobrainer/simple_no_brainer.rb} +8 -8
  111. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  112. data/spec/models/parametrised_event.rb +7 -0
  113. data/spec/models/{mongo_mapper/complex_mongo_mapper_example.rb → redis/complex_redis_example.rb} +8 -5
  114. data/spec/models/redis/redis_multiple.rb +20 -0
  115. data/spec/models/redis/redis_simple.rb +20 -0
  116. data/spec/models/sequel/complex_sequel_example.rb +4 -3
  117. data/spec/models/sequel/invalid_persistor.rb +52 -0
  118. data/spec/models/sequel/sequel_multiple.rb +13 -13
  119. data/spec/models/sequel/sequel_simple.rb +13 -12
  120. data/spec/models/sequel/silent_persistor.rb +50 -0
  121. data/spec/models/sequel/transactor.rb +112 -0
  122. data/spec/models/sequel/validator.rb +93 -0
  123. data/spec/models/sequel/worker.rb +12 -0
  124. data/spec/models/simple_example.rb +8 -0
  125. data/spec/models/simple_example_with_guard_args.rb +17 -0
  126. data/spec/models/simple_multiple_example.rb +12 -0
  127. data/spec/models/sub_class.rb +34 -0
  128. data/spec/models/timestamps_example.rb +19 -0
  129. data/spec/models/timestamps_with_named_machine_example.rb +13 -0
  130. data/spec/spec_helper.rb +15 -33
  131. data/spec/spec_helpers/active_record.rb +8 -0
  132. data/spec/spec_helpers/dynamoid.rb +35 -0
  133. data/spec/spec_helpers/mongoid.rb +26 -0
  134. data/spec/spec_helpers/nobrainer.rb +15 -0
  135. data/spec/spec_helpers/redis.rb +18 -0
  136. data/spec/spec_helpers/remove_warnings.rb +1 -0
  137. data/spec/spec_helpers/sequel.rb +7 -0
  138. data/spec/unit/abstract_class_spec.rb +27 -0
  139. data/spec/unit/api_spec.rb +79 -72
  140. data/spec/unit/callback_multiple_spec.rb +7 -3
  141. data/spec/unit/callbacks_spec.rb +37 -2
  142. data/spec/unit/complex_example_spec.rb +12 -3
  143. data/spec/unit/complex_multiple_example_spec.rb +20 -4
  144. data/spec/unit/event_multiple_spec.rb +1 -1
  145. data/spec/unit/event_spec.rb +29 -4
  146. data/spec/unit/exception_spec.rb +1 -1
  147. data/spec/unit/guard_arguments_check_spec.rb +9 -0
  148. data/spec/unit/guard_spec.rb +17 -0
  149. data/spec/unit/guard_with_params_spec.rb +4 -0
  150. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  151. data/spec/unit/inspection_multiple_spec.rb +9 -5
  152. data/spec/unit/inspection_spec.rb +7 -3
  153. data/spec/unit/invoker_spec.rb +189 -0
  154. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  155. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  156. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  157. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  158. data/spec/unit/localizer_spec.rb +85 -52
  159. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
  160. data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
  161. data/spec/unit/override_warning_spec.rb +8 -0
  162. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +468 -447
  163. data/spec/unit/persistence/active_record_persistence_spec.rb +639 -486
  164. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +4 -9
  165. data/spec/unit/persistence/dynamoid_persistence_spec.rb +4 -9
  166. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +83 -13
  167. data/spec/unit/persistence/mongoid_persistence_spec.rb +97 -13
  168. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  169. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  170. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  171. data/spec/unit/persistence/redis_persistence_spec.rb +8 -32
  172. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +6 -11
  173. data/spec/unit/persistence/sequel_persistence_spec.rb +278 -10
  174. data/spec/unit/rspec_matcher_spec.rb +9 -0
  175. data/spec/unit/simple_example_spec.rb +15 -0
  176. data/spec/unit/simple_multiple_example_spec.rb +28 -0
  177. data/spec/unit/state_spec.rb +23 -7
  178. data/spec/unit/subclassing_multiple_spec.rb +37 -2
  179. data/spec/unit/subclassing_spec.rb +17 -2
  180. data/spec/unit/timestamps_spec.rb +32 -0
  181. data/spec/unit/transition_spec.rb +1 -1
  182. data/test/minitest_helper.rb +57 -0
  183. data/test/unit/minitest_matcher_test.rb +80 -0
  184. metadata +213 -37
  185. data/callbacks.txt +0 -51
  186. data/gemfiles/rails_3.2_stable.gemfile +0 -15
  187. data/gemfiles/rails_4.0.gemfile +0 -16
  188. data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -16
  189. data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -17
  190. data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -163
  191. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
  192. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
  193. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -149
  194. data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -96
@@ -5,7 +5,7 @@ module AASM
5
5
  list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
6
6
  list
7
7
  end
8
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
8
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(event))
9
9
  end
10
10
 
11
11
  def human_state_name(klass, state)
@@ -14,7 +14,7 @@ module AASM
14
14
  list << item_for(klass, state, ancestor, :old_style => true)
15
15
  list
16
16
  end
17
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
17
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(state))
18
18
  end
19
19
 
20
20
  private
@@ -46,8 +46,18 @@ module AASM
46
46
  end
47
47
 
48
48
  def ancestors_list(klass)
49
+ has_active_record_base = defined?(::ActiveRecord::Base)
49
50
  klass.ancestors.select do |ancestor|
50
- ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
51
+ not_active_record_base = has_active_record_base ? (ancestor != ::ActiveRecord::Base) : true
52
+ ancestor.respond_to?(:model_name) && not_active_record_base
53
+ end
54
+ end
55
+
56
+ def default_display_name(object) # Can use better arguement name
57
+ if object.respond_to?(:default_display_name)
58
+ object.default_display_name
59
+ else
60
+ object.to_s.gsub(/_/, ' ').capitalize
51
61
  end
52
62
  end
53
63
  end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_event_allowed(object, event, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).may_fire_event?(event),
5
+ "Expected that the event :#{event} would be allowed (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_event_allowed(object, event, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).may_fire_event?(event),
11
+ "Expected that the event :#{event} would not be allowed (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_transition_to_allowed(object, to_state, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).states(permitted: true).include?(to_state),
5
+ "Expected that the state :#{to_state} would be reachable (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_transition_to_allowed(object, to_state, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).states(permitted: true).include?(to_state),
11
+ "Expected that the state :#{to_state} would be reachable (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Minitest::Assertions
2
+ def assert_have_state(object, state, options = {})
3
+ state_machine_name = options.fetch(:on, :default)
4
+ assert object.aasm(state_machine_name).current_state == state,
5
+ "Expected that :#{object.aasm(state_machine_name).current_state} would be :#{state} (on :#{state_machine_name})"
6
+ end
7
+
8
+ def refute_have_state(object, state, options = {})
9
+ state_machine_name = options.fetch(:on, :default)
10
+ refute object.aasm(state_machine_name).current_state == state,
11
+ "Expected that :#{object.aasm(state_machine_name).current_state} would be :#{state} (on :#{state_machine_name})"
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module Minitest::Assertions
2
+ def assert_transitions_from(object, from_state, *args)
3
+ options = args.first
4
+ options[:on] ||= :default
5
+ assert _transitions_from?(object, from_state, args, options),
6
+ "Expected transition state to :#{options[:to]} from :#{from_state} on event :#{options[:on_event]}, (on :#{options[:on]})"
7
+ end
8
+
9
+ def refute_transitions_from(object, from_state, *args)
10
+ options = args.first
11
+ options[:on] ||= :default
12
+ refute _transitions_from?(object, from_state, args, options),
13
+ "Expected transition state to :#{options[:to]} from :#{from_state} on event :#{options[:on_event]}, (on :#{options[:on]})"
14
+ end
15
+
16
+ def _transitions_from?(object, from_state, args, options)
17
+ state_machine_name = options[:on]
18
+ object.aasm(state_machine_name).current_state = from_state.to_sym
19
+ object.send(options[:on_event], *args) && options[:to].to_sym == object.aasm(state_machine_name).current_state
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ Minitest = MiniTest unless defined? Minitest
2
+ # relative-require all minitest_spec files
3
+ Dir[File.dirname(__FILE__) + '/minitest/*.rb'].each do |file|
4
+ require 'aasm/minitest/' + File.basename(file, File.extname(file))
5
+ end
@@ -0,0 +1,15 @@
1
+ require 'aasm/minitest'
2
+
3
+ module Minitest::Expectations
4
+ AASM.infect_an_assertion :assert_transitions_from, :must_transition_from, :do_not_flip
5
+ AASM.infect_an_assertion :refute_transitions_from, :wont_transition_from, :do_not_flip
6
+
7
+ AASM.infect_an_assertion :assert_transition_to_allowed, :must_allow_transition_to, :do_not_flip
8
+ AASM.infect_an_assertion :refute_transition_to_allowed, :wont_allow_transition_to, :do_not_flip
9
+
10
+ AASM.infect_an_assertion :assert_have_state, :must_have_state, :do_not_flip
11
+ AASM.infect_an_assertion :refute_have_state, :wont_have_state, :do_not_flip
12
+
13
+ AASM.infect_an_assertion :assert_event_allowed, :must_allow_event, :do_not_flip
14
+ AASM.infect_an_assertion :refute_event_allowed, :wont_allow_event, :do_not_flip
15
+ end
@@ -1,3 +1,4 @@
1
+ require 'aasm/persistence/orm'
1
2
  module AASM
2
3
  module Persistence
3
4
  module ActiveRecordPersistence
@@ -28,6 +29,7 @@ module AASM
28
29
  #
29
30
  def self.included(base)
30
31
  base.send(:include, AASM::Persistence::Base)
32
+ base.send(:include, AASM::Persistence::ORM)
31
33
  base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
32
34
  base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
33
35
 
@@ -39,14 +41,15 @@ module AASM
39
41
 
40
42
  module ClassMethods
41
43
  def aasm_create_scope(state_machine_name, scope_name)
42
- conditions = {
43
- table_name => { aasm(state_machine_name).attribute_name => scope_name.to_s }
44
- }
45
44
  if ActiveRecord::VERSION::MAJOR >= 3
45
+ conditions = { aasm(state_machine_name).attribute_name => scope_name.to_s }
46
46
  class_eval do
47
- scope scope_name, lambda { where(conditions) }
47
+ scope scope_name, lambda { where(table_name => conditions) }
48
48
  end
49
49
  else
50
+ conditions = {
51
+ table_name => { aasm(state_machine_name).attribute_name => scope_name.to_s }
52
+ }
50
53
  class_eval do
51
54
  named_scope scope_name, :conditions => conditions
52
55
  end
@@ -56,67 +59,59 @@ module AASM
56
59
 
57
60
  module InstanceMethods
58
61
 
59
- # Writes <tt>state</tt> to the state column and persists it to the database
60
- #
61
- # foo = Foo.find(1)
62
- # foo.aasm.current_state # => :opened
63
- # foo.close!
64
- # foo.aasm.current_state # => :closed
65
- # Foo.find(1).aasm.current_state # => :closed
66
- #
67
- # NOTE: intended to be called from an event
68
- def aasm_write_state(state, name=:default)
69
- old_value = read_attribute(self.class.aasm(name).attribute_name)
70
- aasm_write_attribute state, name
71
-
72
- success = if aasm_skipping_validations(name)
73
- value = aasm_raw_attribute_value(state, name)
74
- aasm_update_column(name, value)
75
- else
76
- self.save
77
- end
62
+ private
63
+
64
+ def aasm_execute_after_commit
65
+ begin
66
+ require 'after_commit_everywhere'
67
+ raise LoadError unless Gem::Version.new(::AfterCommitEverywhere::VERSION) >= Gem::Version.new('0.1.5')
78
68
 
79
- unless success
80
- aasm_rollback(name, old_value)
81
- raise ActiveRecord::RecordInvalid.new(self) if aasm_whiny_persistence(name)
69
+ self.extend ::AfterCommitEverywhere
70
+ after_commit do
71
+ yield
72
+ end
73
+ rescue LoadError
74
+ warn <<-MSG
75
+ [DEPRECATION] :after_commit AASM callback is not safe in terms of race conditions and redundant calls.
76
+ Please add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile in order to fix that.
77
+ MSG
78
+ yield
82
79
  end
80
+ end
83
81
 
84
- success
82
+ def aasm_raise_invalid_record
83
+ raise ActiveRecord::RecordInvalid.new(self)
85
84
  end
86
85
 
87
- # Writes <tt>state</tt> to the state column, but does not persist it to the database
88
- #
89
- # foo = Foo.find(1)
90
- # foo.aasm.current_state # => :opened
91
- # foo.close
92
- # foo.aasm.current_state # => :closed
93
- # Foo.find(1).aasm.current_state # => :opened
94
- # foo.save
95
- # foo.aasm.current_state # => :closed
96
- # Foo.find(1).aasm.current_state # => :closed
97
- #
98
- # NOTE: intended to be called from an event
99
- def aasm_write_state_without_persistence(state, name=:default)
100
- aasm_write_attribute(state, name)
86
+ def aasm_save
87
+ self.save
101
88
  end
102
89
 
103
- private
90
+ def aasm_update_column(attribute_name, value)
91
+ self.class.unscoped.where(self.class.primary_key => self.id).update_all(attribute_name => value) == 1
92
+ end
104
93
 
105
- def aasm_update_column(name, value)
106
- self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm(name).attribute_name => value) == 1
94
+ def aasm_read_attribute(name)
95
+ read_attribute(name)
107
96
  end
108
97
 
109
- def aasm_rollback(name, old_value)
110
- write_attribute(self.class.aasm(name).attribute_name, old_value)
111
- false
98
+ def aasm_write_attribute(name, value)
99
+ write_attribute(name, value)
100
+ end
101
+
102
+ def aasm_transaction(requires_new, requires_lock)
103
+ self.class.transaction(:requires_new => requires_new) do
104
+ lock!(requires_lock) if requires_lock
105
+ yield
106
+ end
112
107
  end
113
108
 
114
109
  def aasm_enum(name=:default)
115
110
  case AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
116
- when false then nil
117
- when true then aasm_guess_enum_method(name)
118
- when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
119
- else AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
111
+ when false then nil
112
+ when true then aasm_guess_enum_method(name)
113
+ when nil then aasm_guess_enum_method(name) if aasm_column_looks_like_enum(name)
114
+ else AASM::StateMachineStore.fetch(self.class, true).machine(name).config.enum
120
115
  end
121
116
  end
122
117
 
@@ -131,23 +126,11 @@ module AASM
131
126
  self.class.aasm(name).attribute_name.to_s.pluralize.to_sym
132
127
  end
133
128
 
134
- def aasm_skipping_validations(state_machine_name)
135
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
136
- end
137
-
138
- def aasm_whiny_persistence(state_machine_name)
139
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_persistence
140
- end
141
-
142
- def aasm_write_attribute(state, name=:default)
143
- write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
144
- end
145
-
146
129
  def aasm_raw_attribute_value(state, name=:default)
147
130
  if aasm_enum(name)
148
131
  self.class.send(aasm_enum(name))[state]
149
132
  else
150
- state.to_s
133
+ super
151
134
  end
152
135
  end
153
136
 
@@ -176,47 +159,8 @@ module AASM
176
159
 
177
160
  def aasm_column_is_blank?(state_machine_name)
178
161
  attribute_name = self.class.aasm(state_machine_name).attribute_name
179
- attribute_names.include?(attribute_name.to_s) && send(attribute_name).blank?
180
- end
181
-
182
- def aasm_fire_event(state_machine_name, name, options, *args, &block)
183
- event = self.class.aasm(state_machine_name).state_machine.events[name]
184
-
185
- if options[:persist]
186
- event.fire_callbacks(:before_transaction, self, *args)
187
- event.fire_global_callbacks(:before_all_transactions, self, *args)
188
- end
189
-
190
- begin
191
- success = if options[:persist]
192
- self.class.transaction(:requires_new => requires_new?(state_machine_name)) do
193
- lock!(requires_lock?(state_machine_name)) if requires_lock?(state_machine_name)
194
- super
195
- end
196
- else
197
- super
198
- end
199
-
200
- if options[:persist] && success
201
- event.fire_callbacks(:after_commit, self, *args)
202
- event.fire_global_callbacks(:after_all_commits, self, *args)
203
- end
204
- ensure
205
- if options[:persist]
206
- event.fire_callbacks(:after_transaction, self, *args)
207
- event.fire_global_callbacks(:after_all_transactions, self, *args)
208
- end
209
- end
210
-
211
- success
212
- end
213
-
214
- def requires_new?(state_machine_name)
215
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction
216
- end
217
-
218
- def requires_lock?(state_machine_name)
219
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_lock
162
+ attribute_names.include?(attribute_name.to_s) &&
163
+ (send(attribute_name).respond_to?(:empty?) ? !!send(attribute_name).empty? : !send(attribute_name))
220
164
  end
221
165
 
222
166
  def aasm_validate_states
@@ -34,13 +34,17 @@ module AASM
34
34
  # This allows for nil aasm states - be sure to add validation to your model
35
35
  def aasm_read_state(name=:default)
36
36
  state = send(self.class.aasm(name).attribute_name)
37
- if new_record?
38
- state.blank? ? aasm(name).determine_state_name(self.class.aasm(name).initial_state) : state.to_sym
37
+ if !state || state.empty?
38
+ aasm_new_record? ? aasm(name).determine_state_name(self.class.aasm(name).initial_state) : nil
39
39
  else
40
- state.blank? ? nil : state.to_sym
40
+ state.to_sym
41
41
  end
42
42
  end
43
43
 
44
+ def aasm_new_record?
45
+ new_record?
46
+ end
47
+
44
48
  module ClassMethods
45
49
  def aasm_column(attribute_name=nil)
46
50
  warn "[DEPRECATION] aasm_column is deprecated. Use aasm.attribute_name instead"
@@ -55,7 +59,9 @@ module AASM
55
59
  # make sure to create a (named) scope for each state
56
60
  def state_with_scope(*args)
57
61
  names = state_without_scope(*args)
58
- names.each { |name| create_scope(name) if create_scope?(name) }
62
+ names.each do |name|
63
+ create_scopes(name)
64
+ end
59
65
  end
60
66
  alias_method :state_without_scope, :state
61
67
  alias_method :state, :state_with_scope
@@ -67,7 +73,16 @@ module AASM
67
73
  end
68
74
 
69
75
  def create_scope(name)
70
- @klass.aasm_create_scope(@name, name)
76
+ @klass.aasm_create_scope(@name, name) if create_scope?(name)
77
+ end
78
+
79
+ def create_scopes(name)
80
+ if namespace?
81
+ # Create default scopes even when namespace? for backward compatibility
82
+ namepaced_name = "#{namespace}_#{name}"
83
+ create_scope(namepaced_name)
84
+ end
85
+ create_scope(name)
71
86
  end
72
87
  end # Base
73
88
 
@@ -77,7 +77,8 @@ module AASM
77
77
  #
78
78
  def aasm_ensure_initial_state
79
79
  AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
80
- send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s) if send(self.class.aasm(state_machine_name).attribute_name).blank?
80
+ next if !send(self.class.aasm(state_machine_name).attribute_name) || send(self.class.aasm(state_machine_name).attribute_name).empty?
81
+ send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s)
81
82
  end
82
83
  end
83
84
  end # InstanceMethods
@@ -83,7 +83,7 @@ module AASM
83
83
  #
84
84
  def aasm_ensure_initial_state
85
85
  AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
86
- aasm(state_machine_name).enter_initial_state if send(self.class.aasm(state_machine_name).attribute_name).blank?
86
+ aasm(state_machine_name).enter_initial_state if !send(self.class.aasm(state_machine_name).attribute_name) || send(self.class.aasm(state_machine_name).attribute_name).empty?
87
87
  end
88
88
  end
89
89
  end # InstanceMethods
@@ -1,3 +1,4 @@
1
+ require 'aasm/persistence/orm'
1
2
  module AASM
2
3
  module Persistence
3
4
  module MongoidPersistence
@@ -30,6 +31,7 @@ module AASM
30
31
  #
31
32
  def self.included(base)
32
33
  base.send(:include, AASM::Persistence::Base)
34
+ base.send(:include, AASM::Persistence::ORM)
33
35
  base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
34
36
  base.extend AASM::Persistence::MongoidPersistence::ClassMethods
35
37
 
@@ -50,45 +52,37 @@ module AASM
50
52
 
51
53
  module InstanceMethods
52
54
 
53
- # Writes <tt>state</tt> to the state column and persists it to the database
54
- # using update_attribute (which bypasses validation)
55
- #
56
- # foo = Foo.find(1)
57
- # foo.aasm.current_state # => :opened
58
- # foo.close!
59
- # foo.aasm.current_state # => :closed
60
- # Foo.find(1).aasm.current_state # => :closed
61
- #
62
- # NOTE: intended to be called from an event
63
- def aasm_write_state(state, name=:default)
64
- old_value = read_attribute(self.class.aasm(name).attribute_name)
65
- write_attribute(self.class.aasm(name).attribute_name, state.to_s)
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
66
64
 
67
- unless self.save(:validate => false)
68
- write_attribute(self.class.aasm(name).attribute_name, old_value)
69
- return false
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)
70
74
  end
71
75
 
72
76
  true
73
77
  end
74
78
 
75
- # Writes <tt>state</tt> to the state column, but does not persist it to the database
76
- #
77
- # foo = Foo.find(1)
78
- # foo.aasm.current_state # => :opened
79
- # foo.close
80
- # foo.aasm.current_state # => :closed
81
- # Foo.find(1).aasm.current_state # => :opened
82
- # foo.save
83
- # foo.aasm.current_state # => :closed
84
- # Foo.find(1).aasm.current_state # => :closed
85
- #
86
- # NOTE: intended to be called from an event
87
- def aasm_write_state_without_persistence(state, name=:default)
88
- write_attribute(self.class.aasm(name).attribute_name, state.to_s)
79
+ def aasm_read_attribute(name)
80
+ read_attribute(name)
89
81
  end
90
82
 
91
- private
83
+ def aasm_write_attribute(name, value)
84
+ write_attribute(name, value)
85
+ end
92
86
 
93
87
  # Ensures that if the aasm_state column is nil and the record is new
94
88
  # that the initial state gets populated before validation on create
@@ -112,7 +106,7 @@ module AASM
112
106
  # mongoid has_many relationship does not load child object attributes when
113
107
  # only ids are loaded, for example parent.child_ids will not load child object attributes.
114
108
  # This feature is introduced in mongoid > 4.
115
- if attribute_names.include?(attribute_name) && attributes[attribute_name].blank?
109
+ if attribute_names.include?(attribute_name) && !attributes[attribute_name] || attributes[attribute_name].empty?
116
110
  # attribute_missing? is defined in mongoid > 4
117
111
  return if Mongoid::VERSION.to_f >= 4 && attribute_missing?(attribute_name)
118
112
  send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s)
@@ -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