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 +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
|