flow_machine 0.2.2 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cd2fd744bfa9fae413fd4c4f5c9d47d05f80520623fdc5d95dd8be0bf0e13b1
4
- data.tar.gz: f8f593c69fe42dbce33166fa81c3a92886bb70fe81169f24f1620f6c7d7733d8
3
+ metadata.gz: 07e50fbe9241a8d62738c46d08486aa3266540bdf16947f279e6468071381591
4
+ data.tar.gz: 19837451911696b6789209e65d6189ff65e7a01d05828297827339e3239d5e10
5
5
  SHA512:
6
- metadata.gz: 1459686da1123e9358ec5bbfc73d52e5db44058aa1f691b9a5ce36f0fddd01ba1a38f28eea8646974c0463843b1c5c2ad26b4d260649431bfade61294e0e9bd6
7
- data.tar.gz: 7006a927ea623460f60069e2ae9b082996fa571799651830692920924b61147fec783895093d3b1bf29dbebd76a4777606a61b658ffc3249012e7dfbf3d9d127
6
+ metadata.gz: 4a150dadcb0d9704686c57728c2c51aa7fe11ca349ca6a9462b1a38d3e05b0343b11babd41288733f791da1428a269556d406a026c323499ed5b346ee303af3b
7
+ data.tar.gz: b3dea74e0219d55b51f133a1aab01aac0b88f85a0ebf77e6a9849140c0dcfe443d81d430ec0a83da4dc607a1ec596a16859da71a5d6820daac74c0f4ff564472
data/README.md CHANGED
@@ -43,10 +43,15 @@ end
43
43
 
44
44
  class PublishedState < FlowMachine::WorkflowState
45
45
  on_enter :notify_email_author
46
+ on_exit :clear_published_at
46
47
 
47
48
  def notify_email_author
48
49
  # Send an email
49
50
  end
51
+
52
+ def clear_published_at
53
+ object.published_at = nil
54
+ end
50
55
  end
51
56
  ```
52
57
 
@@ -114,7 +119,8 @@ State and Workflow callbacks accept `if` and `unless` options. They may be a sym
114
119
 
115
120
  Declared in the `WorkflowState` class.
116
121
 
117
- * `on_enter` Called after the object has transitioned into the state
122
+ * `on_exit` Called after the object has transitioned out of the state.
123
+ * `on_enter` Called after the object has transitioned into the state. Triggered after the previous state's `on_exit`.
118
124
 
119
125
  The following are available when `Workflow#save` is used (`workflow.save` or `workflow.transition!`) *Not called if you call `save` directly on the decorated model*.
120
126
 
@@ -137,7 +143,7 @@ The following are available when `Workflow#save` is used:
137
143
 
138
144
  Declared as an option to the `transition` method inside an `event` block.
139
145
 
140
- * `after` Will be called after the transition has happened successfully. Useful when you only want something to trigger when moving from a specific state to another.
146
+ * `after` Will be called after the transition has happened successfully including persistance (if applicable). Useful when you only want something to trigger when moving from a specific state to another.
141
147
 
142
148
  `transition to: :published, after: :send_mailing_list_email`
143
149
 
data/Rakefile CHANGED
@@ -1,38 +1,34 @@
1
1
  begin
2
- require 'bundler/setup'
2
+ require "bundler/setup"
3
3
  rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
5
  end
6
6
 
7
- require 'rdoc/task'
7
+ require "rdoc/task"
8
8
 
9
9
  RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Workflow'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.rdoc')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
10
+ rdoc.rdoc_dir = "rdoc"
11
+ rdoc.title = "Workflow"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.rdoc")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
15
  end
16
16
 
17
-
18
-
19
-
20
17
  Bundler::GemHelper.install_tasks
21
18
 
22
- require 'rake/testtask'
19
+ require "rake/testtask"
23
20
 
24
21
  Rake::TestTask.new(:test) do |t|
25
- t.libs << 'lib'
26
- t.libs << 'test'
27
- t.pattern = 'test/**/*_test.rb'
22
+ t.libs << "lib"
23
+ t.libs << "test"
24
+ t.pattern = "test/**/*_test.rb"
28
25
  t.verbose = false
29
26
  end
30
27
 
31
-
32
28
  task default: :test
33
29
 
34
30
  begin
35
- require 'rspec/core/rake_task'
31
+ require "rspec/core/rake_task"
36
32
  RSpec::Core::RakeTask.new(:spec)
37
33
  rescue LoadError
38
34
  end
@@ -1,4 +1,4 @@
1
- $:.unshift(File.dirname(__FILE__))
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require "flow_machine/workflow"
4
4
  require "flow_machine/workflow/factory_methods"
@@ -9,4 +9,3 @@ require "flow_machine/change_callback"
9
9
 
10
10
  module FlowMachine
11
11
  end
12
-
@@ -1,43 +1,46 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/array/extract_options'
1
+ require "active_support/core_ext/object/blank"
2
+ require "active_support/core_ext/array/extract_options"
3
3
 
4
- class FlowMachine::Callback
5
- attr_accessor :method, :options
6
- def initialize(*args, &block)
7
- @options = args.extract_options!
8
- @method = args.shift unless block
9
- @block = block
10
- end
4
+ module FlowMachine
5
+ class Callback
6
+ attr_accessor :method, :options
7
+ def initialize(*args, &block)
8
+ @options = args.extract_options!
9
+ @method = args.shift unless block
10
+ @block = block
11
+ end
11
12
 
12
- def call(target, changes = {})
13
- return unless will_run? target, changes
14
- call!(target)
15
- end
13
+ def call(target, changes = {})
14
+ return unless will_run? target, changes
16
15
 
17
- # Runs the callback without any validations
18
- def call!(target)
19
- run_method_or_lambda(target, method.presence || @block)
20
- end
16
+ call!(target)
17
+ end
21
18
 
22
- def run_method_or_lambda(target, method)
23
- if method.respond_to? :call # is it a lambda
24
- target.instance_exec &method
25
- else
26
- run_method(target, method)
19
+ # Runs the callback without any validations
20
+ def call!(target)
21
+ run_method_or_lambda(target, method.presence || @block)
27
22
  end
28
- end
29
23
 
30
- def run_method(target, method)
31
- target.send(method)
32
- end
24
+ def run_method_or_lambda(target, method)
25
+ if method.respond_to? :call # is it a lambda
26
+ target.instance_exec(&method)
27
+ else
28
+ run_method(target, method)
29
+ end
30
+ end
31
+
32
+ def run_method(target, method)
33
+ target.send(method)
34
+ end
33
35
 
34
- def will_run?(target, changes = {})
35
- if options[:if]
36
- [*options[:if]].all? { |m| run_method_or_lambda(target, m) }
37
- elsif options[:unless]
38
- [*options[:unless]].none? { |m| run_method_or_lambda(target, m) }
39
- else
40
- true
36
+ def will_run?(target, _changes = {})
37
+ if options[:if]
38
+ [*options[:if]].all? { |m| run_method_or_lambda(target, m) }
39
+ elsif options[:unless]
40
+ [*options[:unless]].none? { |m| run_method_or_lambda(target, m) }
41
+ else
42
+ true
43
+ end
41
44
  end
42
45
  end
43
46
  end
@@ -1,11 +1,13 @@
1
- class FlowMachine::ChangeCallback < FlowMachine::StateCallback
2
- attr_accessor :field
3
- def initialize(field, *args, &block)
4
- @field = field
5
- super(*args, &block)
6
- end
1
+ module FlowMachine
2
+ class ChangeCallback < FlowMachine::StateCallback
3
+ attr_accessor :field
4
+ def initialize(field, *args, &block)
5
+ @field = field
6
+ super(*args, &block)
7
+ end
7
8
 
8
- def will_run?(object, changes = {})
9
- changes.keys.include?(field.to_s) && super
9
+ def will_run?(object, changes = {})
10
+ changes.key?(field.to_s) && super
11
+ end
10
12
  end
11
13
  end
@@ -1,5 +1,7 @@
1
- class FlowMachine::StateCallback < FlowMachine::Callback
2
- def run_method(target, method)
3
- target.run_workflow_method(method)
1
+ module FlowMachine
2
+ class StateCallback < FlowMachine::Callback
3
+ def run_method(target, method)
4
+ target.run_workflow_method(method)
5
+ end
4
6
  end
5
7
  end
@@ -1,3 +1,3 @@
1
1
  module FlowMachine
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3".freeze
3
3
  end
@@ -14,11 +14,10 @@ module FlowMachine
14
14
  end
15
15
 
16
16
  module ClassMethods
17
-
18
17
  attr_accessor :callbacks
19
18
 
20
19
  def state_names
21
- states.keys.map &:to_s
20
+ states.keys.map(&:to_s)
22
21
  end
23
22
 
24
23
  def states
@@ -112,7 +111,7 @@ module FlowMachine
112
111
  def current_state_name
113
112
  object.send(self.class.state_method)
114
113
  end
115
- alias_method :state, :current_state_name
114
+ alias state current_state_name
116
115
 
117
116
  def previous_state_name
118
117
  @previous_state.try(:name)
@@ -139,7 +138,7 @@ module FlowMachine
139
138
  self.changes = object.changes
140
139
  # If the model has a default state from the database, then it doesn't get
141
140
  # included in `changes` when you're first saving it.
142
- self.changes[state_method.to_s] ||= [nil, current_state_name] if object.new_record?
141
+ changes[state_method.to_s] ||= [nil, current_state_name] if object.new_record?
143
142
 
144
143
  fire_callbacks(:before_save)
145
144
  current_state.fire_callbacks(:before_change, changes)
@@ -157,7 +156,7 @@ module FlowMachine
157
156
  # Useful for using in if/unless on state and after_save callbacks so you can
158
157
  # run the callback only on the initial persistence
159
158
  def create?
160
- self.changes[state_method.to_s].try(:first).blank?
159
+ changes[state_method.to_s].try(:first).blank?
161
160
  end
162
161
 
163
162
  def persist_object
@@ -165,7 +164,8 @@ module FlowMachine
165
164
  end
166
165
 
167
166
  def current_state_name=(new_state)
168
- raise ArgumentError.new("invalid state: #{new_state}") unless self.class.state_names.include?(new_state.to_s)
167
+ raise ArgumentError, "invalid state: #{new_state}" unless self.class.state_names.include?(new_state.to_s)
168
+
169
169
  object.send("#{self.class.state_method}=", new_state)
170
170
  end
171
171
 
@@ -8,6 +8,7 @@ module FlowMachine
8
8
 
9
9
  klazz = class_for(object)
10
10
  return nil unless klazz
11
+
11
12
  klazz.new(object, options)
12
13
  end
13
14
 
@@ -19,7 +20,7 @@ module FlowMachine
19
20
  if object_or_class.is_a? Class
20
21
  "#{object_or_class.name}Workflow".constantize
21
22
  else
22
- self.class_for(object_or_class.class)
23
+ class_for(object_or_class.class)
23
24
  end
24
25
  rescue NameError # if the workflow class doesn't exist
25
26
  nil
@@ -1,162 +1,172 @@
1
1
  require "active_support/core_ext/module/delegation"
2
2
  require "active_support/inflector"
3
3
 
4
- class FlowMachine::WorkflowState
5
- attr_reader :workflow
6
- attr_accessor :guard_errors
4
+ module FlowMachine
5
+ class WorkflowState
6
+ attr_reader :workflow
7
+ attr_accessor :guard_errors
7
8
 
8
- delegate :object, :options, to: :workflow
9
+ delegate :object, :options, to: :workflow
9
10
 
10
- module ClassMethods
11
- attr_accessor :state_callbacks
12
- attr_accessor :expose_to_workflow_methods
11
+ module ClassMethods
12
+ attr_accessor :state_callbacks
13
+ attr_accessor :expose_to_workflow_methods
13
14
 
14
- def state_name
15
- name.demodulize.sub(/State\z/, '').underscore.to_sym
16
- end
15
+ def state_name
16
+ name.demodulize.sub(/State\z/, "").underscore.to_sym
17
+ end
17
18
 
18
- # Maintains a list of methods that should be exposed to the workflow
19
- # the workflow is responsible for reading this list
20
- def expose_to_workflow(name)
21
- self.expose_to_workflow_methods ||= []
22
- self.expose_to_workflow_methods << name
23
- end
19
+ # Maintains a list of methods that should be exposed to the workflow
20
+ # the workflow is responsible for reading this list
21
+ def expose_to_workflow(name)
22
+ self.expose_to_workflow_methods ||= []
23
+ self.expose_to_workflow_methods << name
24
+ end
24
25
 
25
- def event(name, options = {}, &block)
26
- define_may_event(name, options)
27
- define_event(name, options, &block)
28
- define_event_bang(name)
29
- end
26
+ def event(name, options = {}, &block)
27
+ define_may_event(name, options)
28
+ define_event(name, options, &block)
29
+ define_event_bang(name)
30
+ end
30
31
 
31
- private
32
+ private
33
+
34
+ def define_event(name, _options, &block)
35
+ define_method name do |*args|
36
+ return false unless send("may_#{name}?")
32
37
 
33
- def define_event(name, options, &block)
34
- define_method name do |*args|
35
- return false unless self.send("may_#{name}?")
36
- instance_exec *args, &block
38
+ instance_exec(*args, &block)
39
+ end
40
+ expose_to_workflow name
37
41
  end
38
- expose_to_workflow name
39
- end
40
42
 
41
- def define_may_event(name, options)
42
- define_method "may_#{name}?" do
43
- run_guard_methods([*options[:guard]])
43
+ def define_may_event(name, options)
44
+ define_method "may_#{name}?" do
45
+ run_guard_methods([*options[:guard]])
46
+ end
47
+ expose_to_workflow "may_#{name}?"
44
48
  end
45
- expose_to_workflow "may_#{name}?"
46
- end
47
49
 
48
- def define_event_bang(name)
49
- define_method "#{name}!" do |*args|
50
- workflow.persist if self.send(name, *args)
50
+ def define_event_bang(name)
51
+ define_method "#{name}!" do |*args|
52
+ workflow.persist if send(name, *args)
53
+ end
54
+ expose_to_workflow "#{name}!"
51
55
  end
52
- expose_to_workflow "#{name}!"
53
- end
54
- end
55
- extend ClassMethods
56
-
57
- # Callbacks may be a symbol method name on the state, workflow, or underlying object,
58
- # and will look for that method on those objects in that order. You may also
59
- # use a block.
60
- # Callbacks will accept :if and :unless options, which also may be method name
61
- # symbols or blocks. The option accepts an array meaning all methods must return
62
- # true (for if) and false (for unless)
63
- #
64
- # class ExampleState < Workflow::State
65
- # on_enter :some_method, if: :allowed?
66
- # after_enter :after_enter_method, if: [:this_is_true?, :and_this_is_true?]
67
- # before_change(:field_name) { do_something }
68
- # end
69
- #
70
- module CallbackDsl
71
- # Called when the workflow `transition`s to the state
72
- def on_enter(*args, &block)
73
- add_callback(:on_enter, FlowMachine::StateCallback.new(*args, &block))
74
56
  end
57
+ extend ClassMethods
58
+
59
+ # Callbacks may be a symbol method name on the state, workflow, or underlying object,
60
+ # and will look for that method on those objects in that order. You may also
61
+ # use a block.
62
+ # Callbacks will accept :if and :unless options, which also may be method name
63
+ # symbols or blocks. The option accepts an array meaning all methods must return
64
+ # true (for if) and false (for unless)
65
+ #
66
+ # class ExampleState < Workflow::State
67
+ # on_enter :some_method, if: :allowed?
68
+ # after_enter :after_enter_method, if: [:this_is_true?, :and_this_is_true?]
69
+ # before_change(:field_name) { do_something }
70
+ # end
71
+ #
72
+ module CallbackDsl
73
+ # Called when the workflow `transition`s to the state
74
+ def on_enter(*args, &block)
75
+ add_callback(:on_enter, FlowMachine::StateCallback.new(*args, &block))
76
+ end
75
77
 
76
- # Called after `persist` when the workflow transitioned into this state
77
- def after_enter(*args, &block)
78
- add_callback(:after_enter, FlowMachine::StateCallback.new(*args, &block))
79
- end
78
+ # Called after `persist` when the workflow transitioned into this state
79
+ def after_enter(*args, &block)
80
+ add_callback(:after_enter, FlowMachine::StateCallback.new(*args, &block))
81
+ end
80
82
 
81
- # Happens before persistence if the field on the object has changed
82
- def before_change(field, *args, &block)
83
- add_callback(:before_change, FlowMachine::ChangeCallback.new(field, *args, &block))
84
- end
83
+ # Called when the worklow `transition`s out of the state
84
+ def on_exit(*args, &block)
85
+ add_callback(:on_exit, FlowMachine::StateCallback.new(*args, &block))
86
+ end
87
+
88
+ # Happens before persistence if the field on the object has changed
89
+ def before_change(field, *args, &block)
90
+ add_callback(:before_change, FlowMachine::ChangeCallback.new(field, *args, &block))
91
+ end
92
+
93
+ # Happens after persistence if the field on the object has changed
94
+ def after_change(field, *args, &block)
95
+ add_callback(:after_change, FlowMachine::ChangeCallback.new(field, *args, &block))
96
+ end
97
+
98
+ private
85
99
 
86
- # Happens after persistence if the field on the object has changed
87
- def after_change(field, *args, &block)
88
- add_callback(:after_change, FlowMachine::ChangeCallback.new(field, *args, &block))
100
+ def add_callback(hook, callback)
101
+ self.state_callbacks ||= {}
102
+ state_callbacks[hook] ||= []
103
+ state_callbacks[hook] << callback
104
+ end
89
105
  end
106
+ extend CallbackDsl
90
107
 
91
- private
108
+ def initialize(workflow)
109
+ @workflow = workflow
110
+ @guard_errors = []
111
+ end
92
112
 
93
- def add_callback(hook, callback)
94
- self.state_callbacks ||= {}
95
- state_callbacks[hook] ||= []
96
- state_callbacks[hook] << callback
113
+ def fire_callback_list(callbacks, changes = {})
114
+ callbacks.each do |callback|
115
+ callback.call(self, changes)
116
+ end
97
117
  end
98
- end
99
- extend CallbackDsl
100
118
 
101
- def initialize(workflow)
102
- @workflow = workflow
103
- @guard_errors = []
104
- end
119
+ def fire_callbacks(event, changes = {})
120
+ return unless self.class.state_callbacks.try(:[], event)
105
121
 
106
- def fire_callback_list(callbacks, changes = {})
107
- callbacks.each do |callback|
108
- callback.call(self, changes)
122
+ fire_callback_list self.class.state_callbacks[event], changes
109
123
  end
110
- end
111
124
 
112
- def fire_callbacks(event, changes = {})
113
- return unless self.class.state_callbacks.try(:[], event)
114
- fire_callback_list self.class.state_callbacks[event], changes
115
- end
125
+ # Allows method calls to fallback up the object chain so
126
+ # guards and other methods can be defined on the object or workflow
127
+ # as well as the state
128
+ def run_workflow_method(method_name, *args, &block)
129
+ target = object_chain(method_name)
130
+ raise NoMethodError.new("undefined method #{method_name}", method_name) unless target
116
131
 
117
- # Allows method calls to fallback up the object chain so
118
- # guards and other methods can be defined on the object or workflow
119
- # as well as the state
120
- def run_workflow_method(method_name, *args, &block)
121
- if target = object_chain(method_name)
122
132
  target.send(method_name, *args, &block)
123
- else
124
- raise NoMethodError.new("undefined method #{method_name}", method_name)
125
133
  end
126
- end
127
134
 
128
- def transition(options = {})
129
- workflow.transition(options).tap do |new_state|
130
- new_state.fire_callbacks(:on_enter) if new_state
135
+ def transition(options = {})
136
+ workflow.transition(options).tap do |new_state|
137
+ if new_state != workflow.previous_state
138
+ workflow.previous_state.fire_callbacks(:on_exit)
139
+ new_state.fire_callbacks(:on_enter)
140
+ end
141
+ end
131
142
  end
132
- end
133
143
 
134
- def name
135
- self.class.state_name
136
- end
144
+ def name
145
+ self.class.state_name
146
+ end
137
147
 
138
- def ==(other)
139
- self.class == other.class
140
- end
148
+ def ==(other)
149
+ self.class == other.class
150
+ end
141
151
 
142
- private
143
-
144
- def run_guard_methods(guard_methods)
145
- self.guard_errors = []
146
- # Use inject to ensure that all guard methods are run.
147
- # all? short circuits on first false value
148
- guard_methods.inject(true) do |valid, guard_method|
149
- if self.run_workflow_method(guard_method)
150
- valid
151
- else
152
- self.guard_errors << guard_method
153
- false
152
+ private
153
+
154
+ def run_guard_methods(guard_methods)
155
+ self.guard_errors = []
156
+ # Use inject to ensure that all guard methods are run.
157
+ # all? short circuits on first false value
158
+ guard_methods.inject(true) do |valid, guard_method|
159
+ if run_workflow_method(guard_method)
160
+ valid
161
+ else
162
+ guard_errors << guard_method
163
+ false
164
+ end
154
165
  end
155
166
  end
156
- #
157
- end
158
167
 
159
- def object_chain(method_name)
160
- [self, workflow, object].find { |o| o.respond_to?(method_name, true) }
168
+ def object_chain(method_name)
169
+ [self, workflow, object].find { |o| o.respond_to?(method_name, true) }
170
+ end
161
171
  end
162
172
  end