argon 1.0.3 → 1.1.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +13 -7
- data/lib/argon/invalid_parameter_error.rb +2 -0
- data/lib/argon/version.rb +1 -1
- data/lib/argon.rb +86 -20
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4bc7f87bd0b3e31f15e1c194ea49faac796ba68
|
4
|
+
data.tar.gz: 2dced65f814d1d838b57800dd7336bf30f1fbd1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e87960bccf2ec34fe5cf927566c8f714325ea79fe9e42eed57b374c5570148289f29e1ad44ccc0af0453b03620b92514c3008df551994c99bcedc5c9c476cdf0
|
7
|
+
data.tar.gz: '0619768228c23bba7e01dd756e99d66d82c14be9d0d30c2d14987341fea1d9bd5450854b9cb942e509910f912aeedc9c04be8d0cb13836cab098b6bc6604e03a'
|
data/.gitignore
CHANGED
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
|
}
|
data/lib/argon/version.rb
CHANGED
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")
|
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
|
-
|
36
|
-
|
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).
|
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
|
44
|
-
to
|
45
|
-
action
|
46
|
-
do_action
|
47
|
-
check_action
|
48
|
-
|
49
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
166
|
-
self.send("
|
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
|
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-
|
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
|