aasm 4.9.0 → 4.10.0

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