argon 1.0.1 → 1.0.2
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 +59 -9
- data/lib/argon/version.rb +1 -1
- data/lib/argon.rb +29 -25
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d09b17a786ef14a57f421fc3a44d18a3422e57ff
|
4
|
+
data.tar.gz: 3389b1b936db32747285bf13d990cbc829e4af7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 439e99b9f44cfdb6964925572d649bd2e8458daddc9db818f8990ea94ec1316e40a6917b2f75995c0670e0551cae58eb3cee30a299e90e08d60d390dd3acdad8
|
7
|
+
data.tar.gz: 446e84e3808c6f89e778f02178f249f840386c45c7d57e899cdf1334669a03062b2d570ddecc50d7c386a71a3a2cd3a76cd9a7729d2da1e806cb52b260251fa8
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,13 @@
|
|
1
|
-
#
|
1
|
+
# Argon
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Argon is a workflow engine for Rails, built around a state machine. It relies on a `state` propery on a model to manage workflow around state transitions.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
7
|
Add this line to your application's Gemfile:
|
10
8
|
|
11
9
|
```ruby
|
12
|
-
gem '
|
10
|
+
gem 'argon'
|
13
11
|
```
|
14
12
|
|
15
13
|
And then execute:
|
@@ -18,11 +16,63 @@ And then execute:
|
|
18
16
|
|
19
17
|
Or install it yourself as:
|
20
18
|
|
21
|
-
$ gem install
|
19
|
+
$ gem install argon
|
22
20
|
|
23
21
|
## Usage
|
24
22
|
|
25
|
-
|
23
|
+
The `Maxim` module provides a `state_machine` class method which expects the following args (as demonstrated by the example below).
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Report
|
27
|
+
include Argon
|
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
|
31
|
+
end
|
32
|
+
|
33
|
+
def after_cancel
|
34
|
+
# This is called after a successful transition
|
35
|
+
end
|
36
|
+
|
37
|
+
state_machine state: {
|
38
|
+
states: {
|
39
|
+
draft: 0,
|
40
|
+
submitted: 1,
|
41
|
+
approved: 2,
|
42
|
+
rejected: 3,
|
43
|
+
cancelled: 4,
|
44
|
+
},
|
45
|
+
events: [
|
46
|
+
:cancel,
|
47
|
+
],
|
48
|
+
edges: [
|
49
|
+
{ from: :draft, to: :submitted, action: :submit, callbacks: {in: false, post: false} },
|
50
|
+
{ from: :draft, to: :cancelled, action: :cancel_draft, callbacks: {in: false, post: false}, on_events: [:cancel] },
|
51
|
+
{ from: :submitted, to: :cancelled, action: :cancel_submitted, callbacks: {in: false, post: false}, on_events: [:cancel] },
|
52
|
+
],
|
53
|
+
on_successful_transition: ->(from:, to:) { /* Do something here */ },
|
54
|
+
on_failed_transition: ->(from:, to:) { /* Do something else */ },
|
55
|
+
}
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
`Report#state` will now return one of `:draft`, `:submitted`, `:approved`, `:rejected` or `:cancelled`. There is no method to set the state directly, and it is recommended to set the numberical value of the initial state (e.g. `0` here for `:draft`) as the default value of the `state` column for the `reports` table, which should be an integer column.
|
60
|
+
|
61
|
+
This will generate the following methods for the states:
|
62
|
+
|
63
|
+
* `Report.draft`, `Report.submitted`, `Report.approved`, `Report.rejected`, and `Report.cancelled` : These are scopes similar to the ones generated by Rails enum
|
64
|
+
* `Report#draft?`, `Report#submitted?`, `Report#approved?`, `Report#rejected?`, and `Report#cancelled?` : These are query methods similar to the ones generated by Rails enum
|
65
|
+
|
66
|
+
The following methods are generated from the edges:
|
67
|
+
|
68
|
+
* `Report#submit!` : This will move the state to `submitted` if the object was in the `draft` state. The state change is done inside a lock (`ActiveRecord::Locking::Pessimistic#with_lock`). If `callbacks.in` was true, then `Report#on_submit` is called from within the lock. If `callbacks.post` was true, then `Report#after_submit` will be called, after the lock is released. Note that if enabled, the callbacks have to be defined before `state_machine` is called, or an exception will be raised.
|
69
|
+
* `Report#can_submit?` : This will return `true` if the object was in the `draft` state.
|
70
|
+
|
71
|
+
(similar methods are created for `cancel_draft` and `cancel_submitted`)
|
72
|
+
|
73
|
+
The following method will be generated for the event:
|
74
|
+
|
75
|
+
* `Report#cancel!` : This will check `can_cancel_draft?`, and if true, will call `cancel_draft!`. Else, it will try `can_cancel_submitted?`, and call `cancel_submitted!` if true, and so on. Note that for event methods, both callbacks are mandatory (i.e. here, both `on_cancel` and `after_cancel` must be defined before `state_machine` is called).
|
26
76
|
|
27
77
|
## Development
|
28
78
|
|
@@ -32,7 +82,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
82
|
|
33
83
|
## Contributing
|
34
84
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
85
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sparkymat/argon. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
86
|
|
37
87
|
## License
|
38
88
|
|
@@ -40,4 +90,4 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
40
90
|
|
41
91
|
## Code of Conduct
|
42
92
|
|
43
|
-
Everyone interacting in the
|
93
|
+
Everyone interacting in the Argon project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/sparkymat/argon/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/argon/version.rb
CHANGED
data/lib/argon.rb
CHANGED
@@ -32,11 +32,11 @@ module Argon
|
|
32
32
|
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
33
|
events_list.each do |event_name|
|
34
34
|
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}(
|
36
|
-
raise Argon::Error.new("`after_#{event_name}(
|
35
|
+
raise Argon::Error.new("`on_#{event_name}()` not found") if !self.instance_methods.include?("on_#{event_name}".to_sym) || self.instance_method("on_#{event_name}".to_sym).parameters.to_set != [].to_set
|
36
|
+
raise Argon::Error.new("`after_#{event_name}()` not found") if !self.instance_methods.include?("after_#{event_name}".to_sym) || self.instance_method("after_#{event_name}".to_sym).parameters.to_set != [].to_set
|
37
37
|
end
|
38
38
|
|
39
|
-
raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{
|
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]
|
40
40
|
|
41
41
|
registered_edge_pairs = [].to_set
|
42
42
|
edges_list.each_with_index do |edge_details, index|
|
@@ -46,13 +46,19 @@ module Argon
|
|
46
46
|
do_action = "#{action}!".to_sym
|
47
47
|
check_action = "can_#{action}?".to_sym
|
48
48
|
|
49
|
-
raise Argon::Error.new("`edges` should be an Array of Hashes, with keys: from, to, action, callbacks{
|
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)
|
50
50
|
raise Argon::Error.new("`edges[#{index}].from` is not a valid state") unless states_map.keys.include?(from)
|
51
51
|
raise Argon::Error.new("`edges[#{index}].to` is not a valid state") unless states_map.keys.include?(to)
|
52
52
|
raise Argon::Error.new("`edges[#{index}].action` is not a Symbol") unless action.is_a?(Symbol)
|
53
53
|
raise Argon::Error.new("`#{edge_details[:action]}` is an invalid action name. `#{self.name}##{do_action}` method already exists") if self.instance_methods.include?(do_action)
|
54
54
|
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
|
-
raise Argon::Error.new("`edges[#{index}].callbacks` must be {
|
55
|
+
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
|
+
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
|
58
|
+
end
|
59
|
+
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
|
61
|
+
end
|
56
62
|
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)
|
57
63
|
unless edge_details[:on_events].nil?
|
58
64
|
edge_details[:on_events].each_with_index do |event_name, event_index|
|
@@ -63,11 +69,11 @@ module Argon
|
|
63
69
|
registered_edge_pairs << [from, to]
|
64
70
|
end
|
65
71
|
|
66
|
-
raise Argon::Error.new("`on_successful_transition` must be a lambda of signature `(from:, to
|
67
|
-
raise Argon::Error.new("`on_successful_transition` must be a lambda of signature `(from:, to
|
72
|
+
raise Argon::Error.new("`on_successful_transition` must be a lambda of signature `(from:, to:)`") if !on_successful_transition.nil? && !on_successful_transition.is_a?(Proc)
|
73
|
+
raise Argon::Error.new("`on_successful_transition` must be a lambda of signature `(from:, to:)`") if on_successful_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to]].to_set
|
68
74
|
|
69
|
-
raise Argon::Error.new("`on_failed_transition` must be a lambda of signature `(from:, to
|
70
|
-
raise Argon::Error.new("`on_failed_transition` must be a lambda of signature `(from:, to
|
75
|
+
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
|
+
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
|
71
77
|
|
72
78
|
state_machines = {}
|
73
79
|
begin
|
@@ -109,16 +115,16 @@ module Argon
|
|
109
115
|
from = edge_details[:from]
|
110
116
|
to = edge_details[:to]
|
111
117
|
action = edge_details[:action]
|
112
|
-
|
113
|
-
|
118
|
+
on_lock_callback = "on_#{action}".to_sym if edge_details[:callbacks][:on] == true
|
119
|
+
after_lock_callback = "after_#{action}".to_sym if edge_details[:callbacks][:after] == true
|
114
120
|
|
115
121
|
define_method("can_#{action}?".to_sym) do
|
116
122
|
self.send(field) == from
|
117
123
|
end
|
118
124
|
|
119
|
-
define_method("#{action}!".to_sym) do
|
125
|
+
define_method("#{action}!".to_sym) do |&block|
|
120
126
|
if self.send(field) != from
|
121
|
-
on_failed_transition.call(from: self.send(field), to: to
|
127
|
+
on_failed_transition.call(from: self.send(field), to: to)
|
122
128
|
raise Argon::InvalidTransitionError.new("Invalid state transition")
|
123
129
|
end
|
124
130
|
|
@@ -127,8 +133,8 @@ module Argon
|
|
127
133
|
self.update_column(field, self.class.send("#{ field.to_s.pluralize }").map{|v| [v[0],v[1]]}.to_h[to])
|
128
134
|
self.touch
|
129
135
|
|
130
|
-
unless
|
131
|
-
self.send(
|
136
|
+
unless on_lock_callback.nil?
|
137
|
+
self.send(on_lock_callback)
|
132
138
|
end
|
133
139
|
|
134
140
|
unless block.nil?
|
@@ -136,32 +142,30 @@ module Argon
|
|
136
142
|
end
|
137
143
|
end
|
138
144
|
rescue => e
|
139
|
-
on_failed_transition.call(from: self.send(field), to: to
|
145
|
+
on_failed_transition.call(from: self.send(field), to: to)
|
140
146
|
raise e
|
141
147
|
end
|
142
148
|
|
143
|
-
on_successful_transition.call(from: from, to: to
|
149
|
+
on_successful_transition.call(from: from, to: to)
|
144
150
|
|
145
|
-
unless
|
146
|
-
self.send(
|
151
|
+
unless after_lock_callback.nil?
|
152
|
+
self.send(after_lock_callback)
|
147
153
|
end
|
148
154
|
end
|
149
155
|
end
|
150
156
|
|
151
157
|
events_list.each do |event_name|
|
152
|
-
define_method("#{event_name}!".to_sym) do
|
158
|
+
define_method("#{event_name}!".to_sym) do
|
153
159
|
matching_edges = edges_list.select{ |edge| !edge[:on_events].nil? && edge[:on_events].to_set.include?(event_name) }
|
154
160
|
|
155
161
|
matching_edges.each do |edge|
|
156
162
|
action = edge[:action]
|
157
|
-
from = edge[:from]
|
158
|
-
to = edge[:to]
|
159
163
|
|
160
164
|
if self.send("can_#{ action }?")
|
161
|
-
self.send("#{ action }!"
|
162
|
-
self.send("on_#{ event_name }"
|
165
|
+
self.send("#{ action }!") do
|
166
|
+
self.send("on_#{ event_name }")
|
163
167
|
end
|
164
|
-
self.send("after_#{ event_name }"
|
168
|
+
self.send("after_#{ event_name }")
|
165
169
|
return
|
166
170
|
end
|
167
171
|
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.0.2
|
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-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|