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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7833b8b1180240aca7e95882353b3661de98af55
4
- data.tar.gz: aa01804bbf4cdf5d19ea2b20817f1cc9f7f1feda
3
+ metadata.gz: d09b17a786ef14a57f421fc3a44d18a3422e57ff
4
+ data.tar.gz: 3389b1b936db32747285bf13d990cbc829e4af7a
5
5
  SHA512:
6
- metadata.gz: 556b6d45c27c1812f1a4bc8999c505bf16dad8a1771eac57002abae7ccadecd22a9d3d8a45b8b9bc01bbfdb1dd7167f3939d90f6194ff6a7f36bbb4ec569476d
7
- data.tar.gz: 5642eda6be0759bb1521603a62b5106fa95cc5c34bf5bfc8f2f7f0769906c3e14ce91288bd16ec145fa6410b8d2c3428ec2bc38e0264d27006e997e0ee19c917
6
+ metadata.gz: 439e99b9f44cfdb6964925572d649bd2e8458daddc9db818f8990ea94ec1316e40a6917b2f75995c0670e0551cae58eb3cee30a299e90e08d60d390dd3acdad8
7
+ data.tar.gz: 446e84e3808c6f89e778f02178f249f840386c45c7d57e899cdf1334669a03062b2d570ddecc50d7c386a71a3a2cd3a76cd9a7729d2da1e806cb52b260251fa8
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ *.gem
data/README.md CHANGED
@@ -1,15 +1,13 @@
1
- # Maxim
1
+ # Argon
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/maxim`. To experiment with that code, run `bin/console` for an interactive prompt.
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 'maxim'
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 maxim
19
+ $ gem install argon
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
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/[USERNAME]/maxim. 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.
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 Maxim project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/maxim/blob/master/CODE_OF_CONDUCT.md).
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
@@ -1,3 +1,3 @@
1
1
  module Argon
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
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}(from:, to:, context:)` not found") if !self.instance_methods.include?("on_#{event_name}".to_sym) || self.instance_method("on_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :from],[:keyreq, :to],[:keyreq, :context]].to_set
36
- raise Argon::Error.new("`after_#{event_name}(from:, to:, context:)` not found") if !self.instance_methods.include?("after_#{event_name}".to_sym) || self.instance_method("after_#{event_name}".to_sym).parameters.to_set != [[:keyreq, :from],[:keyreq, :to],[:keyreq, :context]].to_set
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{in: true/false, post: true/false}, on_events (optional)") if !edges_list.is_a?(Array) || edges_list.map(&:class).uniq != [Hash]
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{in: true/false, post: 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)
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 {in: true/false, post: true/false}") if !edge_details[:callbacks].is_a?(Hash) || edge_details[:callbacks].keys.to_set != [:post, :in].to_set || !edge_details[:callbacks].values.to_set.subset?([true, false].to_set)
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:, context:)`") if !on_successful_transition.nil? && !on_successful_transition.is_a?(Proc)
67
- raise Argon::Error.new("`on_successful_transition` must be a lambda of signature `(from:, to:, context:)`") if on_successful_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to]].to_set && on_successful_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to],[:keyreq, :context]].to_set
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:, context:)`") if !on_failed_transition.nil? && !on_failed_transition.is_a?(Proc)
70
- raise Argon::Error.new("`on_failed_transition` must be a lambda of signature `(from:, to:, context:)`") if on_failed_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to]].to_set && on_failed_transition.parameters.to_set != [[:keyreq, :from],[:keyreq, :to],[:keyreq, :context]].to_set
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
- in_lock_callback = "on_#{action}".to_sym if edge_details[:callbacks][:in] == true
113
- post_lock_callback = "after_#{action}".to_sym if edge_details[:callbacks][:post] == true
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 |context = nil, &block|
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, context: context)
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 in_lock_callback.nil?
131
- self.send(in_lock_callback, from: from, to: to, context: context)
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, context: context)
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, context: context)
149
+ on_successful_transition.call(from: from, to: to)
144
150
 
145
- unless post_lock_callback.nil?
146
- self.send(post_lock_callback, from: from, to: to, context: context)
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 |context = nil|
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 }!", context) do
162
- self.send("on_#{ event_name }", from: from, to: to, context: context)
165
+ self.send("#{ action }!") do
166
+ self.send("on_#{ event_name }")
163
167
  end
164
- self.send("after_#{ event_name }", from: from, to: to, context: context)
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.1
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-05 00:00:00.000000000 Z
11
+ date: 2017-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler