aasm 4.12.3 → 5.0.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -12
  3. data/Appraisals +39 -16
  4. data/CHANGELOG.md +8 -1
  5. data/Dockerfile +44 -0
  6. data/Gemfile +1 -2
  7. data/README.md +51 -11
  8. data/docker-compose.yml +40 -0
  9. data/gemfiles/rails_3.2.gemfile +6 -6
  10. data/gemfiles/rails_4.0.gemfile +7 -7
  11. data/gemfiles/rails_4.2.gemfile +8 -8
  12. data/gemfiles/rails_4.2_mongoid_5.gemfile +5 -5
  13. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  14. data/gemfiles/rails_5.0.gemfile +5 -6
  15. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  16. data/gemfiles/rails_5.1.gemfile +13 -0
  17. data/lib/aasm.rb +5 -0
  18. data/lib/aasm/aasm.rb +1 -0
  19. data/lib/aasm/core/event.rb +5 -21
  20. data/lib/aasm/core/invoker.rb +129 -0
  21. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  22. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  23. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  24. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  25. data/lib/aasm/core/state.rb +10 -9
  26. data/lib/aasm/core/transition.rb +7 -68
  27. data/lib/aasm/errors.rb +2 -2
  28. data/lib/aasm/persistence.rb +3 -0
  29. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  30. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  31. data/lib/aasm/persistence/sequel_persistence.rb +0 -1
  32. data/lib/aasm/rspec/allow_event.rb +5 -1
  33. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  34. data/lib/aasm/version.rb +1 -1
  35. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  36. data/lib/motion-aasm.rb +1 -0
  37. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  38. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  39. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  40. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  41. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  42. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  43. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  44. data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
  45. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  46. data/spec/models/simple_example.rb +2 -0
  47. data/spec/models/simple_example_with_guard_args.rb +17 -0
  48. data/spec/spec_helpers/active_record.rb +2 -1
  49. data/spec/spec_helpers/dynamoid.rb +7 -5
  50. data/spec/spec_helpers/mongoid.rb +20 -1
  51. data/spec/spec_helpers/nobrainer.rb +15 -0
  52. data/spec/spec_helpers/redis.rb +5 -2
  53. data/spec/spec_helpers/sequel.rb +1 -1
  54. data/spec/unit/callbacks_spec.rb +2 -2
  55. data/spec/unit/exception_spec.rb +1 -1
  56. data/spec/unit/invoker_spec.rb +189 -0
  57. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  58. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  59. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  60. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  61. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -4
  62. data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -4
  63. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  64. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  65. data/spec/unit/rspec_matcher_spec.rb +6 -0
  66. data/spec/unit/state_spec.rb +2 -2
  67. data/spec/unit/transition_spec.rb +1 -1
  68. data/test/minitest_helper.rb +2 -2
  69. data/test/unit/minitest_matcher_test.rb +1 -1
  70. metadata +51 -3
@@ -2,15 +2,15 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "sqlite3", :platforms => :ruby
6
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
5
+ gem "sqlite3", platforms: :ruby
7
6
  gem "rails", "4.2.5"
8
- gem "nokogiri", "1.6.8.1", :platforms => [:ruby_19]
9
- gem "mime-types", "~> 2", :platforms => [:ruby_19, :jruby]
10
- gem "mongoid", "~>4.0"
7
+ gem "nokogiri", "1.6.8.1", platforms: [:ruby_19]
8
+ gem "mime-types", "~> 2", platforms: [:ruby_19, :jruby]
9
+ gem "mongoid", "~> 4.0"
11
10
  gem "sequel"
12
- gem "dynamoid", "~> 1", :platforms => :ruby
13
- gem "aws-sdk", "~>2", :platforms => :ruby
11
+ gem "dynamoid", "~> 1", platforms: :ruby
12
+ gem "aws-sdk", "~> 2", platforms: :ruby
14
13
  gem "redis-objects"
14
+ gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
15
15
 
16
- gemspec :path => "../"
16
+ gemspec path: "../"
@@ -2,10 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "sqlite3", :platforms => :ruby
6
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
5
+ gem "sqlite3", platforms: :ruby
7
6
  gem "rails", "4.2.5"
8
- gem "mime-types", "~> 2", :platforms => [:ruby_19, :jruby]
9
- gem "mongoid", "~>5.0"
7
+ gem "mime-types", "~> 2", platforms: [:ruby_19, :jruby]
8
+ gem "mongoid", "~> 5.0"
9
+ gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
10
10
 
11
- gemspec :path => "../"
11
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", platforms: :ruby
6
+ gem "rails", "4.2.5"
7
+ gem "nobrainer", "~> 0.33.0"
8
+
9
+ gemspec path: "../"
@@ -2,13 +2,12 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "sqlite3", :platforms => :ruby
6
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
5
+ gem "sqlite3", platforms: :ruby
7
6
  gem "rails", "5.0.0"
8
- gem "mongoid", "~>6.0"
7
+ gem "mongoid", "~> 6.0"
9
8
  gem "sequel"
10
- gem "dynamoid", "~> 1", :platforms => :ruby
11
- gem "aws-sdk", "~>2", :platforms => :ruby
9
+ gem "dynamoid", "~> 1", platforms: :ruby
10
+ gem "aws-sdk", "~> 2", platforms: :ruby
12
11
  gem "redis-objects"
13
12
 
14
- gemspec :path => "../"
13
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", platforms: :ruby
6
+ gem "rails", "5.0.0"
7
+ gem "nobrainer", "~> 0.33.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,13 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", platforms: :ruby
6
+ gem "rails", "5.1"
7
+ gem "mongoid", "~>6.0"
8
+ gem "sequel"
9
+ gem "dynamoid", "~> 1", platforms: :ruby
10
+ gem "aws-sdk", "~>2", platforms: :ruby
11
+ gem "redis-objects"
12
+
13
+ gemspec path: "../"
@@ -9,6 +9,11 @@ require 'aasm/instance_base'
9
9
  require 'aasm/core/transition'
10
10
  require 'aasm/core/event'
11
11
  require 'aasm/core/state'
12
+ require 'aasm/core/invoker'
13
+ require 'aasm/core/invokers/base_invoker'
14
+ require 'aasm/core/invokers/class_invoker'
15
+ require 'aasm/core/invokers/literal_invoker'
16
+ require 'aasm/core/invokers/proc_invoker'
12
17
  require 'aasm/localizer'
13
18
  require 'aasm/state_machine_store'
14
19
  require 'aasm/state_machine'
@@ -130,6 +130,7 @@ private
130
130
  event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
131
131
  event.fire_global_callbacks(:error_on_all_events, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
132
132
  raise(e)
133
+ false
133
134
  ensure
134
135
  event.fire_callbacks(:ensure, self, *process_args(event, aasm(state_machine_name).current_state, *args))
135
136
  event.fire_global_callbacks(:ensure_on_all_events, self, *process_args(event, aasm(state_machine_name).current_state, *args))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AASM::Core
2
4
  class Event
3
5
  include DslHelper
@@ -156,27 +158,9 @@ module AASM::Core
156
158
  end
157
159
 
158
160
  def invoke_callbacks(code, record, args)
159
- case code
160
- when Symbol, String
161
- unless record.respond_to?(code, true)
162
- raise NoMethodError.new("NoMethodError: undefined method `#{code}' for #{record.inspect}:#{record.class}")
163
- end
164
- arity = record.__send__(:method, code.to_sym).arity
165
- record.__send__(code, *(arity < 0 ? args : args[0...arity]))
166
- true
167
-
168
- when Proc
169
- arity = code.arity
170
- record.instance_exec(*(arity < 0 ? args : args[0...arity]), &code)
171
- true
172
-
173
- when Array
174
- code.each {|a| invoke_callbacks(a, record, args)}
175
- true
176
-
177
- else
178
- false
179
- end
161
+ Invoker.new(code, record, args)
162
+ .with_default_return_value(false)
163
+ .invoke
180
164
  end
181
165
  end
182
166
  end # AASM
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ ##
6
+ # main invoker class which encapsulates the logic
7
+ # for invoking literal-based, proc-based, class-based
8
+ # and array-based callbacks for different entities.
9
+ class Invoker
10
+ DEFAULT_RETURN_VALUE = true
11
+
12
+ ##
13
+ # Initialize a new invoker instance.
14
+ # NOTE that invoker must be used per-subject/record
15
+ # (one instance per subject/record)
16
+ #
17
+ # ==Options:
18
+ #
19
+ # +subject+ - invoking subject, may be Proc,
20
+ # Class, String, Symbol or Array
21
+ # +record+ - invoking record
22
+ # +args+ - arguments which will be passed to the callback
23
+
24
+ def initialize(subject, record, args)
25
+ @subject = subject
26
+ @record = record
27
+ @args = args
28
+ @options = {}
29
+ @failures = []
30
+ @default_return_value = DEFAULT_RETURN_VALUE
31
+ end
32
+
33
+ ##
34
+ # Pass additional options to concrete invoker
35
+ #
36
+ # ==Options:
37
+ #
38
+ # +options+ - hash of options which will be passed to
39
+ # concrete invokers
40
+ #
41
+ # ==Example:
42
+ #
43
+ # with_options(guard: proc {...})
44
+
45
+ def with_options(options)
46
+ @options = options
47
+ self
48
+ end
49
+
50
+ ##
51
+ # Collect failures to a specified buffer
52
+ #
53
+ # ==Options:
54
+ #
55
+ # +failures+ - failures buffer to collect failures
56
+
57
+ def with_failures(failures)
58
+ @failures = failures
59
+ self
60
+ end
61
+
62
+ ##
63
+ # Change default return value of #invoke method
64
+ # if none of invokers processed the request.
65
+ #
66
+ # The default return value is #DEFAULT_RETURN_VALUE
67
+ #
68
+ # ==Options:
69
+ #
70
+ # +value+ - default return value for #invoke method
71
+
72
+ def with_default_return_value(value)
73
+ @default_return_value = value
74
+ self
75
+ end
76
+
77
+ ##
78
+ # Find concrete invoker for specified subject and invoker it,
79
+ # or return default value set by #DEFAULT_RETURN_VALUE or
80
+ # overridden by #with_default_return_value
81
+
82
+ # rubocop:disable Metrics/AbcSize
83
+ def invoke
84
+ return invoke_array if subject.is_a?(Array)
85
+ return literal_invoker.invoke if literal_invoker.may_invoke?
86
+ return proc_invoker.invoke if proc_invoker.may_invoke?
87
+ return class_invoker.invoke if class_invoker.may_invoke?
88
+ default_return_value
89
+ end
90
+ # rubocop:enable Metrics/AbcSize
91
+
92
+ private
93
+
94
+ attr_reader :subject, :record, :args, :options, :failures,
95
+ :default_return_value
96
+
97
+ def invoke_array
98
+ return subject.all? { |item| sub_invoke(item) } if options[:guard]
99
+ return subject.all? { |item| !sub_invoke(item) } if options[:unless]
100
+ subject.map { |item| sub_invoke(item) }
101
+ end
102
+
103
+ def sub_invoke(new_subject)
104
+ self.class.new(new_subject, record, args)
105
+ .with_failures(failures)
106
+ .with_options(options)
107
+ .invoke
108
+ end
109
+
110
+ def proc_invoker
111
+ @proc_invoker ||= Invokers::ProcInvoker
112
+ .new(subject, record, args)
113
+ .with_failures(failures)
114
+ end
115
+
116
+ def class_invoker
117
+ @class_invoker ||= Invokers::ClassInvoker
118
+ .new(subject, record, args)
119
+ .with_failures(failures)
120
+ end
121
+
122
+ def literal_invoker
123
+ @literal_invoker ||= Invokers::LiteralInvoker
124
+ .new(subject, record, args)
125
+ .with_failures(failures)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Base concrete invoker class which contain basic
8
+ # invoking and logging definitions
9
+ class BaseInvoker
10
+ attr_reader :failures, :subject, :record, :args, :result
11
+
12
+ ##
13
+ # Initialize a new concrete invoker instance.
14
+ # NOTE that concrete invoker must be used per-subject/record
15
+ # (one instance per subject/record)
16
+ #
17
+ # ==Options:
18
+ #
19
+ # +subject+ - invoking subject comparable with this invoker
20
+ # +record+ - invoking record
21
+ # +args+ - arguments which will be passed to the callback
22
+
23
+ def initialize(subject, record, args)
24
+ @subject = subject
25
+ @record = record
26
+ @args = args
27
+ @result = false
28
+ @failures = []
29
+ end
30
+
31
+ ##
32
+ # Collect failures to a specified buffer
33
+ #
34
+ # ==Options:
35
+ #
36
+ # +failures+ - failures buffer to collect failures
37
+
38
+ def with_failures(failures_buffer)
39
+ @failures = failures_buffer
40
+ self
41
+ end
42
+
43
+ ##
44
+ # Execute concrete invoker, log the error and return result
45
+
46
+ def invoke
47
+ return unless may_invoke?
48
+ log_failure unless invoke_subject
49
+ result
50
+ end
51
+
52
+ ##
53
+ # Check if concrete invoker may be invoked for a specified subject
54
+
55
+ def may_invoke?
56
+ raise NoMethodError, '"#may_invoke?" is not implemented'
57
+ end
58
+
59
+ ##
60
+ # Log failed invoking
61
+
62
+ def log_failure
63
+ raise NoMethodError, '"#log_failure" is not implemented'
64
+ end
65
+
66
+ ##
67
+ # Execute concrete invoker
68
+
69
+ def invoke_subject
70
+ raise NoMethodError, '"#invoke_subject" is not implemented'
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Class invoker which allows to use classes which respond to #call
8
+ # to be used as state/event/transition callbacks.
9
+ class ClassInvoker < BaseInvoker
10
+ def may_invoke?
11
+ subject.is_a?(Class) && subject.instance_methods.include?(:call)
12
+ end
13
+
14
+ def log_failure
15
+ return log_source_location if Method.method_defined?(:source_location)
16
+ log_method_info
17
+ end
18
+
19
+ def invoke_subject
20
+ @result = retrieve_instance.call
21
+ end
22
+
23
+ private
24
+
25
+ def log_source_location
26
+ failures << instance.method(:call).source_location.join('#')
27
+ end
28
+
29
+ def log_method_info
30
+ failures << instance.method(:call)
31
+ end
32
+
33
+ def instance
34
+ @instance ||= retrieve_instance
35
+ end
36
+
37
+ # rubocop:disable Metrics/AbcSize
38
+ def retrieve_instance
39
+ return subject.new if subject_arity.zero?
40
+ return subject.new(record) if subject_arity == 1
41
+ return subject.new(record, *args) if subject_arity < 0
42
+ subject.new(record, *args[0..(subject_arity - 2)])
43
+ end
44
+ # rubocop:enable Metrics/AbcSize
45
+
46
+ def subject_arity
47
+ @arity ||= subject.instance_method(:initialize).arity
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Literal invoker which allows to use strings or symbols to call
8
+ # record methods as state/event/transition callbacks.
9
+ class LiteralInvoker < BaseInvoker
10
+ def may_invoke?
11
+ subject.is_a?(String) || subject.is_a?(Symbol)
12
+ end
13
+
14
+ def log_failure
15
+ failures << subject
16
+ end
17
+
18
+ def invoke_subject
19
+ @result = exec_subject
20
+ end
21
+
22
+ private
23
+
24
+ def subject_arity
25
+ @arity ||= record.__send__(:method, subject.to_sym).arity
26
+ end
27
+
28
+ # rubocop:disable Metrics/AbcSize
29
+ def exec_subject
30
+ raise(*record_error) unless record.respond_to?(subject, true)
31
+ return record.__send__(subject) if subject_arity.zero?
32
+ return record.__send__(subject, *args) if subject_arity < 0
33
+ record.__send__(subject, *args[0..(subject_arity - 1)])
34
+ end
35
+ # rubocop:enable Metrics/AbcSize
36
+
37
+ def record_error
38
+ [
39
+ NoMethodError,
40
+ 'NoMethodError: undefined method ' \
41
+ "`#{subject}' for #{record.inspect}:#{record.class}"
42
+ ]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end