argon 1.0.3 → 1.1.0

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
  SHA1:
3
- metadata.gz: 0a64d4d65fc896c5d490a0ccc2c8fa44961c4020
4
- data.tar.gz: '08e4cb3f3bd20952613b23c06e15b9838dc12883'
3
+ metadata.gz: f4bc7f87bd0b3e31f15e1c194ea49faac796ba68
4
+ data.tar.gz: 2dced65f814d1d838b57800dd7336bf30f1fbd1d
5
5
  SHA512:
6
- metadata.gz: a9ad8ad56aed3f86e3ac838a766588ccf58ed57cddc408fbc640ef4196145b200a7fb1cf762a7a18add9d3c50836d5961ee17531869c4e11f8c0eaa8add50d19
7
- data.tar.gz: 90f0f026e7e8f04e8c53bf8fa3fbb4eb285ea767f97c61500982519c93e00cc5a6836c284bd30a845262b5e563ee98c4ea788ebfb772ea37942dc292f656890d
6
+ metadata.gz: e87960bccf2ec34fe5cf927566c8f714325ea79fe9e42eed57b374c5570148289f29e1ad44ccc0af0453b03620b92514c3008df551994c99bcedc5c9c476cdf0
7
+ data.tar.gz: '0619768228c23bba7e01dd756e99d66d82c14be9d0d30c2d14987341fea1d9bd5450854b9cb942e509910f912aeedc9c04be8d0cb13836cab098b6bc6604e03a'
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
13
  *.gem
14
+ .byebug_history
data/README.md CHANGED
@@ -26,12 +26,12 @@ The `Maxim` module provides a `state_machine` class method which expects the fol
26
26
  class Report
27
27
  include Argon
28
28
 
29
- def on_cancel
30
- # This is called from inside the lock, after the edge transitions. If an exception is thrown here, the entire transition is rolled back
29
+ def on_cancel(action:, message:)
30
+ # This is called from inside the lock, after the edge transitions, with the name of the action. If an exception is thrown here, the entire transition is rolled back
31
31
  end
32
32
 
33
- def after_cancel
34
- # This is called after a successful transition
33
+ def after_cancel(action:, message:)
34
+ # This is called after a successful transition, with the action which actually succeeded
35
35
  end
36
36
 
37
37
  state_machine state: {
@@ -46,10 +46,16 @@ The `Maxim` module provides a `state_machine` class method which expects the fol
46
46
  :cancel,
47
47
  ],
48
48
  edges: [
49
- { from: :draft, to: :submitted, action: :submit, callbacks: {on: false, after: false} },
50
- { from: :draft, to: :cancelled, action: :cancel_draft, callbacks: {on: false, after: false}, on_events: [:cancel] },
51
- { from: :submitted, to: :cancelled, action: :cancel_submitted, callbacks: {on: false, after: false}, on_events: [:cancel] },
49
+ { from: :draft, to: :submitted, action: :submit, callbacks: {on: false, after: false} },
50
+ { from: :draft, to: :cancelled, action: :cancel_draft, callbacks: {on: false, after: false}, on_events: [:cancel], parameters: [:message_param] },
51
+ { from: :submitted, to: :cancelled, action: :cancel_submitted, callbacks: {on: false, after: false}, on_events: [:cancel], parameters: [:message_param] },
52
52
  ],
53
+ parameters: {
54
+ message_param: {
55
+ name: :message,
56
+ check: ->(message) { !message.nil? },
57
+ }
58
+ },
53
59
  on_successful_transition: ->(from:, to:) { /* Do something here */ },
54
60
  on_failed_transition: ->(from:, to:) { /* Do something else */ },
55
61
  }
@@ -0,0 +1,2 @@
1
+ class Argon::InvalidParameterError < StandardError
2
+ end
data/lib/argon/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Argon
2
- VERSION = "1.0.3"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/argon.rb CHANGED
@@ -1,22 +1,26 @@
1
1
  require 'argon/version'
2
2
  require 'argon/error'
3
3
  require 'argon/invalid_transition_error'
4
+ require 'argon/invalid_parameter_error'
4
5
  require 'active_support/concern'
5
6
  require 'active_support/inflector'
6
7
  require 'pry-byebug'
7
8
 
8
9
  module Argon
9
10
  extend ActiveSupport::Concern
11
+ extend ActiveSupport::Inflector
12
+ include ActiveSupport::Inflector
10
13
 
11
14
  module ClassMethods
12
15
  def state_machine(mapping)
13
16
  raise Argon::Error.new("state_machine() has to be called on a Hash") unless mapping.is_a?(Hash)
14
17
  raise Argon::Error.new("state_machine() has to specify a field and the mappings") unless mapping.keys.count == 1 && mapping.keys.first.is_a?(Symbol) && mapping.values.first.is_a?(Hash)
15
- raise Argon::Error.new("state_machine() should have (only) the following mappings: states, events, edges, on_successful_transition, on_failed_transition") if mapping.values.first.keys.sort != %i(states events edges on_successful_transition on_failed_transition).sort
18
+ raise Argon::Error.new("state_machine() should have (only) the following mappings: states, events, edges, parameters (optional), on_successful_transition, on_failed_transition") unless mapping.values.first.keys.to_set.subset?(%i(states events edges on_successful_transition on_failed_transition parameters).to_set) && %i(states events edges on_successful_transition on_failed_transition).to_set.subset?(mapping.values.first.keys.to_set)
16
19
 
17
20
  field = mapping.keys.first
18
21
  states_map = mapping.values.first[:states]
19
22
  events_list = mapping.values.first[:events]
23
+ parameters = mapping.values.first[:parameters]
20
24
  edges_list = mapping.values.first[:edges]
21
25
  on_successful_transition = mapping.values.first[:on_successful_transition]
22
26
  on_failed_transition = mapping.values.first[:on_failed_transition]
@@ -32,21 +36,44 @@ module Argon
32
36
  raise Argon::Error.new("`events` should be an Array of Symbols") if !events_list.is_a?(Array) || (events_list.length > 0 && events_list.map(&:class).uniq != [Symbol])
33
37
  events_list.each do |event_name|
34
38
  raise Argon::Error.new("`#{event_name}` is not a valid event name. `#{self.name}##{event_name}` method already exists") if self.instance_methods.include?(event_name)
35
- raise Argon::Error.new("`on_#{event_name}(action:)` not found") if !self.instance_methods.include?("on_#{event_name}".to_sym) || self.instance_method("on_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :action]].to_set
36
- raise Argon::Error.new("`after_#{event_name}(action:)` not found") if !self.instance_methods.include?("after_#{event_name}".to_sym) || self.instance_method("after_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :action]].to_set
39
+
40
+ event_edges = edges_list.select{|e| !e[:on_events].nil? && e[:on_events].include?(event_name)}
41
+
42
+ if event_edges.empty?
43
+ raise Argon::Error.new("`on_#{event_name}(action:)` not found") if !self.instance_methods.include?("on_#{event_name}".to_sym) || self.instance_method("on_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :action]].to_set
44
+ raise Argon::Error.new("`after_#{event_name}(action:)` not found") if !self.instance_methods.include?("after_#{event_name}".to_sym) || self.instance_method("after_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :action]].to_set
45
+ else
46
+ raise Argon::Error.new("Event `#{event_name}` is being used by edges (#{ event_edges.map{|e| "`#{e[:action]}`"}.join(", ") }) with mixed lists of parameters") if event_edges.map{|e| (e[:parameters] || []).to_set}.uniq.length > 1
47
+ expected_parameters = %i(action)
48
+ if !event_edges[0][:parameters].nil?
49
+ expected_parameters += parameters.values_at(*event_edges[0][:parameters]).map{|p| p[:name]}
50
+ end
51
+ raise Argon::Error.new("`on_#{event_name}(#{ expected_parameters.map{|p| "#{p}:"}.join(", ") })` not found") if !self.instance_methods.include?("on_#{event_name}".to_sym) || self.instance_method("on_#{event_name}".to_sym).parameters.to_set != expected_parameters.map{|name| [:keyreq, name]}.to_set
52
+ raise Argon::Error.new("`after_#{event_name}(#{ expected_parameters.map{|p| "#{p}:"}.join(", ") })` not found") if !self.instance_methods.include?("after_#{event_name}".to_sym) || self.instance_method("after_#{event_name}".to_sym).parameters.to_set != expected_parameters.map{|name| [:keyreq, name]}.to_set
53
+ end
54
+ end
55
+ raise Argon::Error.new("`parameters` should be a Hash with keys as the parameter identifier, with value as a Hash as {name: Symbol, check: lambda(object)}") if !parameters.nil? && !parameters.is_a?(Hash) && parameters.keys.map(&:class).to_set != [Symbol].to_set
56
+ if !parameters.nil?
57
+ parameters.each_pair do |param_name, param_details|
58
+ raise Argon::Error.new("`parameters.#{param_name}` should be a Hash with keys as the parameter identifier, with value as a Hash as {name: Symbol, check: lambda(object)}") if param_details.keys.to_set != %i(name check).to_set
59
+ raise Argon::Error.new("`parameters.#{param_name}.name` should be a Symbol") unless param_details[:name].is_a?(Symbol)
60
+ raise Argon::Error.new("`parameters.#{param_name}.check` should be a lambda that takes one arg") if !param_details[:check].is_a?(Proc) || !(param_details[:check].parameters.length == 1 && param_details[:check].parameters[0].length == 2 && param_details[:check].parameters[0][0] == :req && param_details[:check].parameters[0][1].is_a?(Symbol))
61
+ end
37
62
  end
38
63
 
39
- raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{on: true/false, after: true/false}, on_events (optional)") if !edges_list.is_a?(Array) || edges_list.map(&:class).uniq != [Hash]
64
+ raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{on: true/false, after: true/false}, on_events (optional), parameters (optional)") if !edges_list.is_a?(Array) || edges_list.map(&:class).to_set != [Hash].to_set
40
65
 
41
66
  registered_edge_pairs = [].to_set
42
67
  edges_list.each_with_index do |edge_details, index|
43
- from = edge_details[:from]
44
- to = edge_details[:to]
45
- action = edge_details[:action]
46
- do_action = "#{action}!".to_sym
47
- check_action = "can_#{action}?".to_sym
48
-
49
- raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{on: true/false, after: true/false}, on_events (optional)") unless edge_details.keys.to_set.subset?([:from, :to, :action, :callbacks, :on_events].to_set) && [:from, :to, :action, :callbacks].to_set.subset?(edge_details.keys.to_set)
68
+ from = edge_details[:from]
69
+ to = edge_details[:to]
70
+ action = edge_details[:action]
71
+ do_action = "#{action}!".to_sym
72
+ check_action = "can_#{action}?".to_sym
73
+ action_parameters = edge_details[:parameters]
74
+ action_parameter_names = (action_parameters.nil? || parameters.nil?) ? [] : parameters.values_at(*action_parameters).compact.map{|p| p[:name]}
75
+
76
+ raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{on: true/false, after: true/false}, on_events (optional), parameters (optional)") unless edge_details.keys.to_set.subset?([:from, :to, :action, :callbacks, :on_events, :parameters].to_set) && [:from, :to, :action, :callbacks].to_set.subset?(edge_details.keys.to_set)
50
77
  raise Argon::Error.new("`edges[#{index}].from` is not a valid state") unless states_map.keys.include?(from)
51
78
  raise Argon::Error.new("`edges[#{index}].to` is not a valid state") unless states_map.keys.include?(to)
52
79
  raise Argon::Error.new("`edges[#{index}].action` is not a Symbol") unless action.is_a?(Symbol)
@@ -54,10 +81,10 @@ module Argon
54
81
  raise Argon::Error.new("`#{edge_details[:action]}` is an invalid action name. `#{self.name}##{check_action}` method already exists") if self.instance_methods.include?(check_action)
55
82
  raise Argon::Error.new("`edges[#{index}].callbacks` must be {on: true/false, after: true/false}") if !edge_details[:callbacks].is_a?(Hash) || edge_details[:callbacks].keys.to_set != [:after, :on].to_set || !edge_details[:callbacks].values.to_set.subset?([true, false].to_set)
56
83
  if edge_details[:callbacks][:on]
57
- raise Argon::Error.new("`on_#{edge_details[:action]}()` not found") if !self.instance_methods.include?("on_#{edge_details[:action]}".to_sym) || self.instance_method("on_#{edge_details[:action]}".to_sym).parameters.to_set != [].to_set
84
+ raise Argon::Error.new("`on_#{edge_details[:action]}(#{ action_parameter_names.map{|p| "#{p}:"}.join(", ") })` not found") if !self.instance_methods.include?("on_#{edge_details[:action]}".to_sym) || self.instance_method("on_#{edge_details[:action]}".to_sym).parameters.to_set != action_parameter_names.map{|name| [:keyreq, name]}.to_set
58
85
  end
59
86
  if edge_details[:callbacks][:after]
60
- raise Argon::Error.new("`after_#{edge_details[:action]}()` not found") if !self.instance_methods.include?("after_#{edge_details[:action]}".to_sym) || self.instance_method("after_#{edge_details[:action]}".to_sym).parameters.to_set != [].to_set
87
+ raise Argon::Error.new("`after_#{edge_details[:action]}(#{ action_parameter_names.map{|p| "#{p}:"}.join(",") })` not found") if !self.instance_methods.include?("after_#{edge_details[:action]}".to_sym) || self.instance_method("after_#{edge_details[:action]}".to_sym).parameters.to_set != action_parameter_names.map{|name| [:keyreq, name]}.to_set
61
88
  end
62
89
  raise Argon::Error.new("`#{edge_details[:on_events]}` (`edges[#{index}].on_events`) is not a valid list of events") if !edge_details[:on_events].nil? && !edge_details[:on_events].is_a?(Array)
63
90
  unless edge_details[:on_events].nil?
@@ -65,6 +92,14 @@ module Argon
65
92
  raise Argon::Error.new("`#{ event_name }` (`edges[#{index}].on_events[#{event_index}]`) is not a registered event") unless events_list.include?(event_name)
66
93
  end
67
94
  end
95
+
96
+ raise Argon::Error.new("`edges[#{index}].parameters` lists multiple parameters with the same name") if action_parameter_names.length != action_parameter_names.uniq.length
97
+
98
+ unless edge_details[:parameters].nil?
99
+ edge_details[:parameters].each_with_index do |param_name, param_index|
100
+ raise Argon::Error.new("`#{ param_name }` (`edges[#{index}].parameters[#{param_index}]`) is not a registered parameter") unless parameters.keys.include?(param_name)
101
+ end
102
+ end
68
103
  raise Argon::Error.new("`edges[#{index}]` is a duplicate edge") if registered_edge_pairs.include?([from,to])
69
104
  registered_edge_pairs << [from, to]
70
105
  end
@@ -75,6 +110,9 @@ module Argon
75
110
  raise Argon::Error.new("`on_failed_transition` must be a lambda of signature `(from:, to:)`") if !on_failed_transition.nil? && !on_failed_transition.is_a?(Proc)
76
111
  raise Argon::Error.new("`on_failed_transition` must be a lambda of signature `(from:, to:)`") if on_failed_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to]].to_set
77
112
 
113
+ events_list.each do |event_name|
114
+ end
115
+
78
116
  state_machines = {}
79
117
  begin
80
118
  state_machines = self.class_variable_get(:@@state_machines)
@@ -115,6 +153,7 @@ module Argon
115
153
  from = edge_details[:from]
116
154
  to = edge_details[:to]
117
155
  action = edge_details[:action]
156
+ action_parameters = edge_details[:parameters] || []
118
157
  on_lock_callback = "on_#{action}".to_sym if edge_details[:callbacks][:on] == true
119
158
  after_lock_callback = "after_#{action}".to_sym if edge_details[:callbacks][:after] == true
120
159
 
@@ -122,7 +161,20 @@ module Argon
122
161
  self.send(field) == from
123
162
  end
124
163
 
125
- define_method("#{action}!".to_sym) do |&block|
164
+ define_method("#{action}!".to_sym) do |**args, &block|
165
+ required_keywords = !parameters.nil? ? parameters.values_at(*action_parameters).map{|p| p[:name]} : []
166
+ available_keywords = args.keys
167
+
168
+ raise ArgumentError.new("wrong number of arguments (given 1, expected 0)") if required_keywords.to_set.empty? && !available_keywords.to_set.empty?
169
+ raise ArgumentError.new("missing #{ pluralize("keyword", (required_keywords - available_keywords).length) }: #{ (required_keywords - available_keywords).join(", ") }") if !required_keywords.to_set.subset?(available_keywords.to_set)
170
+ raise ArgumentError.new("unknown #{ pluralize("keyword", (available_keywords - required_keywords).length) }: #{ (available_keywords - required_keywords).join(", ") }") if !available_keywords.to_set.subset?(required_keywords.to_set)
171
+
172
+ if !parameters.nil? && !action_parameters.empty?
173
+ parameters.select{ |k,v| action_parameters.include?(k) }.each_pair do |param_name, param_details|
174
+ raise Argon::InvalidParameterError.new("incorrect value for `#{ param_name }`") if !param_details[:check].call(args[:param_name])
175
+ end
176
+ end
177
+
126
178
  if self.send(field) != from
127
179
  on_failed_transition.call(from: self.send(field), to: to)
128
180
  raise Argon::InvalidTransitionError.new("Invalid state transition")
@@ -134,7 +186,11 @@ module Argon
134
186
  self.touch
135
187
 
136
188
  unless on_lock_callback.nil?
137
- self.send(on_lock_callback)
189
+ if args.empty?
190
+ self.send(on_lock_callback)
191
+ else
192
+ self.send(on_lock_callback, args)
193
+ end
138
194
  end
139
195
 
140
196
  unless block.nil?
@@ -149,23 +205,33 @@ module Argon
149
205
  on_successful_transition.call(from: from, to: to)
150
206
 
151
207
  unless after_lock_callback.nil?
152
- self.send(after_lock_callback)
208
+ if args.empty?
209
+ self.send(after_lock_callback)
210
+ else
211
+ self.send(after_lock_callback, args)
212
+ end
153
213
  end
154
214
  end
155
215
  end
156
216
 
157
217
  events_list.each do |event_name|
158
- define_method("#{event_name}!".to_sym) do
218
+ define_method("#{event_name}!".to_sym) do |**args|
159
219
  matching_edges = edges_list.select{ |edge| !edge[:on_events].nil? && edge[:on_events].to_set.include?(event_name) }
160
220
 
161
221
  matching_edges.each do |edge|
162
222
  action = edge[:action]
163
223
 
164
224
  if self.send("can_#{ action }?")
165
- self.send("#{ action }!") do
166
- self.send("on_#{ event_name }", action: action)
225
+ if args.empty?
226
+ self.send("#{ action }!") do
227
+ self.send("on_#{ event_name }", {action: action})
228
+ end
229
+ else
230
+ self.send("#{ action }!", args) do
231
+ self.send("on_#{ event_name }", {action: action}.merge(args))
232
+ end
167
233
  end
168
- self.send("after_#{ event_name }", action: action)
234
+ self.send("after_#{ event_name }", {action: action}.merge(args))
169
235
  return
170
236
  end
171
237
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: argon
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajith Hussain
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-07 00:00:00.000000000 Z
11
+ date: 2017-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,6 +114,7 @@ files:
114
114
  - bin/setup
115
115
  - lib/argon.rb
116
116
  - lib/argon/error.rb
117
+ - lib/argon/invalid_parameter_error.rb
117
118
  - lib/argon/invalid_transition_error.rb
118
119
  - lib/argon/version.rb
119
120
  homepage: https://github.com/sparkymat/argon