aasm 4.5.1 → 5.5.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 (186) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +809 -129
  4. data/lib/aasm/aasm.rb +74 -37
  5. data/lib/aasm/base.rb +188 -41
  6. data/lib/aasm/configuration.rb +27 -2
  7. data/lib/aasm/core/event.rb +75 -47
  8. data/lib/aasm/core/invoker.rb +129 -0
  9. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  10. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  11. data/lib/aasm/core/invokers/literal_invoker.rb +49 -0
  12. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  13. data/lib/aasm/core/state.rb +22 -13
  14. data/lib/aasm/core/transition.rb +30 -23
  15. data/lib/aasm/dsl_helper.rb +24 -22
  16. data/lib/aasm/errors.rb +8 -5
  17. data/lib/aasm/instance_base.rb +63 -15
  18. data/lib/aasm/localizer.rb +13 -3
  19. data/lib/aasm/minitest/allow_event.rb +13 -0
  20. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  21. data/lib/aasm/minitest/have_state.rb +13 -0
  22. data/lib/aasm/minitest/transition_from.rb +21 -0
  23. data/lib/aasm/minitest.rb +5 -0
  24. data/lib/aasm/minitest_spec.rb +15 -0
  25. data/lib/aasm/persistence/active_record_persistence.rb +87 -79
  26. data/lib/aasm/persistence/base.rb +30 -30
  27. data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  28. data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  29. data/lib/aasm/persistence/mongoid_persistence.rb +49 -35
  30. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  31. data/lib/aasm/persistence/orm.rb +154 -0
  32. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  33. data/lib/aasm/persistence/redis_persistence.rb +112 -0
  34. data/lib/aasm/persistence/sequel_persistence.rb +37 -67
  35. data/lib/aasm/persistence.rb +20 -5
  36. data/lib/aasm/rspec/allow_event.rb +5 -1
  37. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  38. data/lib/aasm/rspec/transition_from.rb +8 -4
  39. data/lib/aasm/state_machine.rb +6 -12
  40. data/lib/aasm/state_machine_store.rb +76 -0
  41. data/lib/aasm/version.rb +1 -1
  42. data/lib/aasm.rb +8 -2
  43. data/lib/generators/aasm/aasm_generator.rb +16 -0
  44. data/lib/generators/aasm/orm_helpers.rb +41 -0
  45. data/lib/generators/active_record/aasm_generator.rb +40 -0
  46. data/lib/generators/active_record/templates/migration.rb +8 -0
  47. data/lib/generators/active_record/templates/migration_existing.rb +5 -0
  48. data/lib/generators/mongoid/aasm_generator.rb +28 -0
  49. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  50. data/lib/motion-aasm.rb +37 -0
  51. metadata +104 -259
  52. data/.document +0 -6
  53. data/.gitignore +0 -19
  54. data/.travis.yml +0 -37
  55. data/API +0 -34
  56. data/CHANGELOG.md +0 -272
  57. data/CODE_OF_CONDUCT.md +0 -13
  58. data/Gemfile +0 -15
  59. data/HOWTO +0 -12
  60. data/PLANNED_CHANGES.md +0 -11
  61. data/README_FROM_VERSION_3_TO_4.md +0 -240
  62. data/Rakefile +0 -26
  63. data/aasm.gemspec +0 -31
  64. data/callbacks.txt +0 -51
  65. data/gemfiles/rails_3.2.gemfile +0 -14
  66. data/gemfiles/rails_4.0.gemfile +0 -12
  67. data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -14
  68. data/gemfiles/rails_4.1.gemfile +0 -12
  69. data/gemfiles/rails_4.1_mongo_mapper.gemfile +0 -14
  70. data/gemfiles/rails_4.2.gemfile +0 -12
  71. data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -14
  72. data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -12
  73. data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -157
  74. data/spec/database.rb +0 -63
  75. data/spec/database.yml +0 -3
  76. data/spec/en.yml +0 -9
  77. data/spec/en_deprecated_style.yml +0 -10
  78. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
  79. data/spec/models/active_record/complex_active_record_example.rb +0 -33
  80. data/spec/models/active_record/derivate_new_dsl.rb +0 -7
  81. data/spec/models/active_record/false_state.rb +0 -35
  82. data/spec/models/active_record/gate.rb +0 -39
  83. data/spec/models/active_record/localizer_test_model.rb +0 -34
  84. data/spec/models/active_record/no_direct_assignment.rb +0 -21
  85. data/spec/models/active_record/no_scope.rb +0 -21
  86. data/spec/models/active_record/persisted_state.rb +0 -12
  87. data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
  88. data/spec/models/active_record/reader.rb +0 -7
  89. data/spec/models/active_record/readme_job.rb +0 -21
  90. data/spec/models/active_record/simple_new_dsl.rb +0 -17
  91. data/spec/models/active_record/thief.rb +0 -29
  92. data/spec/models/active_record/transient.rb +0 -6
  93. data/spec/models/active_record/with_enum.rb +0 -39
  94. data/spec/models/active_record/with_false_enum.rb +0 -31
  95. data/spec/models/active_record/with_true_enum.rb +0 -39
  96. data/spec/models/active_record/writer.rb +0 -6
  97. data/spec/models/basic_two_state_machines_example.rb +0 -25
  98. data/spec/models/callbacks/basic.rb +0 -78
  99. data/spec/models/callbacks/basic_multiple.rb +0 -75
  100. data/spec/models/callbacks/guard_within_block.rb +0 -66
  101. data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
  102. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -65
  103. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
  104. data/spec/models/callbacks/private_method.rb +0 -44
  105. data/spec/models/callbacks/private_method_multiple.rb +0 -44
  106. data/spec/models/callbacks/with_args.rb +0 -61
  107. data/spec/models/callbacks/with_args_multiple.rb +0 -61
  108. data/spec/models/callbacks/with_state_arg.rb +0 -26
  109. data/spec/models/callbacks/with_state_arg_multiple.rb +0 -26
  110. data/spec/models/complex_example.rb +0 -222
  111. data/spec/models/conversation.rb +0 -93
  112. data/spec/models/default_state.rb +0 -12
  113. data/spec/models/double_definer.rb +0 -21
  114. data/spec/models/foo.rb +0 -92
  115. data/spec/models/foo_callback_multiple.rb +0 -45
  116. data/spec/models/guardian.rb +0 -48
  117. data/spec/models/guardian_multiple.rb +0 -48
  118. data/spec/models/initial_state_proc.rb +0 -31
  119. data/spec/models/invalid_persistor.rb +0 -31
  120. data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +0 -37
  121. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
  122. data/spec/models/mongo_mapper/simple_mongo_mapper.rb +0 -23
  123. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
  124. data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
  125. data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
  126. data/spec/models/mongoid/simple_mongoid.rb +0 -23
  127. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
  128. data/spec/models/no_initial_state.rb +0 -25
  129. data/spec/models/not_auto_loaded/process.rb +0 -21
  130. data/spec/models/parametrised_event.rb +0 -29
  131. data/spec/models/parametrised_event_multiple.rb +0 -29
  132. data/spec/models/process_with_new_dsl.rb +0 -31
  133. data/spec/models/provided_state.rb +0 -24
  134. data/spec/models/sequel/complex_sequel_example.rb +0 -45
  135. data/spec/models/sequel/sequel_multiple.rb +0 -25
  136. data/spec/models/sequel/sequel_simple.rb +0 -25
  137. data/spec/models/silencer.rb +0 -27
  138. data/spec/models/simple_example.rb +0 -15
  139. data/spec/models/simple_multiple_example.rb +0 -30
  140. data/spec/models/state_machine_with_failed_event.rb +0 -12
  141. data/spec/models/sub_class.rb +0 -7
  142. data/spec/models/sub_class_with_more_states.rb +0 -18
  143. data/spec/models/sub_classing.rb +0 -3
  144. data/spec/models/super_class.rb +0 -46
  145. data/spec/models/this_name_better_not_be_in_use.rb +0 -11
  146. data/spec/models/transactor.rb +0 -53
  147. data/spec/models/valid_state_name.rb +0 -23
  148. data/spec/models/validator.rb +0 -79
  149. data/spec/models/worker.rb +0 -2
  150. data/spec/spec_helper.rb +0 -25
  151. data/spec/unit/api_spec.rb +0 -77
  152. data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
  153. data/spec/unit/callback_multiple_spec.rb +0 -295
  154. data/spec/unit/callbacks_spec.rb +0 -296
  155. data/spec/unit/complex_example_spec.rb +0 -84
  156. data/spec/unit/complex_multiple_example_spec.rb +0 -99
  157. data/spec/unit/edge_cases_spec.rb +0 -16
  158. data/spec/unit/event_multiple_spec.rb +0 -73
  159. data/spec/unit/event_naming_spec.rb +0 -11
  160. data/spec/unit/event_spec.rb +0 -322
  161. data/spec/unit/guard_multiple_spec.rb +0 -60
  162. data/spec/unit/guard_spec.rb +0 -60
  163. data/spec/unit/initial_state_multiple_spec.rb +0 -15
  164. data/spec/unit/initial_state_spec.rb +0 -12
  165. data/spec/unit/inspection_multiple_spec.rb +0 -201
  166. data/spec/unit/inspection_spec.rb +0 -111
  167. data/spec/unit/localizer_spec.rb +0 -76
  168. data/spec/unit/memory_leak_spec.rb +0 -38
  169. data/spec/unit/new_dsl_spec.rb +0 -12
  170. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -573
  171. data/spec/unit/persistence/active_record_persistence_spec.rb +0 -552
  172. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -146
  173. data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -93
  174. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -127
  175. data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -79
  176. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -153
  177. data/spec/unit/persistence/sequel_persistence_spec.rb +0 -100
  178. data/spec/unit/readme_spec.rb +0 -42
  179. data/spec/unit/reloading_spec.rb +0 -15
  180. data/spec/unit/rspec_matcher_spec.rb +0 -79
  181. data/spec/unit/simple_example_spec.rb +0 -42
  182. data/spec/unit/simple_multiple_example_spec.rb +0 -63
  183. data/spec/unit/state_spec.rb +0 -89
  184. data/spec/unit/subclassing_multiple_spec.rb +0 -39
  185. data/spec/unit/subclassing_spec.rb +0 -31
  186. data/spec/unit/transition_spec.rb +0 -291
@@ -1,29 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AASM::Core
2
4
  class Event
3
- include DslHelper
5
+ include AASM::DslHelper
4
6
 
5
- attr_reader :name, :state_machine, :options
7
+ attr_reader :name, :state_machine, :options, :default_display_name
6
8
 
7
9
  def initialize(name, state_machine, options = {}, &block)
8
10
  @name = name
9
11
  @state_machine = state_machine
10
12
  @transitions = []
13
+ @valid_transitions = Hash.new { |h, k| h[k] = {} }
11
14
  @guards = Array(options[:guard] || options[:guards] || options[:if])
12
15
  @unless = Array(options[:unless]) #TODO: This could use a better name
16
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
13
17
 
14
18
  # from aasm4
15
19
  @options = options # QUESTION: .dup ?
16
- add_options_from_dsl(@options, [:after, :before, :error, :success, :after_commit], &block) if block
20
+ add_options_from_dsl(@options, [
21
+ :after,
22
+ :after_commit,
23
+ :after_transaction,
24
+ :before,
25
+ :before_transaction,
26
+ :ensure,
27
+ :error,
28
+ :before_success,
29
+ :success,
30
+ ], &block) if block
31
+ end
32
+
33
+ # called internally by Ruby 1.9 after clone()
34
+ def initialize_copy(orig)
35
+ super
36
+ @transitions = @transitions.collect { |transition| transition.clone }
37
+ @guards = @guards.dup
38
+ @unless = @unless.dup
39
+ @options = {}
40
+ orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
17
41
  end
18
42
 
19
43
  # a neutered version of fire - it doesn't actually fire the event, it just
20
44
  # executes the transition guards to determine if a transition is even
21
45
  # an option given current conditions.
22
- def may_fire?(obj, to_state=nil, *args)
46
+ def may_fire?(obj, to_state=::AASM::NO_VALUE, *args)
23
47
  _fire(obj, {:test_only => true}, to_state, *args) # true indicates test firing
24
48
  end
25
49
 
26
- def fire(obj, options={}, to_state=nil, *args)
50
+ def fire(obj, options={}, to_state=::AASM::NO_VALUE, *args)
27
51
  _fire(obj, options, to_state, *args) # false indicates this is not a test (fire!)
28
52
  end
29
53
 
@@ -43,12 +67,23 @@ module AASM::Core
43
67
  @transitions.select { |t| t.to == state }
44
68
  end
45
69
 
70
+ def fire_global_callbacks(callback_name, record, *args)
71
+ invoke_callbacks(state_machine.global_callbacks[callback_name], record, args)
72
+ end
73
+
46
74
  def fire_callbacks(callback_name, record, *args)
47
75
  # strip out the first element in args if it's a valid to_state
48
76
  # #given where we're coming from, this condition implies args not empty
49
77
  invoke_callbacks(@options[callback_name], record, args)
50
78
  end
51
79
 
80
+ def fire_transition_callbacks(obj, *args)
81
+ from_state = obj.aasm(state_machine.name).current_state
82
+ transition = @valid_transitions[obj.object_id][from_state]
83
+ transition.invoke_success_callbacks(obj, *args) if transition
84
+ @valid_transitions.delete(obj.object_id)
85
+ end
86
+
52
87
  def ==(event)
53
88
  if event.is_a? Symbol
54
89
  name == event
@@ -65,13 +100,21 @@ module AASM::Core
65
100
  @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
66
101
  end
67
102
  # Create a transition if :to is specified without :from (transitions from ANY state)
68
- if @transitions.empty? && definitions[:to]
103
+ if !definitions[:from] && definitions[:to]
69
104
  @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions), &block)
70
105
  end
71
106
  end
72
107
  @transitions
73
108
  end
74
109
 
110
+ def failed_callbacks
111
+ transitions.flat_map(&:failures)
112
+ end
113
+
114
+ def to_s
115
+ name.to_s
116
+ end
117
+
75
118
  private
76
119
 
77
120
  def attach_event_guards(definitions)
@@ -86,33 +129,32 @@ module AASM::Core
86
129
  definitions
87
130
  end
88
131
 
89
- # Execute if test == false, otherwise return true/false depending on whether it would fire
90
- def _fire(obj, options={}, to_state=nil, *args)
132
+ def _fire(obj, options={}, to_state=::AASM::NO_VALUE, *args)
91
133
  result = options[:test_only] ? false : nil
92
- if @transitions.map(&:from).any?
93
- transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state }
94
- return result if transitions.size == 0
95
- else
96
- transitions = @transitions
97
- end
98
-
99
- # If to_state is not nil it either contains a potential
100
- # to_state or an arg
101
- unless to_state == nil
102
- if !to_state.respond_to?(:to_sym) || !transitions.map(&:to).flatten.include?(to_state.to_sym)
103
- args.unshift(to_state)
104
- to_state = nil
105
- end
134
+ clear_failed_callbacks
135
+ transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state || t.from == nil}
136
+ return result if transitions.size == 0
137
+
138
+ if to_state == ::AASM::NO_VALUE
139
+ to_state = nil
140
+ elsif !(to_state.respond_to?(:to_sym) && transitions.map(&:to).flatten.include?(to_state.to_sym))
141
+ # to_state is an argument
142
+ args.unshift(to_state)
143
+ to_state = nil
106
144
  end
145
+
146
+ # nop, to_state is a valid to-state
107
147
 
108
148
  transitions.each do |transition|
109
149
  next if to_state and !Array(transition.to).include?(to_state)
110
- if (options.key?(:may_fire) && Array(transition.to).include?(options[:may_fire])) ||
150
+ if (options.key?(:may_fire) && transition.eql?(options[:may_fire])) ||
111
151
  (!options.key?(:may_fire) && transition.allowed?(obj, *args))
112
- result = to_state || Array(transition.to).first
152
+
113
153
  if options[:test_only]
114
- # result = true
154
+ result = transition
115
155
  else
156
+ result = to_state || Array(transition.to).first
157
+ Array(transition.to).each {|to| @valid_transitions[obj.object_id][to] = transition }
116
158
  transition.execute(obj, *args)
117
159
  end
118
160
 
@@ -122,29 +164,15 @@ module AASM::Core
122
164
  result
123
165
  end
124
166
 
125
- def invoke_callbacks(code, record, args)
126
- case code
127
- when Symbol, String
128
- unless record.respond_to?(code, true)
129
- raise NoMethodError.new("NoMethodError: undefined method `#{code}' for #{record.inspect}:#{record.class}")
130
- end
131
- arity = record.send(:method, code.to_sym).arity
132
- record.send(code, *(arity < 0 ? args : args[0...arity]))
133
- true
134
-
135
- when Proc
136
- arity = code.arity
137
- record.instance_exec(*(arity < 0 ? args : args[0...arity]), &code)
138
- true
139
-
140
- when Array
141
- code.each {|a| invoke_callbacks(a, record, args)}
142
- true
143
-
144
- else
145
- false
146
- end
167
+ def clear_failed_callbacks
168
+ # https://github.com/aasm/aasm/issues/383, https://github.com/aasm/aasm/issues/599
169
+ transitions.each { |transition| transition.failures.clear }
147
170
  end
148
171
 
172
+ def invoke_callbacks(code, record, args)
173
+ Invoker.new(code, record, args)
174
+ .with_default_return_value(false)
175
+ .invoke
176
+ end
149
177
  end
150
178
  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 = 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,49 @@
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
+ req_args = args[0..(subject_arity - 1)]
34
+ return record.__send__(subject, **req_args[0]) if req_args[0].is_a?(Hash)
35
+ record.__send__(subject, *req_args)
36
+ end
37
+ # rubocop:enable Metrics/AbcSize
38
+
39
+ def record_error
40
+ [
41
+ NoMethodError,
42
+ 'NoMethodError: undefined method ' \
43
+ "`#{subject}' for #{record.inspect}:#{record.class}"
44
+ ]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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
@@ -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 ||= begin
58
+ @display_name = begin
43
59
  if Module.const_defined?(:I18n)
44
60
  localized_name
45
61
  else
46
- name.to_s.gsub(/_/, ' ').capitalize
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) then
64
- @display_name = options.delete(:display)
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
- case action
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