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
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AASM
|
4
|
+
module Core
|
5
|
+
module Invokers
|
6
|
+
##
|
7
|
+
# Proc invoker which allows to use Procs as
|
8
|
+
# state/event/transition callbacks.
|
9
|
+
class ProcInvoker < BaseInvoker
|
10
|
+
def may_invoke?
|
11
|
+
subject.is_a?(Proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def log_failure
|
15
|
+
return log_source_location if Method.method_defined?(:source_location)
|
16
|
+
log_proc_info
|
17
|
+
end
|
18
|
+
|
19
|
+
def invoke_subject
|
20
|
+
@result = if support_parameters?
|
21
|
+
exec_proc(parameters_to_arity)
|
22
|
+
else
|
23
|
+
exec_proc(subject.arity)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def support_parameters?
|
30
|
+
subject.respond_to?(:parameters)
|
31
|
+
end
|
32
|
+
|
33
|
+
# rubocop:disable Metrics/AbcSize
|
34
|
+
def exec_proc(parameters_size)
|
35
|
+
return record.instance_exec(&subject) if parameters_size.zero?
|
36
|
+
return record.instance_exec(*args, &subject) if parameters_size < 0
|
37
|
+
record.instance_exec(*args[0..(parameters_size - 1)], &subject)
|
38
|
+
end
|
39
|
+
# rubocop:enable Metrics/AbcSize
|
40
|
+
|
41
|
+
def log_source_location
|
42
|
+
failures << subject.source_location.join('#')
|
43
|
+
end
|
44
|
+
|
45
|
+
def log_proc_info
|
46
|
+
failures << subject
|
47
|
+
end
|
48
|
+
|
49
|
+
def parameters_to_arity
|
50
|
+
subject.parameters.inject(0) do |memo, parameter|
|
51
|
+
memo += 1
|
52
|
+
memo *= -1 if parameter[0] == :rest && memo > 0
|
53
|
+
memo
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/aasm/core/state.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AASM::Core
|
2
4
|
class State
|
3
5
|
attr_reader :name, :state_machine, :options
|
@@ -13,7 +15,13 @@ module AASM::Core
|
|
13
15
|
def initialize_copy(orig)
|
14
16
|
super
|
15
17
|
@options = {}
|
16
|
-
orig.options.each_pair
|
18
|
+
orig.options.each_pair do |name, setting|
|
19
|
+
@options[name] = if setting.is_a?(Hash) || setting.is_a?(Array)
|
20
|
+
setting.dup
|
21
|
+
else
|
22
|
+
setting
|
23
|
+
end
|
24
|
+
end
|
17
25
|
end
|
18
26
|
|
19
27
|
def ==(state)
|
@@ -75,14 +83,7 @@ module AASM::Core
|
|
75
83
|
end
|
76
84
|
|
77
85
|
def _fire_callbacks(action, record, args)
|
78
|
-
|
79
|
-
when Symbol, String
|
80
|
-
arity = record.__send__(:method, action.to_sym).arity
|
81
|
-
record.__send__(action, *(arity < 0 ? args : args[0...arity]))
|
82
|
-
when Proc
|
83
|
-
arity = action.arity
|
84
|
-
action.call(record, *(arity < 0 ? args : args[0...arity]))
|
85
|
-
end
|
86
|
+
Invoker.new(action, record, args).invoke
|
86
87
|
end
|
87
88
|
|
88
89
|
end
|
data/lib/aasm/core/transition.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AASM::Core
|
2
4
|
class Transition
|
3
5
|
include DslHelper
|
@@ -67,77 +69,14 @@ module AASM::Core
|
|
67
69
|
record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=)
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
result
|
75
|
-
when Proc
|
76
|
-
if code.respond_to?(:parameters)
|
77
|
-
# In Ruby's Proc, the 'arity' method is not a good condidate to know if
|
78
|
-
# we should pass the arguments or not, since it does return 0 even in
|
79
|
-
# presence of optional parameters.
|
80
|
-
result = (code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
|
81
|
-
|
82
|
-
failures << code.source_location.join('#') unless result
|
83
|
-
else
|
84
|
-
# In RubyMotion's Proc, the 'parameter' method does not exists, however its
|
85
|
-
# 'arity' method works just like the one from Method, only returning 0 when
|
86
|
-
# there is no parameters whatsoever, optional or not.
|
87
|
-
result = (code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
|
88
|
-
|
89
|
-
# Sadly, RubyMotion's Proc does not define the method 'source_location' either.
|
90
|
-
failures << code unless result
|
91
|
-
end
|
92
|
-
|
93
|
-
result
|
94
|
-
when Class
|
95
|
-
arity = code.instance_method(:initialize).arity
|
96
|
-
if arity == 0
|
97
|
-
instance = code.new
|
98
|
-
elsif arity == 1
|
99
|
-
instance = code.new(record)
|
100
|
-
else
|
101
|
-
instance = code.new(record, *args)
|
102
|
-
end
|
103
|
-
result = instance.call
|
104
|
-
|
105
|
-
if Method.method_defined?(:source_location)
|
106
|
-
failures << instance.method(:call).source_location.join('#') unless result
|
107
|
-
else
|
108
|
-
# RubyMotion support ('source_location' not defined for Method)
|
109
|
-
failures << instance.method(:call) unless result
|
110
|
-
end
|
111
|
-
|
112
|
-
result
|
113
|
-
when Array
|
114
|
-
if options[:guard]
|
115
|
-
# invoke guard callbacks
|
116
|
-
code.all? {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
117
|
-
elsif options[:unless]
|
118
|
-
# invoke unless callbacks
|
119
|
-
code.all? {|a| !invoke_callbacks_compatible_with_guard(a, record, args)}
|
120
|
-
else
|
121
|
-
# invoke after callbacks
|
122
|
-
code.map {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
123
|
-
end
|
124
|
-
else
|
125
|
-
true
|
126
|
-
end
|
72
|
+
Invoker.new(code, record, args)
|
73
|
+
.with_options(options)
|
74
|
+
.with_failures(failures)
|
75
|
+
.invoke
|
127
76
|
end
|
128
77
|
|
129
78
|
def _fire_callbacks(code, record, args)
|
130
|
-
|
131
|
-
when Symbol, String
|
132
|
-
arity = record.send(:method, code.to_sym).arity
|
133
|
-
record.send(code, *(arity < 0 ? args : args[0...arity]))
|
134
|
-
when Proc
|
135
|
-
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
|
136
|
-
when Array
|
137
|
-
code.map {|a| _fire_callbacks(a, record, args)}
|
138
|
-
else
|
139
|
-
true
|
140
|
-
end
|
79
|
+
Invoker.new(code, record, args).invoke
|
141
80
|
end
|
142
81
|
|
143
82
|
end
|
data/lib/aasm/errors.rb
CHANGED
@@ -7,11 +7,11 @@ module AASM
|
|
7
7
|
|
8
8
|
def initialize(object, event_name, state_machine_name, failures = [])
|
9
9
|
@object, @event_name, @originating_state, @failures = object, event_name, object.aasm(state_machine_name).current_state, failures
|
10
|
-
super("Event '#{event_name}' cannot transition from '#{originating_state}'
|
10
|
+
super("Event '#{event_name}' cannot transition from '#{originating_state}'.#{reasoning}")
|
11
11
|
end
|
12
12
|
|
13
13
|
def reasoning
|
14
|
-
"Failed callback(s): #{failures}." unless failures.empty?
|
14
|
+
" Failed callback(s): #{failures}." unless failures.empty?
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
data/lib/aasm/persistence.rb
CHANGED
@@ -12,6 +12,9 @@ module AASM
|
|
12
12
|
elsif hierarchy.include?("Mongoid::Document")
|
13
13
|
require_persistence :mongoid
|
14
14
|
include_persistence base, :mongoid
|
15
|
+
elsif hierarchy.include?("NoBrainer::Document")
|
16
|
+
require_persistence :no_brainer
|
17
|
+
include_persistence base, :no_brainer
|
15
18
|
elsif hierarchy.include?("Sequel::Model")
|
16
19
|
require_persistence :sequel
|
17
20
|
include_persistence base, :sequel
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'aasm/persistence/orm'
|
2
|
+
module AASM
|
3
|
+
module Persistence
|
4
|
+
module NoBrainerPersistence
|
5
|
+
# This method:
|
6
|
+
#
|
7
|
+
# * extends the model with ClassMethods
|
8
|
+
# * includes InstanceMethods
|
9
|
+
#
|
10
|
+
# Adds
|
11
|
+
#
|
12
|
+
# before_validation :aasm_ensure_initial_state
|
13
|
+
#
|
14
|
+
# As a result, it doesn't matter when you define your methods - the following 2 are equivalent
|
15
|
+
#
|
16
|
+
# class Foo
|
17
|
+
# include NoBrainer::Document
|
18
|
+
# def aasm_write_state(state)
|
19
|
+
# "bar"
|
20
|
+
# end
|
21
|
+
# include AASM
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Foo
|
25
|
+
# include NoBrainer::Document
|
26
|
+
# include AASM
|
27
|
+
# def aasm_write_state(state)
|
28
|
+
# "bar"
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
def self.included(base)
|
33
|
+
base.send(:include, AASM::Persistence::Base)
|
34
|
+
base.send(:include, AASM::Persistence::ORM)
|
35
|
+
base.send(:include, AASM::Persistence::NoBrainerPersistence::InstanceMethods)
|
36
|
+
base.extend AASM::Persistence::NoBrainerPersistence::ClassMethods
|
37
|
+
|
38
|
+
base.after_initialize :aasm_ensure_initial_state
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def aasm_create_scope(state_machine_name, scope_name)
|
43
|
+
scope_options = lambda {
|
44
|
+
where(aasm(state_machine_name).attribute_name.to_sym => scope_name.to_s)
|
45
|
+
}
|
46
|
+
send(:scope, scope_name, scope_options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module InstanceMethods
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def aasm_save
|
55
|
+
self.save
|
56
|
+
end
|
57
|
+
|
58
|
+
def aasm_raise_invalid_record
|
59
|
+
raise NoBrainer::Error::DocumentInvalid.new(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
def aasm_supports_transactions?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def aasm_update_column(attribute_name, value)
|
67
|
+
write_attribute(attribute_name, value)
|
68
|
+
save(validate: false)
|
69
|
+
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def aasm_read_attribute(name)
|
74
|
+
read_attribute(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def aasm_write_attribute(name, value)
|
78
|
+
write_attribute(name, value)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Ensures that if the aasm_state column is nil and the record is new
|
82
|
+
# that the initial state gets populated before validation on create
|
83
|
+
#
|
84
|
+
# foo = Foo.new
|
85
|
+
# foo.aasm_state # => nil
|
86
|
+
# foo.valid?
|
87
|
+
# foo.aasm_state # => "open" (where :open is the initial state)
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# foo = Foo.find(:first)
|
91
|
+
# foo.aasm_state # => 1
|
92
|
+
# foo.aasm_state = nil
|
93
|
+
# foo.valid?
|
94
|
+
# foo.aasm_state # => nil
|
95
|
+
#
|
96
|
+
def aasm_ensure_initial_state
|
97
|
+
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |name|
|
98
|
+
aasm_column = self.class.aasm(name).attribute_name
|
99
|
+
aasm(name).enter_initial_state if read_attribute(aasm_column).blank?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end # InstanceMethods
|
103
|
+
end
|
104
|
+
end # Persistence
|
105
|
+
end # AASM
|
@@ -5,7 +5,8 @@ module AASM
|
|
5
5
|
# may be overwritten by persistence mixins
|
6
6
|
def aasm_read_state(name=:default)
|
7
7
|
# all the following lines behave like @current_state ||= aasm(name).enter_initial_state
|
8
|
-
current = aasm(name).
|
8
|
+
current = aasm(name).instance_variable_defined?("@current_state_#{name}") &&
|
9
|
+
aasm(name).instance_variable_get("@current_state_#{name}")
|
9
10
|
return current if current
|
10
11
|
aasm(name).instance_variable_set("@current_state_#{name}", aasm(name).enter_initial_state)
|
11
12
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
RSpec::Matchers.define :allow_event do |event|
|
2
2
|
match do |obj|
|
3
3
|
@state_machine_name ||= :default
|
4
|
-
obj.aasm(@state_machine_name).may_fire_event?(event)
|
4
|
+
obj.aasm(@state_machine_name).may_fire_event?(event, *@args)
|
5
5
|
end
|
6
6
|
|
7
7
|
chain :on do |state_machine_name|
|
8
8
|
@state_machine_name = state_machine_name
|
9
9
|
end
|
10
10
|
|
11
|
+
chain :with do |*args|
|
12
|
+
@args = args
|
13
|
+
end
|
14
|
+
|
11
15
|
description do
|
12
16
|
"allow event #{expected} (on :#{@state_machine_name})"
|
13
17
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
RSpec::Matchers.define :allow_transition_to do |state|
|
2
2
|
match do |obj|
|
3
3
|
@state_machine_name ||= :default
|
4
|
-
obj.aasm(@state_machine_name).states(:permitted => true).include?(state)
|
4
|
+
obj.aasm(@state_machine_name).states({:permitted => true}, *@args).include?(state)
|
5
5
|
end
|
6
6
|
|
7
7
|
chain :on do |state_machine_name|
|
8
8
|
@state_machine_name = state_machine_name
|
9
9
|
end
|
10
10
|
|
11
|
+
chain :with do |*args|
|
12
|
+
@args = args
|
13
|
+
end
|
14
|
+
|
11
15
|
description do
|
12
16
|
"allow transition to #{expected} (on :#{@state_machine_name})"
|
13
17
|
end
|
data/lib/aasm/version.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'generators/aasm/orm_helpers'
|
3
|
+
|
4
|
+
module NoBrainer
|
5
|
+
module Generators
|
6
|
+
class AASMGenerator < Rails::Generators::NamedBase
|
7
|
+
include AASM::Generators::OrmHelpers
|
8
|
+
namespace 'nobrainer:aasm'
|
9
|
+
argument :column_name, type: :string, default: 'aasm_state'
|
10
|
+
|
11
|
+
def generate_model
|
12
|
+
invoke 'nobrainer:model', [name] unless model_exists?
|
13
|
+
end
|
14
|
+
|
15
|
+
def inject_aasm_content
|
16
|
+
inject_into_file model_path, model_contents, after: "include NoBrainer::Document::Timestamps\n" if model_exists?
|
17
|
+
end
|
18
|
+
|
19
|
+
def inject_field_types
|
20
|
+
inject_into_file model_path, migration_data, after: "include NoBrainer::Document::Timestamps\n" if model_exists?
|
21
|
+
end
|
22
|
+
|
23
|
+
def migration_data
|
24
|
+
" field :#{column_name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/motion-aasm.rb
CHANGED
@@ -16,6 +16,7 @@ exclude_files = [
|
|
16
16
|
'aasm/persistence/active_record_persistence.rb',
|
17
17
|
'aasm/persistence/dynamoid_persistence.rb',
|
18
18
|
'aasm/persistence/mongoid_persistence.rb',
|
19
|
+
'aasm/persistence/no_brainer_persistence.rb',
|
19
20
|
'aasm/persistence/sequel_persistence.rb',
|
20
21
|
'aasm/persistence/redis_persistence.rb'
|
21
22
|
]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
if defined?(NoBrainer::Document)
|
4
|
+
require 'generator_spec'
|
5
|
+
require 'generators/nobrainer/aasm_generator'
|
6
|
+
|
7
|
+
describe NoBrainer::Generators::AASMGenerator, type: :generator do
|
8
|
+
destination File.expand_path('../../../tmp', __FILE__)
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
prepare_destination
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'creates model with aasm block for default column_name' do
|
15
|
+
run_generator %w[user]
|
16
|
+
assert_file 'app/models/user.rb', /include AASM\n\n aasm do\n end\n/
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates model with aasm block for custom column_name' do
|
20
|
+
run_generator %w[user state]
|
21
|
+
assert_file 'app/models/user.rb', /aasm :column => 'state' do\n end\n/
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'creates model with aasm block for namespaced model' do
|
25
|
+
run_generator %w[Admin::User state]
|
26
|
+
assert_file 'app/models/admin/user.rb', /aasm :column => 'state' do\n end\n/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|