flow_machine 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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