rails-workflow 1.4.5.4 → 1.4.6.4
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/.gitignore +2 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +2 -1
- data/Rakefile +4 -4
- data/bin/console +3 -3
- data/lib/active_support/overloads.rb +13 -6
- data/lib/workflow.rb +12 -279
- data/lib/workflow/adapters/active_record.rb +57 -50
- data/lib/workflow/adapters/active_record_validations.rb +25 -19
- data/lib/workflow/adapters/adapter.rb +23 -0
- data/lib/workflow/adapters/remodel.rb +8 -9
- data/lib/workflow/callbacks.rb +60 -45
- data/lib/workflow/callbacks/callback.rb +23 -37
- data/lib/workflow/callbacks/method_callback.rb +12 -0
- data/lib/workflow/callbacks/proc_callback.rb +23 -0
- data/lib/workflow/callbacks/string_callback.rb +12 -0
- data/lib/workflow/callbacks/transition_callback.rb +88 -78
- data/lib/workflow/callbacks/transition_callbacks/method_caller.rb +53 -0
- data/lib/workflow/callbacks/transition_callbacks/proc_caller.rb +60 -0
- data/lib/workflow/configuration.rb +1 -0
- data/lib/workflow/definition.rb +73 -0
- data/lib/workflow/errors.rb +37 -6
- data/lib/workflow/event.rb +30 -15
- data/lib/workflow/helper_method_configurator.rb +100 -0
- data/lib/workflow/specification.rb +45 -22
- data/lib/workflow/state.rb +45 -36
- data/lib/workflow/transition_context.rb +5 -4
- data/lib/workflow/transitions.rb +94 -0
- data/lib/workflow/version.rb +2 -1
- data/rails-workflow.gemspec +18 -18
- data/tags.markdown +31 -0
- metadata +13 -5
- data/lib/workflow/callbacks/transition_callbacks/method_wrapper.rb +0 -102
- data/lib/workflow/callbacks/transition_callbacks/proc_wrapper.rb +0 -48
- data/lib/workflow/draw.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5aea9261c6bf6fa5ab1c793179ef8440a67be0d6
|
4
|
+
data.tar.gz: aa69e4c243b24250f6880cfcfcf3efafc33b9a15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad8375bdbe64ae0ddadf8d9e272e22d198dfc87108327fba67d7c2852955c334334efafe24195b4f0d6ffe2d18ce726205fff343142a8c2cbe46c0e44721988e
|
7
|
+
data.tar.gz: ccb120fe43028613ab269556076cbee0a7113da2831d0c32308cd68a680080e99fc21e68b50142f8843bb57813fe67d82facc0bfa1d387e6eabac5ad2c40287c
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Exclude:
|
4
|
+
- 'vendor/**/*'
|
5
|
+
- 'config/initializers/*'
|
6
|
+
- 'tmp/**/*'
|
7
|
+
- 'db/**/*'
|
8
|
+
- 'spec/spec_helper.rb'
|
9
|
+
- 'lib/tasks/**/*'
|
10
|
+
- 'spec/rails_helper.rb'
|
11
|
+
- 'spec/support/*'
|
12
|
+
- 'spec/active_support_callbacks_spec.rb'
|
13
|
+
- 'rails-workflow.gemspec'
|
14
|
+
- 'bin/*'
|
15
|
+
|
16
|
+
Style/Documentation:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Metrics/LineLength:
|
20
|
+
Max: 100
|
21
|
+
|
22
|
+
Metrics/MethodLength:
|
23
|
+
Max: 14
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'rubygems'
|
2
3
|
require 'rake/testtask'
|
3
4
|
require 'rdoc/task'
|
@@ -5,7 +6,7 @@ require 'rdoc/task'
|
|
5
6
|
require 'bundler'
|
6
7
|
Bundler.setup
|
7
8
|
|
8
|
-
task :
|
9
|
+
task default: [:test]
|
9
10
|
|
10
11
|
require 'rake'
|
11
12
|
Rake::TestTask.new do |t|
|
@@ -24,7 +25,6 @@ Rake::TestTask.new do |t|
|
|
24
25
|
end
|
25
26
|
|
26
27
|
Rake::RDocTask.new do |rdoc|
|
27
|
-
rdoc.rdoc_files.include(
|
28
|
-
rdoc.options <<
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
rdoc.options << '-S'
|
29
30
|
end
|
30
|
-
|
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'workflow'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "workflow"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_support/callbacks'
|
2
3
|
|
3
4
|
module ActiveSupport
|
@@ -6,16 +7,17 @@ module ActiveSupport
|
|
6
7
|
# to a lambda for the present purposes.
|
7
8
|
module CallbackOverloads
|
8
9
|
private
|
10
|
+
|
9
11
|
def make_lambda(filter)
|
10
|
-
if filter.
|
11
|
-
|
12
|
+
if filter.is_a? Workflow::Callbacks::TransitionCallback
|
13
|
+
filter
|
12
14
|
else
|
13
15
|
super
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
19
|
def compute_identifier(filter)
|
18
|
-
if filter.
|
20
|
+
if filter.is_a? Workflow::Callbacks::TransitionCallback
|
19
21
|
super(filter.raw_proc)
|
20
22
|
else
|
21
23
|
super
|
@@ -24,9 +26,14 @@ module ActiveSupport
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
# Overload {ActiveSupport::Callbacks::Callback} with methods from
|
29
|
+
# Overload {ActiveSupport::Callbacks::Callback} with methods from
|
30
|
+
# {ActiveSupport::CallbackOverloads}.
|
28
31
|
# {Workflow::Callbacks::TransitionCallback}, which is duck-type equivalent
|
29
32
|
# to a lambda for the present purposes.
|
30
|
-
|
31
|
-
|
33
|
+
module ActiveSupport
|
34
|
+
module Callbacks
|
35
|
+
class Callback
|
36
|
+
prepend ActiveSupport::CallbackOverloads
|
37
|
+
end
|
38
|
+
end
|
32
39
|
end
|
data/lib/workflow.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'rubygems'
|
2
3
|
require 'active_support/concern'
|
3
4
|
require 'active_support/callbacks'
|
@@ -5,21 +6,30 @@ require 'workflow/version'
|
|
5
6
|
require 'workflow/configuration'
|
6
7
|
require 'workflow/specification'
|
7
8
|
require 'workflow/callbacks'
|
9
|
+
require 'workflow/helper_method_configurator'
|
8
10
|
require 'workflow/adapters/active_record'
|
9
11
|
require 'workflow/adapters/remodel'
|
12
|
+
require 'workflow/transitions'
|
13
|
+
require 'workflow/definition'
|
14
|
+
require 'workflow/adapters/adapter'
|
10
15
|
require 'workflow/adapters/active_record_validations'
|
11
16
|
require 'workflow/transition_context'
|
12
17
|
require 'active_support/overloads'
|
13
18
|
|
14
|
-
|
15
19
|
# See also README.markdown for documentation
|
16
20
|
module Workflow
|
17
21
|
# @!parse include Callbacks
|
22
|
+
# @!parse include Transitions
|
23
|
+
# @!parse include Definition
|
18
24
|
# @!parse extend Callbacks::ClassMethods
|
19
25
|
|
20
26
|
extend ActiveSupport::Concern
|
21
27
|
include Callbacks
|
22
28
|
include Errors
|
29
|
+
include Transitions
|
30
|
+
include Definition
|
31
|
+
|
32
|
+
include Adapters::Adapter
|
23
33
|
|
24
34
|
# The application-wide Workflow configuration object
|
25
35
|
CONFIGURATION = Configuration.new
|
@@ -30,283 +40,6 @@ module Workflow
|
|
30
40
|
# @return [nil]
|
31
41
|
def self.config(&block)
|
32
42
|
block.call(CONFIGURATION) if block_given?
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
included do
|
37
|
-
|
38
|
-
# Look for a hook; otherwise detect based on ancestor class.
|
39
|
-
if respond_to?(:workflow_adapter)
|
40
|
-
include self.workflow_adapter
|
41
|
-
else
|
42
|
-
if Object.const_defined?(:ActiveRecord) && self < ActiveRecord::Base
|
43
|
-
include Adapter::ActiveRecord
|
44
|
-
include Adapter::ActiveRecordValidations
|
45
|
-
end
|
46
|
-
if Object.const_defined?(:Remodel) && klass < Adapter::Remodel::Entity
|
47
|
-
include Adapter::Remodel::InstanceMethods
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Returns a state object representing the current workflow state.
|
53
|
-
#
|
54
|
-
# @return [State] Current workflow state
|
55
|
-
def current_state
|
56
|
-
loaded_state = load_workflow_state
|
57
|
-
res = workflow_spec.states.find{|t| t.name==loaded_state.to_sym} if loaded_state
|
58
|
-
res || workflow_spec.initial_state
|
59
|
-
end
|
60
|
-
|
61
|
-
# Deprecated. Check for false return value from {#transition!}
|
62
|
-
# @return [Boolean] true if the last transition was halted by one of the transition callbacks.
|
63
|
-
def halted?
|
64
|
-
@halted
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns the reason given to a call to {#halt} or {#halt!}, if any.
|
68
|
-
# @return [String] The reason the transition was aborted.
|
69
|
-
attr_reader :halted_because
|
70
|
-
|
71
|
-
# @api private
|
72
|
-
# @return [Workflow::TransitionContext] During transition, or nil if no transition is underway.
|
73
|
-
# During a state transition, contains transition-specific information:
|
74
|
-
# * The name of the {Workflow::State} being exited,
|
75
|
-
# * The name of the {Workflow::State} being entered,
|
76
|
-
# * The name of the {Workflow::Event} that was fired,
|
77
|
-
# * And whatever arguments were passed to the {Workflow#transition!} method.
|
78
|
-
attr_reader :transition_context
|
79
|
-
|
80
|
-
# Initiates state transition via the named event
|
81
|
-
#
|
82
|
-
# @param [Symbol] name name of event to initiate
|
83
|
-
# @param [Array] args State transition arguments.
|
84
|
-
# @return [Symbol] The name of the new state, or `false` if the transition failed.
|
85
|
-
# TODO: connect args to documentation on how arguments are accessed during state transitions.
|
86
|
-
def transition!(name, *args, **attributes)
|
87
|
-
name = name.to_sym
|
88
|
-
event = current_state.find_event(name)
|
89
|
-
raise NoTransitionAllowed.new(
|
90
|
-
"There is no event #{name} defined for the #{current_state.name} state") \
|
91
|
-
if event.nil?
|
92
|
-
|
93
|
-
@halted_because = nil
|
94
|
-
@halted = false
|
95
|
-
|
96
|
-
target = event.evaluate(self)
|
97
|
-
unless target
|
98
|
-
raise NoMatchingTransitionError.new("No matching transition found on #{name} for target #{target}. Consider adding a catchall transition.")
|
99
|
-
end
|
100
|
-
|
101
|
-
from = current_state
|
102
|
-
return_value = false
|
103
|
-
begin
|
104
|
-
@transition_context = TransitionContext.new \
|
105
|
-
from: from.name,
|
106
|
-
to: target.name,
|
107
|
-
event: name,
|
108
|
-
event_args: args,
|
109
|
-
attributes: attributes,
|
110
|
-
named_arguments: workflow_spec.named_arguments
|
111
|
-
|
112
|
-
run_all_callbacks do
|
113
|
-
callback_value = run_action_callback name, *args
|
114
|
-
persist_value = persist_workflow_state(target.name)
|
115
|
-
return_value = callback_value || persist_value
|
116
|
-
end
|
117
|
-
ensure
|
118
|
-
@transition_context = nil
|
119
|
-
end
|
120
|
-
return_value
|
121
|
-
end
|
122
|
-
|
123
|
-
# Stop the current transition and set the reason for the abort.
|
124
|
-
#
|
125
|
-
# @param [String] reason Optional reason for halting transition.
|
126
|
-
# @return [nil]
|
127
|
-
def halt(reason = nil)
|
128
|
-
@halted_because = reason
|
129
|
-
@halted = true
|
130
|
-
throw :abort
|
131
|
-
end
|
132
|
-
|
133
|
-
# Sets halt reason and raises [TransitionHaltedError] error.
|
134
|
-
#
|
135
|
-
# @param [String] reason Optional reason for halting
|
136
|
-
# @return [nil]
|
137
|
-
def halt!(reason = nil)
|
138
|
-
@halted_because = reason
|
139
|
-
@halted = true
|
140
|
-
raise TransitionHaltedError.new(reason)
|
141
|
-
end
|
142
|
-
|
143
|
-
# The specification for this object.
|
144
|
-
# Could be set on a singleton for the object, on the object's class,
|
145
|
-
# Or else on a superclass of the object.
|
146
|
-
# @return [Specification] The Specification that applies to this object.
|
147
|
-
def workflow_spec
|
148
|
-
# check the singleton class first
|
149
|
-
class << self
|
150
|
-
return workflow_spec if workflow_spec
|
151
|
-
end
|
152
|
-
|
153
|
-
c = self.class
|
154
|
-
# using a simple loop instead of class_inheritable_accessor to avoid
|
155
|
-
# dependency on Rails' ActiveSupport
|
156
|
-
until c.workflow_spec || !(c.include? Workflow)
|
157
|
-
c = c.superclass
|
158
|
-
end
|
159
|
-
c.workflow_spec
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
|
164
|
-
def has_callback?(action)
|
165
|
-
# 1. public callback method or
|
166
|
-
# 2. protected method somewhere in the class hierarchy or
|
167
|
-
# 3. private in the immediate class (parent classes ignored)
|
168
|
-
action = action.to_sym
|
169
|
-
self.respond_to?(action) or
|
170
|
-
self.class.protected_method_defined?(action) or
|
171
|
-
self.private_methods(false).map(&:to_sym).include?(action)
|
172
|
-
end
|
173
|
-
|
174
|
-
def run_action_callback(action_name, *args)
|
175
|
-
action = action_name.to_sym
|
176
|
-
if has_callback?(action)
|
177
|
-
meth = method(action)
|
178
|
-
check_method_arity! meth, *args
|
179
|
-
meth.call *args
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def check_method_arity!(method, *args)
|
184
|
-
arity = method.arity
|
185
|
-
|
186
|
-
unless (arity >= 0 && args.length == arity) || (arity < 0 && (args.length + 1) >= arity.abs)
|
187
|
-
raise CallbackArityError.new("Method #{method.name} has arity #{arity} but was called with #{args.length} arguments.")
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# load_workflow_state and persist_workflow_state
|
192
|
-
# can be overriden to handle the persistence of the workflow state.
|
193
|
-
#
|
194
|
-
# Default (non ActiveRecord) implementation stores the current state
|
195
|
-
# in a variable.
|
196
|
-
#
|
197
|
-
# Default ActiveRecord implementation uses a 'workflow_state' database column.
|
198
|
-
def load_workflow_state
|
199
|
-
@workflow_state if instance_variable_defined? :@workflow_state
|
200
|
-
end
|
201
|
-
|
202
|
-
def persist_workflow_state(new_value)
|
203
|
-
@workflow_state = new_value
|
204
|
-
end
|
205
|
-
|
206
|
-
module ClassMethods
|
207
|
-
attr_reader :workflow_spec
|
208
|
-
|
209
|
-
# Instructs Workflow which column to use to persist workflow state.
|
210
|
-
#
|
211
|
-
# @param [Symbol] column_name If provided, will set a new workflow column name.
|
212
|
-
# @return [Symbol] The current (or new) name for the workflow column on this class.
|
213
|
-
def workflow_column(column_name=nil)
|
214
|
-
if column_name
|
215
|
-
@workflow_state_column_name = column_name.to_sym
|
216
|
-
end
|
217
|
-
if !instance_variable_defined?('@workflow_state_column_name') && superclass.respond_to?(:workflow_column)
|
218
|
-
@workflow_state_column_name = superclass.workflow_column
|
219
|
-
end
|
220
|
-
@workflow_state_column_name ||= :workflow_state
|
221
|
-
end
|
222
|
-
|
223
|
-
|
224
|
-
##
|
225
|
-
# Define workflow for the class.
|
226
|
-
#
|
227
|
-
# @yield [] Specification of workflow. Example below and in README.markdown
|
228
|
-
# @return [nil]
|
229
|
-
#
|
230
|
-
# Workflow definition takes place inside the yielded block.
|
231
|
-
# @see Specification::state
|
232
|
-
# @see Specification::event
|
233
|
-
#
|
234
|
-
# ~~~ruby
|
235
|
-
#
|
236
|
-
# class Article
|
237
|
-
# include Workflow
|
238
|
-
# workflow do
|
239
|
-
# state :new do
|
240
|
-
# event :submit, :transitions_to => :awaiting_review
|
241
|
-
# end
|
242
|
-
# state :awaiting_review do
|
243
|
-
# event :review, :transitions_to => :being_reviewed
|
244
|
-
# end
|
245
|
-
# state :being_reviewed do
|
246
|
-
# event :accept, :transitions_to => :accepted
|
247
|
-
# event :reject, :transitions_to => :rejected
|
248
|
-
# end
|
249
|
-
# state :accepted
|
250
|
-
# state :rejected
|
251
|
-
# end
|
252
|
-
# end
|
253
|
-
#
|
254
|
-
#~~~
|
255
|
-
#
|
256
|
-
def workflow(&specification)
|
257
|
-
assign_workflow Specification.new(Hash.new, &specification)
|
258
|
-
end
|
259
|
-
|
260
|
-
private
|
261
|
-
|
262
|
-
# Creates the convinience methods like `my_transition!`
|
263
|
-
def assign_workflow(specification_object)
|
264
|
-
# Merging two workflow specifications can **not** be done automically, so
|
265
|
-
# just make the latest specification win. Same for inheritance -
|
266
|
-
# definition in the subclass wins.
|
267
|
-
if self.superclass.respond_to?(:workflow_spec, true) && self.superclass.workflow_spec
|
268
|
-
undefine_methods_defined_by_workflow_spec superclass.workflow_spec
|
269
|
-
end
|
270
|
-
|
271
|
-
@workflow_spec = specification_object
|
272
|
-
@workflow_spec.states.each do |state|
|
273
|
-
state_name = state.name
|
274
|
-
module_eval do
|
275
|
-
define_method "#{state_name}?" do
|
276
|
-
state_name == current_state.name
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
state.events.each do |event|
|
281
|
-
event_name = event.name
|
282
|
-
module_eval do
|
283
|
-
define_method "#{event_name}!".to_sym do |*args|
|
284
|
-
transition!(event_name, *args)
|
285
|
-
end
|
286
|
-
|
287
|
-
define_method "can_#{event_name}?" do
|
288
|
-
return !!current_state.find_event(event_name)&.evaluate(self)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def undefine_methods_defined_by_workflow_spec(inherited_workflow_spec)
|
296
|
-
inherited_workflow_spec.states.each do |state|
|
297
|
-
state_name = state.name
|
298
|
-
module_eval do
|
299
|
-
undef_method "#{state_name}?"
|
300
|
-
end
|
301
|
-
|
302
|
-
state.events.each do |event|
|
303
|
-
event_name = event.name
|
304
|
-
module_eval do
|
305
|
-
undef_method "#{event_name}!".to_sym
|
306
|
-
undef_method "can_#{event_name}?"
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
43
|
+
CONFIGURATION
|
311
44
|
end
|
312
45
|
end
|