aasm 4.9.0 → 4.10.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -4
  3. data/CHANGELOG.md +15 -0
  4. data/Gemfile +3 -0
  5. data/README.md +27 -11
  6. data/gemfiles/rails_4.0.gemfile +1 -0
  7. data/gemfiles/rails_4.1.gemfile +1 -0
  8. data/gemfiles/rails_4.2_mongo_mapper.gemfile +1 -0
  9. data/lib/aasm.rb +3 -1
  10. data/lib/aasm/aasm.rb +13 -9
  11. data/lib/aasm/base.rb +67 -28
  12. data/lib/aasm/configuration.rb +3 -0
  13. data/lib/aasm/core/event.rb +8 -1
  14. data/lib/aasm/core/transition.rb +32 -4
  15. data/lib/aasm/errors.rb +4 -4
  16. data/lib/aasm/persistence.rb +14 -1
  17. data/lib/aasm/persistence/active_record_persistence.rb +27 -13
  18. data/lib/aasm/persistence/base.rb +2 -44
  19. data/lib/aasm/persistence/core_data_query_persistence.rb +93 -0
  20. data/lib/aasm/persistence/dynamoid_persistence.rb +2 -4
  21. data/lib/aasm/persistence/mongo_mapper_persistence.rb +15 -9
  22. data/lib/aasm/persistence/mongoid_persistence.rb +24 -4
  23. data/lib/aasm/persistence/redis_persistence.rb +107 -0
  24. data/lib/aasm/persistence/sequel_persistence.rb +1 -3
  25. data/lib/aasm/state_machine.rb +1 -9
  26. data/lib/aasm/state_machine_store.rb +73 -0
  27. data/lib/aasm/version.rb +1 -1
  28. data/lib/generators/active_record/templates/migration_existing.rb +2 -6
  29. data/lib/motion-aasm.rb +35 -0
  30. data/spec/models/callbacks/basic.rb +12 -2
  31. data/spec/models/callbacks/guard_within_block.rb +2 -1
  32. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +2 -1
  33. data/spec/models/callbacks/with_args.rb +2 -1
  34. data/spec/models/callbacks/with_state_arg.rb +6 -2
  35. data/spec/models/mongoid/mongoid_relationships.rb +26 -0
  36. data/spec/models/namespaced_multiple_example.rb +28 -0
  37. data/spec/models/parametrised_event.rb +9 -3
  38. data/spec/models/states_on_one_line_example.rb +8 -0
  39. data/spec/spec_helper.rb +1 -0
  40. data/spec/unit/api_spec.rb +20 -0
  41. data/spec/unit/callbacks_spec.rb +17 -5
  42. data/spec/unit/event_spec.rb +19 -1
  43. data/spec/unit/exception_spec.rb +11 -0
  44. data/spec/unit/memory_leak_spec.rb +1 -1
  45. data/spec/unit/namespaced_multiple_example_spec.rb +53 -0
  46. data/spec/unit/override_warning_spec.rb +43 -0
  47. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +1 -1
  48. data/spec/unit/persistence/active_record_persistence_spec.rb +1 -1
  49. data/spec/unit/persistence/mongoid_persistence_spec.rb +11 -0
  50. data/spec/unit/persistence/redis_persistence_spec.rb +77 -0
  51. data/spec/unit/readme_spec.rb +1 -2
  52. data/spec/unit/states_on_one_line_example_spec.rb +16 -0
  53. data/spec/unit/transition_spec.rb +60 -1
  54. metadata +22 -2
@@ -1,5 +1,3 @@
1
- require_relative 'base'
2
-
3
1
  module AASM
4
2
  module Persistence
5
3
  module MongoidPersistence
@@ -33,10 +31,23 @@ module AASM
33
31
  def self.included(base)
34
32
  base.send(:include, AASM::Persistence::Base)
35
33
  base.send(:include, AASM::Persistence::MongoidPersistence::InstanceMethods)
34
+ base.extend AASM::Persistence::MongoidPersistence::ClassMethods
36
35
 
37
36
  base.after_initialize :aasm_ensure_initial_state
38
37
  end
39
38
 
39
+ module ClassMethods
40
+ def aasm_create_scope(state_machine_name, scope_name)
41
+ scope_options = lambda {
42
+ send(
43
+ :where,
44
+ { aasm(state_machine_name).attribute_name.to_sym => scope_name.to_s }
45
+ )
46
+ }
47
+ send(:scope, scope_name, scope_options)
48
+ end
49
+ end
50
+
40
51
  module InstanceMethods
41
52
 
42
53
  # Writes <tt>state</tt> to the state column and persists it to the database
@@ -95,8 +106,17 @@ module AASM
95
106
  # foo.aasm_state # => nil
96
107
  #
97
108
  def aasm_ensure_initial_state
98
- AASM::StateMachine[self.class].keys.each do |state_machine_name|
99
- 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?
109
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
110
+ attribute_name = self.class.aasm(state_machine_name).attribute_name.to_s
111
+ # Do not load initial state when object attributes are not loaded,
112
+ # mongoid has_many relationship does not load child object attributes when
113
+ # only ids are loaded, for example parent.child_ids will not load child object attributes.
114
+ # This feature is introduced in mongoid > 4.
115
+ if attribute_names.include?(attribute_name) && attributes[attribute_name].blank?
116
+ # attribute_missing? is defined in mongoid > 4
117
+ return if Mongoid::VERSION.to_f >= 4 && attribute_missing?(attribute_name)
118
+ send("#{self.class.aasm(state_machine_name).attribute_name}=", aasm(state_machine_name).enter_initial_state.to_s)
119
+ end
100
120
  end
101
121
  end
102
122
  end # InstanceMethods
@@ -0,0 +1,107 @@
1
+ module AASM
2
+ module Persistence
3
+ module RedisPersistence
4
+
5
+ def self.included(base)
6
+ base.send(:include, AASM::Persistence::Base)
7
+ base.send(:include, AASM::Persistence::RedisPersistence::InstanceMethods)
8
+ end
9
+
10
+ module InstanceMethods
11
+ # Add the inital value to intiializer
12
+ #
13
+ # redis-objects removed the key from redis when set to nil
14
+ def initialize(*args)
15
+ super
16
+ state = send(self.class.aasm.attribute_name)
17
+ state.value = aasm.determine_state_name(self.class.aasm.initial_state)
18
+ end
19
+ # Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
20
+ #
21
+ # If it's a new record, and the aasm state column is blank it returns the initial state
22
+ #
23
+ # class Foo
24
+ # include Redis::Objects
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
+
48
+ if state.value.nil?
49
+ nil
50
+ else
51
+ state.value.to_sym
52
+ end
53
+ end
54
+
55
+ # Ensures that if the aasm_state column is nil and the record is new
56
+ # that the initial state gets populated before validation on create
57
+ #
58
+ # foo = Foo.new
59
+ # foo.aasm_state # => nil
60
+ # foo.valid?
61
+ # foo.aasm_state # => "open" (where :open is the initial state)
62
+ #
63
+ #
64
+ # foo = Foo.find(:first)
65
+ # foo.aasm_state # => 1
66
+ # foo.aasm_state = nil
67
+ # foo.valid?
68
+ # foo.aasm_state # => nil
69
+ #
70
+ def aasm_ensure_initial_state
71
+ aasm.enter_initial_state if
72
+ send(self.class.aasm.attribute_name).to_s.strip.empty?
73
+ end
74
+
75
+ # Writes <tt>state</tt> to the state column and persists it to the database
76
+ #
77
+ # foo = Foo[1]
78
+ # foo.aasm.current_state # => :opened
79
+ # foo.close!
80
+ # foo.aasm.current_state # => :closed
81
+ # Foo[1].aasm.current_state # => :closed
82
+ #
83
+ # 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)
87
+ end
88
+
89
+ # Writes <tt>state</tt> to the state column, but does not persist it to the database
90
+ #
91
+ # foo = Foo[1]
92
+ # foo.aasm.current_state # => :opened
93
+ # foo.close
94
+ # foo.aasm.current_state # => :closed
95
+ # Foo[1].aasm.current_state # => :opened
96
+ # foo.save
97
+ # foo.aasm.current_state # => :closed
98
+ # Foo[1].aasm.current_state # => :closed
99
+ #
100
+ # NOTE: intended to be called from an event
101
+ def aasm_write_state_without_persistence(state)
102
+ send("#{self.class.aasm.attribute_name}=", state)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,5 +1,3 @@
1
- require_relative 'base'
2
-
3
1
  module AASM
4
2
  module Persistence
5
3
  module SequelPersistence
@@ -71,7 +69,7 @@ module AASM
71
69
  # foo.aasm_state # => nil
72
70
  #
73
71
  def aasm_ensure_initial_state
74
- AASM::StateMachine[self.class].keys.each do |state_machine_name|
72
+ AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
75
73
  aasm(state_machine_name).enter_initial_state if
76
74
  (new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
77
75
  send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
@@ -1,14 +1,6 @@
1
1
  module AASM
2
2
  class StateMachine
3
-
4
- # the following two methods provide the storage of all state machines
5
- def self.[](klass)
6
- (@machines ||= {})[klass.to_s]
7
- end
8
-
9
- def self.[]=(klass, machine)
10
- (@machines ||= {})[klass.to_s] = machine
11
- end
3
+ # the following four methods provide the storage of all state machines
12
4
 
13
5
  attr_accessor :states, :events, :initial_state, :config, :name, :global_callbacks
14
6
 
@@ -0,0 +1,73 @@
1
+ module AASM
2
+ class StateMachineStore
3
+ class << self
4
+ def stores
5
+ @stores ||= {}
6
+ end
7
+
8
+ # do not overwrite existing state machines, which could have been created by
9
+ # inheritance, see AASM::ClassMethods method inherited
10
+ def register(klass, overwrite = false, state_machine = nil)
11
+ raise "Cannot register #{klass}" unless klass.is_a?(Class)
12
+
13
+ case name = template = overwrite
14
+ when FalseClass then stores[klass.to_s] ||= new
15
+ when TrueClass then stores[klass.to_s] = new
16
+ when Class then stores[klass.to_s] = stores[template.to_s].clone
17
+ when Symbol then stores[klass.to_s].register(name, state_machine)
18
+ when String then stores[klass.to_s].register(name, state_machine)
19
+ else raise "Don't know what to do with #{overwrite}"
20
+ end
21
+ end
22
+ alias_method :[]=, :register
23
+
24
+ def fetch(klass, fallback = nil)
25
+ stores[klass.to_s] || fallback && begin
26
+ match = klass.ancestors.find do |ancestor|
27
+ ancestor.include? AASM and stores[ancestor.to_s]
28
+ end
29
+
30
+ stores[match.to_s]
31
+ end
32
+ end
33
+ alias_method :[], :fetch
34
+
35
+ def unregister(klass)
36
+ stores.delete(klass.to_s)
37
+ end
38
+ end
39
+
40
+ def initialize
41
+ @machines = {}
42
+ end
43
+
44
+ def clone
45
+ StateMachineStore.new.tap do |store|
46
+ @machines.each_pair do |name, machine|
47
+ store.register(name, machine.clone)
48
+ end
49
+ end
50
+ end
51
+
52
+ def machine(name)
53
+ @machines[name.to_s]
54
+ end
55
+ alias_method :[], :machine
56
+
57
+ def machine_names
58
+ @machines.keys
59
+ end
60
+ alias_method :keys, :machine_names
61
+
62
+ def register(name, machine, force = false)
63
+ raise "Cannot use #{name.inspect} for machine name" unless name.is_a?(Symbol) or name.is_a?(String)
64
+ raise "Cannot use #{machine.inspect} as a machine" unless machine.is_a?(AASM::StateMachine)
65
+
66
+ if force
67
+ @machines[name.to_s] = machine
68
+ else
69
+ @machines[name.to_s] ||= machine
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "4.9.0"
2
+ VERSION = "4.10.0"
3
3
  end
@@ -1,9 +1,5 @@
1
- class AddAASMTo<%= table_name.camelize %> < ActiveRecord::Migration
2
- def self.up
1
+ class Add<%= column_name.camelize %>To<%= table_name.camelize %> < ActiveRecord::Migration
2
+ def change
3
3
  add_column :<%= table_name %>, :<%= column_name %>, :string
4
4
  end
5
-
6
- def self.down
7
- remove_column :<%= table_name %>, :<%= column_name %>
8
- end
9
5
  end
@@ -0,0 +1,35 @@
1
+ unless defined?(Motion::Project::App)
2
+ raise "This must be required from within a RubyMotion Rakefile"
3
+ end
4
+
5
+ file_dependencies = {
6
+ 'aasm/aasm.rb' => ['aasm/persistence.rb'],
7
+ 'aasm/persistence.rb' => ['aasm/persistence/plain_persistence.rb', 'aasm/persistence/core_data_query_persistence.rb'],
8
+ 'aasm/persistence/base.rb' => ['aasm/base.rb'],
9
+ 'aasm/persistence/core_data_query_persistence.rb' => ['aasm/persistence/base.rb']
10
+ }
11
+
12
+ exclude_files = [
13
+ 'aasm/rspec.*',
14
+ 'aasm/persistence/active_record_persistence.rb',
15
+ 'aasm/persistence/dynamoid_persistence.rb',
16
+ 'aasm/persistence/mongo_mapper_persistence.rb',
17
+ 'aasm/persistence/mongoid_persistence.rb',
18
+ 'aasm/persistence/sequel_persistence.rb',
19
+ 'aasm/persistence/redis_persistence.rb'
20
+ ]
21
+
22
+ Motion::Project::App.setup do |app|
23
+ parent = File.expand_path File.dirname(__FILE__)
24
+
25
+ app.files.unshift Dir.glob(File.join(parent, "aasm/**/*.rb")).reject { |file| exclude_files.any? { |exclude| file.match(exclude) } }
26
+
27
+ app.files_dependencies file_dependencies.inject({}, &->(file_dependencies, (file, *dependencies)) do
28
+ file = File.join(parent, file)
29
+ dependencies = dependencies.flatten(1).map do |dependency|
30
+ File.join(parent, dependency)
31
+ end
32
+
33
+ file_dependencies.merge({ file => dependencies })
34
+ end)
35
+ end
@@ -39,8 +39,15 @@ module Callbacks
39
39
  :exit => :exit_closed,
40
40
  :after_exit => :after_exit_closed
41
41
 
42
- event :close, :before => :before_event, :after => :after_event, :guard => :event_guard, :ensure => :ensure_event do
43
- transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :after_transition
42
+ event :close,
43
+ :before => :before_event,
44
+ :after => :after_event,
45
+ :guard => :event_guard,
46
+ :ensure => :ensure_event do
47
+ transitions :to => :closed, :from => [:open],
48
+ :guard => :transition_guard,
49
+ :after => :after_transition,
50
+ :success => :success_transition
44
51
  end
45
52
 
46
53
  event :open, :before => :before_event, :after => :after_event do
@@ -79,6 +86,9 @@ module Callbacks
79
86
  def after_event; log('after_event'); end
80
87
  def after_all_events; log('after_all_events'); end
81
88
 
89
+ def after_transition; log('after_transition'); end
90
+ def success_transition; log('transition_success'); end
91
+
82
92
  def ensure_event; log('ensure'); end
83
93
  def ensure_on_all_events; log('ensure'); end
84
94
  end
@@ -26,7 +26,7 @@ module Callbacks
26
26
  :after_exit => :after_exit_closed
27
27
 
28
28
  event :close, :before => :before, :after => :after, :guard => :event_guard do
29
- transitions :to => :closed, :from => [:open], :after => :transitioning do
29
+ transitions :to => :closed, :from => [:open], :after => :transitioning, :success => :success_transition do
30
30
  guard do
31
31
  transition_guard
32
32
  end
@@ -59,6 +59,7 @@ module Callbacks
59
59
  def event_guard; log('event_guard'); !@fail_event_guard; end
60
60
  def transition_guard; log('transition_guard'); !@fail_transition_guard; end
61
61
  def transitioning; log('transitioning'); end
62
+ def success_transition; log('success transition'); end
62
63
 
63
64
  def before; log('before'); end
64
65
  def after; log('after'); end
@@ -28,7 +28,7 @@ module Callbacks
28
28
  state :failed
29
29
 
30
30
  event :close, :before => :before, :after => :after, :guard => :event_guard do
31
- transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning
31
+ transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning, :success => :success_transition
32
32
  transitions :to => :failed, :from => [:open]
33
33
  end
34
34
 
@@ -58,6 +58,7 @@ module Callbacks
58
58
  def event_guard; log('event_guard'); !@fail_event_guard; end
59
59
  def transition_guard; log('transition_guard'); !@fail_transition_guard; end
60
60
  def transitioning; log('transitioning'); end
61
+ def success_transition; log('transition success'); end
61
62
 
62
63
  def before; log('before'); end
63
64
  def after; log('after'); end
@@ -29,7 +29,7 @@ module Callbacks
29
29
  :after_exit => :after_exit_closed
30
30
 
31
31
  event :close, :before => :before, :after => :after do
32
- transitions :to => :closed, :from => [:open], :after => :transition_proc
32
+ transitions :to => :closed, :from => [:open], :after => :transition_proc, :success => :transition_success_proc
33
33
  end
34
34
 
35
35
  event :open, :before => :before, :after => :after do
@@ -57,5 +57,6 @@ module Callbacks
57
57
  def before(arg1, *args); log("before(#{arg1.inspect},#{args.map(&:inspect).join(',')})"); end
58
58
  def transition_proc(arg1, arg2); log("transition_proc(#{arg1.inspect},#{arg2.inspect})"); end
59
59
  def after(*args); log("after(#{args.map(&:inspect).join(',')})"); end
60
+ def transition_success_proc(*args); log("transition_success(#{args.map(&:inspect).join(',')})"); end
60
61
  end
61
62
  end
@@ -9,8 +9,8 @@ module Callbacks
9
9
  state :out_to_lunch
10
10
 
11
11
  event :close, :before => :before_method, :after => :after_method do
12
- transitions :to => :closed, :from => [:open], :after => :transition_method
13
- transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2
12
+ transitions :to => :closed, :from => [:open], :after => :transition_method, :success => :success_method
13
+ transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2, :success => :success_method2
14
14
  end
15
15
  end
16
16
 
@@ -22,5 +22,9 @@ module Callbacks
22
22
 
23
23
  def transition_method2(arg); end
24
24
 
25
+ def success_method(arg); end
26
+
27
+ def success_method2(arg); end
28
+
25
29
  end
26
30
  end
@@ -0,0 +1,26 @@
1
+ class Parent
2
+ include Mongoid::Document
3
+ include AASM
4
+
5
+ field :status, :type => String
6
+ has_many :childs
7
+
8
+ aasm column: :status do
9
+ state :unknown_scope
10
+ state :new
11
+ end
12
+ end
13
+
14
+ class Child
15
+ include Mongoid::Document
16
+ include AASM
17
+ field :parent_id
18
+
19
+ field :status, :type => String
20
+ belongs_to :parent
21
+
22
+ aasm column: :status do
23
+ state :unknown_scope
24
+ state :new
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ class NamespacedMultipleExample
2
+ include AASM
3
+ aasm(:status) do
4
+ state :unapproved, :initial => true
5
+ state :approved
6
+
7
+ event :approve do
8
+ transitions :from => :unapproved, :to => :approved
9
+ end
10
+
11
+ event :unapprove do
12
+ transitions :from => :approved, :to => :unapproved
13
+ end
14
+ end
15
+
16
+ aasm(:review_status, namespace: :review) do
17
+ state :unapproved, :initial => true
18
+ state :approved
19
+
20
+ event :approve_review do
21
+ transitions :from => :unapproved, :to => :approved
22
+ end
23
+
24
+ event :unapprove_review do
25
+ transitions :from => :approved, :to => :unapproved
26
+ end
27
+ end
28
+ end