aasm 4.12.0 → 4.12.1

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/Appraisals +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +1 -1
  6. data/README.md +102 -8
  7. data/gemfiles/rails_4.2.gemfile +1 -0
  8. data/gemfiles/rails_5.0.gemfile +1 -0
  9. data/lib/aasm/base.rb +8 -0
  10. data/lib/aasm/core/event.rb +1 -1
  11. data/lib/aasm/core/transition.rb +1 -1
  12. data/lib/aasm/instance_base.rb +0 -2
  13. data/lib/aasm/minitest.rb +5 -0
  14. data/lib/aasm/minitest/allow_event.rb +13 -0
  15. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  16. data/lib/aasm/minitest/have_state.rb +13 -0
  17. data/lib/aasm/minitest/transition_from.rb +21 -0
  18. data/lib/aasm/minitest_spec.rb +15 -0
  19. data/lib/aasm/persistence/active_record_persistence.rb +25 -101
  20. data/lib/aasm/persistence/base.rb +7 -3
  21. data/lib/aasm/persistence/mongoid_persistence.rb +15 -60
  22. data/lib/aasm/persistence/orm.rb +142 -0
  23. data/lib/aasm/persistence/redis_persistence.rb +16 -11
  24. data/lib/aasm/persistence/sequel_persistence.rb +36 -63
  25. data/lib/aasm/version.rb +1 -1
  26. data/lib/generators/active_record/templates/migration.rb +1 -1
  27. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  28. data/lib/motion-aasm.rb +2 -0
  29. data/spec/models/active_record/complex_active_record_example.rb +5 -1
  30. data/spec/models/guardian_without_from_specified.rb +18 -0
  31. data/spec/models/namespaced_multiple_example.rb +14 -0
  32. data/spec/models/redis/complex_redis_example.rb +40 -0
  33. data/spec/models/redis/redis_multiple.rb +20 -0
  34. data/spec/models/redis/redis_simple.rb +20 -0
  35. data/spec/models/sequel/complex_sequel_example.rb +4 -3
  36. data/spec/models/sequel/invalid_persistor.rb +52 -0
  37. data/spec/models/sequel/sequel_multiple.rb +13 -13
  38. data/spec/models/sequel/sequel_simple.rb +13 -12
  39. data/spec/models/sequel/silent_persistor.rb +50 -0
  40. data/spec/models/sequel/transactor.rb +112 -0
  41. data/spec/models/sequel/validator.rb +93 -0
  42. data/spec/models/sequel/worker.rb +12 -0
  43. data/spec/spec_helpers/redis.rb +8 -0
  44. data/spec/unit/guard_spec.rb +17 -0
  45. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  46. data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
  47. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +4 -4
  48. data/spec/unit/persistence/active_record_persistence_spec.rb +4 -4
  49. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  50. data/spec/unit/persistence/redis_persistence_spec.rb +5 -25
  51. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +2 -2
  52. data/spec/unit/persistence/sequel_persistence_spec.rb +275 -2
  53. data/test/minitest_helper.rb +57 -0
  54. data/test/unit/minitest_matcher_test.rb +80 -0
  55. metadata +35 -2
@@ -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.blank?
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"
@@ -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,55 +52,21 @@ module AASM
50
52
 
51
53
  module InstanceMethods
52
54
 
53
- # Writes <tt>state</tt> to the state field and persists it to the database
54
- #
55
- # foo = Foo.find(1)
56
- # foo.aasm.current_state # => :opened
57
- # foo.close!
58
- # foo.aasm.current_state # => :closed
59
- # Foo.find(1).aasm.current_state # => :closed
60
- #
61
- # NOTE: intended to be called from an event
62
- def aasm_write_state(state, name=:default)
63
- old_value = read_attribute(self.class.aasm(name).attribute_name)
64
- aasm_write_attribute(state, name)
65
-
66
- success = if aasm_skipping_validations(name)
67
- value = aasm_raw_attribute_value(state, name)
68
- aasm_update_column(name, value)
69
- else
70
- self.save
71
- end
72
-
73
- unless success
74
- aasm_rollback(name, old_value)
75
- raise Mongoid::Errors::Validations.new(self) if aasm_whiny_persistence(name)
76
- end
55
+ private
77
56
 
78
- success
57
+ def aasm_save
58
+ self.save
79
59
  end
80
60
 
81
- # Writes <tt>state</tt> to the state field, but does not persist it to the database
82
- #
83
- # foo = Foo.find(1)
84
- # foo.aasm.current_state # => :opened
85
- # foo.close
86
- # foo.aasm.current_state # => :closed
87
- # Foo.find(1).aasm.current_state # => :opened
88
- # foo.save
89
- # foo.aasm.current_state # => :closed
90
- # Foo.find(1).aasm.current_state # => :closed
91
- #
92
- # NOTE: intended to be called from an event
93
- def aasm_write_state_without_persistence(state, name=:default)
94
- aasm_write_attribute(state, name)
61
+ def aasm_raise_invalid_record
62
+ raise Mongoid::Errors::Validations.new(self)
95
63
  end
96
64
 
97
- private
98
-
99
- def aasm_update_column(name, value)
100
- attribute_name = self.class.aasm(name).attribute_name
65
+ def aasm_supports_transactions?
66
+ false
67
+ end
101
68
 
69
+ def aasm_update_column(attribute_name, value)
102
70
  if Mongoid::VERSION.to_f >= 4
103
71
  set(Hash[attribute_name, value])
104
72
  else
@@ -108,25 +76,12 @@ module AASM
108
76
  true
109
77
  end
110
78
 
111
- def aasm_rollback(name, old_value)
112
- write_attribute(self.class.aasm(name).attribute_name, old_value)
113
- false
114
- end
115
-
116
- def aasm_skipping_validations(state_machine_name)
117
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
118
- end
119
-
120
- def aasm_whiny_persistence(state_machine_name)
121
- AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_persistence
122
- end
123
-
124
- def aasm_write_attribute(state, name=:default)
125
- write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
79
+ def aasm_read_attribute(name)
80
+ read_attribute(name)
126
81
  end
127
82
 
128
- def aasm_raw_attribute_value(state, _name=:default)
129
- state.to_s
83
+ def aasm_write_attribute(name, value)
84
+ write_attribute(name, value)
130
85
  end
131
86
 
132
87
  # Ensures that if the aasm_state column is nil and the record is new
@@ -0,0 +1,142 @@
1
+ module AASM
2
+ module Persistence
3
+ # This module adds transactional support for any database that supports it.
4
+ # This includes rollback capability and rollback/commit callbacks.
5
+ module ORM
6
+
7
+ # Writes <tt>state</tt> to the state column and persists it to the database
8
+ #
9
+ # foo = Foo.find(1)
10
+ # foo.aasm.current_state # => :opened
11
+ # foo.close!
12
+ # foo.aasm.current_state # => :closed
13
+ # Foo.find(1).aasm.current_state # => :closed
14
+ #
15
+ # NOTE: intended to be called from an event
16
+ def aasm_write_state(state, name=:default)
17
+ attribute_name = self.class.aasm(name).attribute_name
18
+ old_value = aasm_read_attribute(attribute_name)
19
+ aasm_write_state_attribute state, name
20
+
21
+ success = if aasm_skipping_validations(name)
22
+ aasm_update_column(attribute_name, aasm_raw_attribute_value(state, name))
23
+ else
24
+ aasm_save
25
+ end
26
+
27
+ unless success
28
+ aasm_rollback(name, old_value)
29
+ aasm_raise_invalid_record if aasm_whiny_persistence(name)
30
+ end
31
+
32
+ success
33
+ end
34
+
35
+ # Writes <tt>state</tt> to the state field, but does not persist it to the database
36
+ #
37
+ # foo = Foo.find(1)
38
+ # foo.aasm.current_state # => :opened
39
+ # foo.close
40
+ # foo.aasm.current_state # => :closed
41
+ # Foo.find(1).aasm.current_state # => :opened
42
+ # foo.save
43
+ # foo.aasm.current_state # => :closed
44
+ # Foo.find(1).aasm.current_state # => :closed
45
+ #
46
+ # NOTE: intended to be called from an event
47
+ def aasm_write_state_without_persistence(state, name=:default)
48
+ aasm_write_state_attribute(state, name)
49
+ end
50
+
51
+ private
52
+
53
+ # Save the record and return true if it succeeded/false otherwise.
54
+ def aasm_save
55
+ raise("Define #aasm_save_without_error in the AASM Persistence class.")
56
+ end
57
+
58
+ def aasm_raise_invalid_record
59
+ raise("Define #aasm_raise_invalid_record in the AASM Persistence class.")
60
+ end
61
+
62
+ # Update only the column without running validations.
63
+ def aasm_update_column(attribute_name, value)
64
+ raise("Define #aasm_update_column in the AASM Persistence class.")
65
+ end
66
+
67
+ def aasm_read_attribute(name)
68
+ raise("Define #aasm_read_attribute the AASM Persistence class.")
69
+ end
70
+
71
+ def aasm_write_attribute(name, value)
72
+ raise("Define #aasm_write_attribute in the AASM Persistence class.")
73
+ end
74
+
75
+ # Returns true or false if transaction completed successfully.
76
+ def aasm_transaction(requires_new, requires_lock)
77
+ raise("Define #aasm_transaction the AASM Persistence class.")
78
+ end
79
+
80
+ def aasm_supports_transactions?
81
+ true
82
+ end
83
+
84
+ def aasm_write_state_attribute(state, name=:default)
85
+ aasm_write_attribute(self.class.aasm(name).attribute_name, aasm_raw_attribute_value(state, name))
86
+ end
87
+
88
+ def aasm_raw_attribute_value(state, _name=:default)
89
+ state.to_s
90
+ end
91
+
92
+ def aasm_rollback(name, old_value)
93
+ aasm_write_attribute(self.class.aasm(name).attribute_name, old_value)
94
+ false
95
+ end
96
+
97
+ def aasm_whiny_persistence(state_machine_name)
98
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_persistence
99
+ end
100
+
101
+ def aasm_skipping_validations(state_machine_name)
102
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save
103
+ end
104
+
105
+ def requires_new?(state_machine_name)
106
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction
107
+ end
108
+
109
+ def requires_lock?(state_machine_name)
110
+ AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_lock
111
+ end
112
+
113
+ # Returns true if event was fired successfully and transaction completed.
114
+ def aasm_fire_event(state_machine_name, name, options, *args, &block)
115
+ if aasm_supports_transactions? && options[:persist]
116
+ event = self.class.aasm(state_machine_name).state_machine.events[name]
117
+ event.fire_callbacks(:before_transaction, self, *args)
118
+ event.fire_global_callbacks(:before_all_transactions, self, *args)
119
+
120
+ begin
121
+ success = aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do
122
+ super
123
+ end
124
+
125
+ if success
126
+ event.fire_callbacks(:after_commit, self, *args)
127
+ event.fire_global_callbacks(:after_all_commits, self, *args)
128
+ end
129
+
130
+ success
131
+ ensure
132
+ event.fire_callbacks(:after_transaction, self, *args)
133
+ event.fire_global_callbacks(:after_all_transactions, self, *args)
134
+ end
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ end # Transactional
141
+ end # Persistence
142
+ end # AASM
@@ -8,13 +8,12 @@ module AASM
8
8
  end
9
9
 
10
10
  module InstanceMethods
11
- # Add the inital value to intiializer
11
+ # Initialize with default values
12
12
  #
13
- # redis-objects removed the key from redis when set to nil
13
+ # Redis::Objects removes the key from Redis when set to `nil`
14
14
  def initialize(*args)
15
15
  super
16
- state = send(self.class.aasm.attribute_name)
17
- state.value = aasm.determine_state_name(self.class.aasm.initial_state)
16
+ aasm_ensure_initial_state
18
17
  end
19
18
  # Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
20
19
  #
@@ -68,8 +67,10 @@ module AASM
68
67
  # foo.aasm_state # => nil
69
68
  #
70
69
  def aasm_ensure_initial_state
71
- aasm.enter_initial_state if
72
- send(self.class.aasm.attribute_name).to_s.strip.empty?
70
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |name|
71
+ aasm_column = self.class.aasm(name).attribute_name
72
+ aasm(name).enter_initial_state if send(aasm_column).value.blank?
73
+ end
73
74
  end
74
75
 
75
76
  # Writes <tt>state</tt> to the state column and persists it to the database
@@ -81,12 +82,16 @@ module AASM
81
82
  # Foo[1].aasm.current_state # => :closed
82
83
  #
83
84
  # NOTE: intended to be called from an event
84
- def aasm_write_state(state)
85
- aasm_column = self.class.aasm.attribute_name
86
- self.send("#{aasm_column}=", state)
85
+ def aasm_write_state(state, name=:default)
86
+ aasm_column = self.class.aasm(name).attribute_name
87
+ send("#{aasm_column}").value = state
87
88
  end
88
89
 
89
90
  # Writes <tt>state</tt> to the state column, but does not persist it to the database
91
+ # (but actually it still does)
92
+ #
93
+ # With Redis::Objects it's not possible to skip persisting - it's not an ORM,
94
+ # it does not operate like an AR model and does not know how to postpone changes.
90
95
  #
91
96
  # foo = Foo[1]
92
97
  # foo.aasm.current_state # => :opened
@@ -98,8 +103,8 @@ module AASM
98
103
  # Foo[1].aasm.current_state # => :closed
99
104
  #
100
105
  # NOTE: intended to be called from an event
101
- def aasm_write_state_without_persistence(state)
102
- send("#{self.class.aasm.attribute_name}=", state)
106
+ def aasm_write_state_without_persistence(state, name=:default)
107
+ aasm_write_state(state, name)
103
108
  end
104
109
  end
105
110
  end
@@ -1,8 +1,10 @@
1
+ require 'aasm/persistence/orm'
1
2
  module AASM
2
3
  module Persistence
3
4
  module SequelPersistence
4
5
  def self.included(base)
5
6
  base.send(:include, AASM::Persistence::Base)
7
+ base.send(:include, AASM::Persistence::ORM)
6
8
  base.send(:include, AASM::Persistence::SequelPersistence::InstanceMethods)
7
9
  end
8
10
 
@@ -17,42 +19,42 @@ module AASM
17
19
  super
18
20
  end
19
21
 
20
- # Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
21
- #
22
- # If it's a new record, and the aasm state column is blank it returns the initial state
23
- #
24
- # class Foo < Sequel::Model
25
- # include AASM
26
- # aasm :column => :status do
27
- # state :opened
28
- # state :closed
29
- # end
30
- # end
31
- #
32
- # foo = Foo.new
33
- # foo.current_state # => :opened
34
- # foo.close
35
- # foo.current_state # => :closed
36
- #
37
- # foo = Foo[1]
38
- # foo.current_state # => :opened
39
- # foo.aasm_state = nil
40
- # foo.current_state # => nil
41
- #
42
- # NOTE: intended to be called from an event
43
- #
44
- # This allows for nil aasm states - be sure to add validation to your model
45
- def aasm_read_state(name=:default)
46
- state = send(self.class.aasm(name).attribute_name)
47
- if new? && state.to_s.strip.empty?
48
- aasm(name).determine_state_name(self.class.aasm(name).initial_state)
49
- elsif state.nil?
50
- nil
51
- else
52
- state.to_sym
22
+ def aasm_raise_invalid_record
23
+ raise Sequel::ValidationFailed.new(self)
24
+ end
25
+
26
+ def aasm_new_record?
27
+ new?
28
+ end
29
+
30
+ # Returns nil if fails silently
31
+ # http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-save
32
+ def aasm_save
33
+ !save(raise_on_failure: false).nil?
34
+ end
35
+
36
+ def aasm_read_attribute(name)
37
+ send(name)
38
+ end
39
+
40
+ def aasm_write_attribute(name, value)
41
+ send("#{name}=", value)
42
+ end
43
+
44
+ def aasm_transaction(requires_new, requires_lock)
45
+ self.class.db.transaction(savepoint: requires_new) do
46
+ if requires_lock
47
+ # http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-lock-21
48
+ requires_lock.is_a?(String) ? lock!(requires_lock) : lock!
49
+ end
50
+ yield
53
51
  end
54
52
  end
55
53
 
54
+ def aasm_update_column(attribute_name, value)
55
+ this.update(attribute_name => value)
56
+ end
57
+
56
58
  # Ensures that if the aasm_state column is nil and the record is new
57
59
  # that the initial state gets populated before validation on create
58
60
  #
@@ -72,39 +74,10 @@ module AASM
72
74
  AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
73
75
  aasm(state_machine_name).enter_initial_state if
74
76
  (new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
75
- send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
77
+ send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
76
78
  end
77
79
  end
78
80
 
79
- # Writes <tt>state</tt> to the state column and persists it to the database
80
- #
81
- # foo = Foo[1]
82
- # foo.aasm.current_state # => :opened
83
- # foo.close!
84
- # foo.aasm.current_state # => :closed
85
- # Foo[1].aasm.current_state # => :closed
86
- #
87
- # NOTE: intended to be called from an event
88
- def aasm_write_state state, name=:default
89
- aasm_column = self.class.aasm(name).attribute_name
90
- update_only({aasm_column => state.to_s}, aasm_column)
91
- end
92
-
93
- # Writes <tt>state</tt> to the state column, but does not persist it to the database
94
- #
95
- # foo = Foo[1]
96
- # foo.aasm.current_state # => :opened
97
- # foo.close
98
- # foo.aasm.current_state # => :closed
99
- # Foo[1].aasm.current_state # => :opened
100
- # foo.save
101
- # foo.aasm.current_state # => :closed
102
- # Foo[1].aasm.current_state # => :closed
103
- #
104
- # NOTE: intended to be called from an event
105
- def aasm_write_state_without_persistence state, name=:default
106
- send("#{self.class.aasm(name).attribute_name}=", state.to_s)
107
- end
108
81
  end
109
82
  end
110
83
  end