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.
- checksums.yaml +4 -4
- data/.travis.yml +14 -12
- data/Appraisals +39 -16
- data/CHANGELOG.md +8 -1
- data/Dockerfile +44 -0
- data/Gemfile +1 -2
- data/README.md +51 -11
- data/docker-compose.yml +40 -0
- data/gemfiles/rails_3.2.gemfile +6 -6
- data/gemfiles/rails_4.0.gemfile +7 -7
- data/gemfiles/rails_4.2.gemfile +8 -8
- data/gemfiles/rails_4.2_mongoid_5.gemfile +5 -5
- data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +5 -6
- data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +13 -0
- data/lib/aasm.rb +5 -0
- data/lib/aasm/aasm.rb +1 -0
- data/lib/aasm/core/event.rb +5 -21
- data/lib/aasm/core/invoker.rb +129 -0
- data/lib/aasm/core/invokers/base_invoker.rb +75 -0
- data/lib/aasm/core/invokers/class_invoker.rb +52 -0
- data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
- data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
- data/lib/aasm/core/state.rb +10 -9
- data/lib/aasm/core/transition.rb +7 -68
- data/lib/aasm/errors.rb +2 -2
- data/lib/aasm/persistence.rb +3 -0
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/plain_persistence.rb +2 -1
- data/lib/aasm/persistence/sequel_persistence.rb +0 -1
- data/lib/aasm/rspec/allow_event.rb +5 -1
- data/lib/aasm/rspec/allow_transition_to.rb +5 -1
- data/lib/aasm/version.rb +1 -1
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +1 -0
- data/spec/generators/no_brainer_generator_spec.rb +29 -0
- data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
- data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
- data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
- data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
- data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
- data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
- data/spec/models/simple_example.rb +2 -0
- data/spec/models/simple_example_with_guard_args.rb +17 -0
- data/spec/spec_helpers/active_record.rb +2 -1
- data/spec/spec_helpers/dynamoid.rb +7 -5
- data/spec/spec_helpers/mongoid.rb +20 -1
- data/spec/spec_helpers/nobrainer.rb +15 -0
- data/spec/spec_helpers/redis.rb +5 -2
- data/spec/spec_helpers/sequel.rb +1 -1
- data/spec/unit/callbacks_spec.rb +2 -2
- data/spec/unit/exception_spec.rb +1 -1
- data/spec/unit/invoker_spec.rb +189 -0
- data/spec/unit/invokers/base_invoker_spec.rb +72 -0
- data/spec/unit/invokers/class_invoker_spec.rb +95 -0
- data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
- data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -4
- data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -4
- data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
- data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
- data/spec/unit/rspec_matcher_spec.rb +6 -0
- data/spec/unit/state_spec.rb +2 -2
- data/spec/unit/transition_spec.rb +1 -1
- data/test/minitest_helper.rb +2 -2
- data/test/unit/minitest_matcher_test.rb +1 -1
- metadata +51 -3
data/gemfiles/rails_4.2.gemfile
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "sqlite3", :
|
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", :
|
9
|
-
gem "mime-types", "~> 2", :
|
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", :
|
13
|
-
gem "aws-sdk", "~>2", :
|
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 :
|
16
|
+
gemspec path: "../"
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "sqlite3", :
|
6
|
-
gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
|
5
|
+
gem "sqlite3", platforms: :ruby
|
7
6
|
gem "rails", "4.2.5"
|
8
|
-
gem "mime-types", "~> 2", :
|
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 :
|
11
|
+
gemspec path: "../"
|
data/gemfiles/rails_5.0.gemfile
CHANGED
@@ -2,13 +2,12 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "sqlite3", :
|
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", :
|
11
|
-
gem "aws-sdk", "~>2", :
|
9
|
+
gem "dynamoid", "~> 1", platforms: :ruby
|
10
|
+
gem "aws-sdk", "~> 2", platforms: :ruby
|
12
11
|
gem "redis-objects"
|
13
12
|
|
14
|
-
gemspec :
|
13
|
+
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: "../"
|
data/lib/aasm.rb
CHANGED
@@ -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'
|
data/lib/aasm/aasm.rb
CHANGED
@@ -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))
|
data/lib/aasm/core/event.rb
CHANGED
@@ -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
|
-
|
160
|
-
|
161
|
-
|
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
|