aasm 4.11.1 → 5.2.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 +5 -5
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.travis.yml +56 -23
- data/Appraisals +67 -0
- data/CHANGELOG.md +112 -0
- data/CONTRIBUTING.md +24 -0
- data/Dockerfile +44 -0
- data/Gemfile +3 -21
- data/Gemfile.lock_old +151 -0
- data/LICENSE +1 -1
- data/README.md +540 -139
- data/Rakefile +6 -1
- data/TESTING.md +25 -0
- data/aasm.gemspec +5 -0
- data/docker-compose.yml +40 -0
- data/gemfiles/norails.gemfile +10 -0
- data/gemfiles/rails_4.2.gemfile +13 -11
- data/gemfiles/rails_4.2_mongoid_5.gemfile +8 -11
- data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +11 -18
- data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +14 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/lib/aasm/aasm.rb +40 -29
- data/lib/aasm/base.rb +61 -11
- data/lib/aasm/configuration.rb +10 -0
- data/lib/aasm/core/event.rb +45 -37
- 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 +22 -13
- data/lib/aasm/core/transition.rb +17 -69
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/errors.rb +4 -6
- data/lib/aasm/instance_base.rb +22 -4
- data/lib/aasm/localizer.rb +13 -3
- data/lib/aasm/minitest/allow_event.rb +13 -0
- data/lib/aasm/minitest/allow_transition_to.rb +13 -0
- data/lib/aasm/minitest/have_state.rb +13 -0
- data/lib/aasm/minitest/transition_from.rb +21 -0
- data/lib/aasm/minitest.rb +5 -0
- data/lib/aasm/minitest_spec.rb +15 -0
- data/lib/aasm/persistence/active_record_persistence.rb +49 -105
- data/lib/aasm/persistence/base.rb +20 -5
- data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
- data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
- data/lib/aasm/persistence/mongoid_persistence.rb +26 -32
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/orm.rb +154 -0
- data/lib/aasm/persistence/plain_persistence.rb +2 -1
- data/lib/aasm/persistence/redis_persistence.rb +16 -11
- data/lib/aasm/persistence/sequel_persistence.rb +36 -64
- data/lib/aasm/persistence.rb +3 -3
- data/lib/aasm/rspec/allow_event.rb +5 -1
- data/lib/aasm/rspec/allow_transition_to.rb +5 -1
- data/lib/aasm/rspec/transition_from.rb +5 -1
- data/lib/aasm/state_machine.rb +4 -2
- data/lib/aasm/state_machine_store.rb +5 -2
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +5 -2
- data/lib/generators/aasm/orm_helpers.rb +6 -0
- data/lib/generators/active_record/aasm_generator.rb +3 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/active_record/templates/migration_existing.rb +1 -1
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +3 -1
- data/spec/database.rb +20 -7
- data/spec/en.yml +0 -3
- data/spec/generators/active_record_generator_spec.rb +49 -40
- data/spec/generators/mongoid_generator_spec.rb +4 -6
- data/spec/generators/no_brainer_generator_spec.rb +29 -0
- data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
- data/spec/localizer_test_model_new_style.yml +11 -0
- data/spec/models/active_record/active_record_callback.rb +93 -0
- data/spec/models/active_record/complex_active_record_example.rb +5 -1
- data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
- data/spec/models/{invalid_persistor.rb → active_record/invalid_persistor.rb} +0 -2
- data/spec/models/active_record/localizer_test_model.rb +11 -3
- data/spec/models/active_record/namespaced.rb +16 -0
- data/spec/models/active_record/person.rb +23 -0
- data/spec/models/{silent_persistor.rb → active_record/silent_persistor.rb} +0 -2
- data/spec/models/active_record/simple_new_dsl.rb +15 -0
- data/spec/models/active_record/timestamp_example.rb +16 -0
- data/spec/models/{transactor.rb → active_record/transactor.rb} +25 -2
- data/spec/models/{validator.rb → active_record/validator.rb} +0 -2
- data/spec/models/active_record/work.rb +3 -0
- data/spec/models/{worker.rb → active_record/worker.rb} +0 -0
- data/spec/models/callbacks/basic.rb +5 -2
- data/spec/models/callbacks/with_state_arg.rb +5 -1
- data/spec/models/callbacks/with_state_arg_multiple.rb +4 -1
- data/spec/models/default_state.rb +1 -1
- data/spec/models/guard_arguments_check.rb +17 -0
- data/spec/models/guard_with_params.rb +1 -1
- data/spec/models/guardian_without_from_specified.rb +18 -0
- data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
- data/spec/models/mongoid/validator_mongoid.rb +100 -0
- data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
- data/spec/models/namespaced_multiple_example.rb +14 -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/{mongo_mapper/simple_mongo_mapper.rb → nobrainer/simple_no_brainer.rb} +8 -8
- data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
- data/spec/models/parametrised_event.rb +7 -0
- data/spec/models/{mongo_mapper/complex_mongo_mapper_example.rb → redis/complex_redis_example.rb} +8 -5
- data/spec/models/redis/redis_multiple.rb +20 -0
- data/spec/models/redis/redis_simple.rb +20 -0
- data/spec/models/sequel/complex_sequel_example.rb +4 -3
- data/spec/models/sequel/invalid_persistor.rb +52 -0
- data/spec/models/sequel/sequel_multiple.rb +13 -13
- data/spec/models/sequel/sequel_simple.rb +13 -12
- data/spec/models/sequel/silent_persistor.rb +50 -0
- data/spec/models/sequel/transactor.rb +112 -0
- data/spec/models/sequel/validator.rb +93 -0
- data/spec/models/sequel/worker.rb +12 -0
- data/spec/models/simple_example.rb +8 -0
- data/spec/models/simple_example_with_guard_args.rb +17 -0
- data/spec/models/simple_multiple_example.rb +12 -0
- data/spec/models/sub_class.rb +34 -0
- data/spec/models/timestamps_example.rb +19 -0
- data/spec/models/timestamps_with_named_machine_example.rb +13 -0
- data/spec/spec_helper.rb +15 -33
- data/spec/spec_helpers/active_record.rb +8 -0
- data/spec/spec_helpers/dynamoid.rb +35 -0
- data/spec/spec_helpers/mongoid.rb +26 -0
- data/spec/spec_helpers/nobrainer.rb +15 -0
- data/spec/spec_helpers/redis.rb +18 -0
- data/spec/spec_helpers/remove_warnings.rb +1 -0
- data/spec/spec_helpers/sequel.rb +7 -0
- data/spec/unit/abstract_class_spec.rb +27 -0
- data/spec/unit/api_spec.rb +79 -72
- data/spec/unit/callback_multiple_spec.rb +7 -3
- data/spec/unit/callbacks_spec.rb +37 -2
- data/spec/unit/complex_example_spec.rb +12 -3
- data/spec/unit/complex_multiple_example_spec.rb +20 -4
- data/spec/unit/event_multiple_spec.rb +1 -1
- data/spec/unit/event_spec.rb +29 -4
- data/spec/unit/exception_spec.rb +1 -1
- data/spec/unit/guard_arguments_check_spec.rb +9 -0
- data/spec/unit/guard_spec.rb +17 -0
- data/spec/unit/guard_with_params_spec.rb +4 -0
- data/spec/unit/guard_without_from_specified_spec.rb +10 -0
- data/spec/unit/inspection_multiple_spec.rb +9 -5
- data/spec/unit/inspection_spec.rb +7 -3
- 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/localizer_spec.rb +85 -52
- data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
- data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
- data/spec/unit/override_warning_spec.rb +8 -0
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +468 -447
- data/spec/unit/persistence/active_record_persistence_spec.rb +639 -486
- data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +4 -9
- data/spec/unit/persistence/dynamoid_persistence_spec.rb +4 -9
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +83 -13
- data/spec/unit/persistence/mongoid_persistence_spec.rb +97 -13
- 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/persistence/redis_persistence_multiple_spec.rb +88 -0
- data/spec/unit/persistence/redis_persistence_spec.rb +8 -32
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +6 -11
- data/spec/unit/persistence/sequel_persistence_spec.rb +278 -10
- data/spec/unit/rspec_matcher_spec.rb +9 -0
- data/spec/unit/simple_example_spec.rb +15 -0
- data/spec/unit/simple_multiple_example_spec.rb +28 -0
- data/spec/unit/state_spec.rb +23 -7
- data/spec/unit/subclassing_multiple_spec.rb +37 -2
- data/spec/unit/subclassing_spec.rb +17 -2
- data/spec/unit/timestamps_spec.rb +32 -0
- data/spec/unit/transition_spec.rb +1 -1
- data/test/minitest_helper.rb +57 -0
- data/test/unit/minitest_matcher_test.rb +80 -0
- metadata +213 -37
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2_stable.gemfile +0 -15
- data/gemfiles/rails_4.0.gemfile +0 -16
- data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -16
- data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -17
- data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -163
- data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
- data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
- data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -149
- data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -96
|
@@ -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
|
|
@@ -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,14 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module AASM::Core
|
|
2
4
|
class State
|
|
3
|
-
attr_reader :name, :state_machine, :options
|
|
5
|
+
attr_reader :name, :state_machine, :options, :default_display_name
|
|
4
6
|
|
|
5
7
|
def initialize(name, klass, state_machine, options={})
|
|
6
8
|
@name = name
|
|
7
9
|
@klass = klass
|
|
8
10
|
@state_machine = state_machine
|
|
11
|
+
@default_display_name = name.to_s.gsub(/_/, ' ').capitalize
|
|
9
12
|
update(options)
|
|
10
13
|
end
|
|
11
14
|
|
|
15
|
+
# called internally by Ruby 1.9 after clone()
|
|
16
|
+
def initialize_copy(orig)
|
|
17
|
+
super
|
|
18
|
+
@options = {}
|
|
19
|
+
orig.options.each_pair do |name, setting|
|
|
20
|
+
@options[name] = if setting.is_a?(Hash) || setting.is_a?(Array)
|
|
21
|
+
setting.dup
|
|
22
|
+
else
|
|
23
|
+
setting
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
def ==(state)
|
|
13
29
|
if state.is_a? Symbol
|
|
14
30
|
name == state
|
|
@@ -39,11 +55,11 @@ module AASM::Core
|
|
|
39
55
|
end
|
|
40
56
|
|
|
41
57
|
def display_name
|
|
42
|
-
@display_name
|
|
58
|
+
@display_name = begin
|
|
43
59
|
if Module.const_defined?(:I18n)
|
|
44
60
|
localized_name
|
|
45
61
|
else
|
|
46
|
-
|
|
62
|
+
@default_display_name
|
|
47
63
|
end
|
|
48
64
|
end
|
|
49
65
|
end
|
|
@@ -60,22 +76,15 @@ module AASM::Core
|
|
|
60
76
|
private
|
|
61
77
|
|
|
62
78
|
def update(options = {})
|
|
63
|
-
if options.key?(:display)
|
|
64
|
-
@
|
|
79
|
+
if options.key?(:display)
|
|
80
|
+
@default_display_name = options.delete(:display)
|
|
65
81
|
end
|
|
66
82
|
@options = options
|
|
67
83
|
self
|
|
68
84
|
end
|
|
69
85
|
|
|
70
86
|
def _fire_callbacks(action, record, args)
|
|
71
|
-
|
|
72
|
-
when Symbol, String
|
|
73
|
-
arity = record.__send__(:method, action.to_sym).arity
|
|
74
|
-
record.__send__(action, *(arity < 0 ? args : args[0...arity]))
|
|
75
|
-
when Proc
|
|
76
|
-
arity = action.arity
|
|
77
|
-
action.call(record, *(arity < 0 ? args : args[0...arity]))
|
|
78
|
-
end
|
|
87
|
+
Invoker.new(action, record, args).invoke
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
end
|
data/lib/aasm/core/transition.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module AASM::Core
|
|
2
4
|
class Transition
|
|
3
|
-
include DslHelper
|
|
5
|
+
include AASM::DslHelper
|
|
4
6
|
|
|
5
7
|
attr_reader :from, :to, :event, :opts, :failures
|
|
6
8
|
alias_method :options, :opts
|
|
@@ -28,6 +30,15 @@ module AASM::Core
|
|
|
28
30
|
@opts = opts
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
# called internally by Ruby 1.9 after clone()
|
|
34
|
+
def initialize_copy(orig)
|
|
35
|
+
super
|
|
36
|
+
@guards = @guards.dup
|
|
37
|
+
@unless = @unless.dup
|
|
38
|
+
@opts = {}
|
|
39
|
+
orig.opts.each_pair { |name, setting| @opts[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
def allowed?(obj, *args)
|
|
32
43
|
invoke_callbacks_compatible_with_guard(@guards, obj, args, :guard => true) &&
|
|
33
44
|
invoke_callbacks_compatible_with_guard(@unless, obj, args, :unless => true)
|
|
@@ -58,77 +69,14 @@ module AASM::Core
|
|
|
58
69
|
record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=)
|
|
59
70
|
end
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
result
|
|
66
|
-
when Proc
|
|
67
|
-
if code.respond_to?(:parameters)
|
|
68
|
-
# In Ruby's Proc, the 'arity' method is not a good condidate to know if
|
|
69
|
-
# we should pass the arguments or not, since it does return 0 even in
|
|
70
|
-
# presence of optional parameters.
|
|
71
|
-
result = (code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
|
|
72
|
-
|
|
73
|
-
failures << code.source_location.join('#') unless result
|
|
74
|
-
else
|
|
75
|
-
# In RubyMotion's Proc, the 'parameter' method does not exists, however its
|
|
76
|
-
# 'arity' method works just like the one from Method, only returning 0 when
|
|
77
|
-
# there is no parameters whatsoever, optional or not.
|
|
78
|
-
result = (code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
|
|
79
|
-
|
|
80
|
-
# Sadly, RubyMotion's Proc does not define the method 'source_location' either.
|
|
81
|
-
failures << code unless result
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
result
|
|
85
|
-
when Class
|
|
86
|
-
arity = code.instance_method(:initialize).arity
|
|
87
|
-
if arity == 0
|
|
88
|
-
instance = code.new
|
|
89
|
-
elsif arity == 1
|
|
90
|
-
instance = code.new(record)
|
|
91
|
-
else
|
|
92
|
-
instance = code.new(record, *args)
|
|
93
|
-
end
|
|
94
|
-
result = instance.call
|
|
95
|
-
|
|
96
|
-
if Method.method_defined?(:source_location)
|
|
97
|
-
failures << instance.method(:call).source_location.join('#') unless result
|
|
98
|
-
else
|
|
99
|
-
# RubyMotion support ('source_location' not defined for Method)
|
|
100
|
-
failures << instance.method(:call) unless result
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
result
|
|
104
|
-
when Array
|
|
105
|
-
if options[:guard]
|
|
106
|
-
# invoke guard callbacks
|
|
107
|
-
code.all? {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
|
108
|
-
elsif options[:unless]
|
|
109
|
-
# invoke unless callbacks
|
|
110
|
-
code.all? {|a| !invoke_callbacks_compatible_with_guard(a, record, args)}
|
|
111
|
-
else
|
|
112
|
-
# invoke after callbacks
|
|
113
|
-
code.map {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
|
114
|
-
end
|
|
115
|
-
else
|
|
116
|
-
true
|
|
117
|
-
end
|
|
72
|
+
Invoker.new(code, record, args)
|
|
73
|
+
.with_options(options)
|
|
74
|
+
.with_failures(failures)
|
|
75
|
+
.invoke
|
|
118
76
|
end
|
|
119
77
|
|
|
120
78
|
def _fire_callbacks(code, record, args)
|
|
121
|
-
|
|
122
|
-
when Symbol, String
|
|
123
|
-
arity = record.send(:method, code.to_sym).arity
|
|
124
|
-
record.send(code, *(arity < 0 ? args : args[0...arity]))
|
|
125
|
-
when Proc
|
|
126
|
-
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
|
|
127
|
-
when Array
|
|
128
|
-
code.map {|a| _fire_callbacks(a, record, args)}
|
|
129
|
-
else
|
|
130
|
-
true
|
|
131
|
-
end
|
|
79
|
+
Invoker.new(code, record, args).invoke
|
|
132
80
|
end
|
|
133
81
|
|
|
134
82
|
end
|
data/lib/aasm/dsl_helper.rb
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
module
|
|
1
|
+
module AASM
|
|
2
|
+
module DslHelper
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
class Proxy
|
|
5
|
+
attr_accessor :options
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
def initialize(options, valid_keys, source)
|
|
8
|
+
@valid_keys = valid_keys
|
|
9
|
+
@source = source
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
def method_missing(name, *args, &block)
|
|
15
|
+
if @valid_keys.include?(name)
|
|
16
|
+
options[name] = Array(options[name])
|
|
17
|
+
options[name] << block if block
|
|
18
|
+
options[name] += Array(args)
|
|
19
|
+
else
|
|
20
|
+
@source.send name, *args, &block
|
|
21
|
+
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
|
-
end
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
def add_options_from_dsl(options, valid_keys, &block)
|
|
26
|
+
proxy = Proxy.new(options, valid_keys, self)
|
|
27
|
+
proxy.instance_eval(&block)
|
|
28
|
+
proxy.options
|
|
29
|
+
end
|
|
29
30
|
|
|
30
|
-
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/aasm/errors.rb
CHANGED
|
@@ -3,18 +3,16 @@ module AASM
|
|
|
3
3
|
class UnknownStateMachineError < RuntimeError; end
|
|
4
4
|
|
|
5
5
|
class InvalidTransition < RuntimeError
|
|
6
|
-
attr_reader :object, :event_name, :originating_state, :failures
|
|
6
|
+
attr_reader :object, :event_name, :originating_state, :failures, :state_machine_name
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
def message
|
|
13
|
-
"Event '#{event_name}' cannot transition from '#{originating_state}'. #{reasoning}"
|
|
10
|
+
@state_machine_name = state_machine_name
|
|
11
|
+
super("Event '#{event_name}' cannot transition from '#{originating_state}'.#{reasoning}")
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def reasoning
|
|
17
|
-
"Failed callback(s): #{failures}." unless failures.empty?
|
|
15
|
+
" Failed callback(s): #{failures}." unless failures.empty?
|
|
18
16
|
end
|
|
19
17
|
end
|
|
20
18
|
|
data/lib/aasm/instance_base.rb
CHANGED
|
@@ -14,7 +14,6 @@ module AASM
|
|
|
14
14
|
|
|
15
15
|
def current_state=(state)
|
|
16
16
|
@instance.aasm_write_state_without_persistence(state, @name)
|
|
17
|
-
# @current_state = state
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
def enter_initial_state
|
|
@@ -22,7 +21,6 @@ module AASM
|
|
|
22
21
|
state_object = state_object_for_name(state_name)
|
|
23
22
|
|
|
24
23
|
state_object.fire_callbacks(:before_enter, @instance)
|
|
25
|
-
# state_object.fire_callbacks(:enter, @instance)
|
|
26
24
|
self.current_state = state_name
|
|
27
25
|
state_object.fire_callbacks(:after_enter, @instance)
|
|
28
26
|
|
|
@@ -30,7 +28,7 @@ module AASM
|
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
def human_state
|
|
33
|
-
|
|
31
|
+
state_object_for_name(current_state).display_name
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
def states(options={}, *args)
|
|
@@ -80,6 +78,17 @@ module AASM
|
|
|
80
78
|
events
|
|
81
79
|
end
|
|
82
80
|
|
|
81
|
+
def permitted_transitions
|
|
82
|
+
events(permitted: true).flat_map do |event|
|
|
83
|
+
available_transitions = event.transitions_from_state(current_state)
|
|
84
|
+
allowed_transitions = available_transitions.select { |t| t.allowed?(@instance) }
|
|
85
|
+
|
|
86
|
+
allowed_transitions.map do |transition|
|
|
87
|
+
{ event: event.name, state: transition.to }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
def state_object_for_name(name)
|
|
84
93
|
obj = @instance.class.aasm(@name).states.find {|s| s.name == name}
|
|
85
94
|
raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
|
|
@@ -93,7 +102,7 @@ module AASM
|
|
|
93
102
|
when Proc
|
|
94
103
|
state.call(@instance)
|
|
95
104
|
else
|
|
96
|
-
raise NotImplementedError, "Unrecognized state-type given.
|
|
105
|
+
raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
|
|
97
106
|
end
|
|
98
107
|
end
|
|
99
108
|
|
|
@@ -105,6 +114,15 @@ module AASM
|
|
|
105
114
|
end
|
|
106
115
|
end
|
|
107
116
|
|
|
117
|
+
def fire(event_name, *args, &block)
|
|
118
|
+
@instance.send(event_name, *args, &block)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def fire!(event_name, *args, &block)
|
|
122
|
+
event_name = event_name.to_s.+("!").to_sym
|
|
123
|
+
@instance.send(event_name, *args, &block)
|
|
124
|
+
end
|
|
125
|
+
|
|
108
126
|
def set_current_state_with_persistence(state)
|
|
109
127
|
save_success = @instance.aasm_write_state(state, @name)
|
|
110
128
|
self.current_state = state if save_success
|