aasm 4.12.0 → 4.12.1

Sign up to get free protection for your applications and to get access to all the features.
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