status_workflow 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +10 -0
- data/README.md +26 -0
- data/lib/status_workflow/version.rb +1 -1
- data/lib/status_workflow.rb +44 -26
- 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: 130b12c5b45db744a86537b671118ce01f0b1be9
|
4
|
+
data.tar.gz: 912a5a9530aa43e9cf1c0c96af814352f2acbf49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f943c7f5188f83493092bd7474878f8f4f47acdc262c1cd08d4fe581d4a1056bf38b4f537b5eab8713af0016e34c2602d1a389ad39c0ae95f3a6c7a99117edd2
|
7
|
+
data.tar.gz: 484a3306cb84e9f2bfe0f100c51dac8c9cbbccc798943bf5d6f36b07c0e1bf560c15ad0580b1ae19cbc9dd4fda2c6d97e22ceba6341b92a1b1c92ad0159409b1
|
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -4,11 +4,15 @@
|
|
4
4
|
|
5
5
|
Basic state machine using Redis for locking.
|
6
6
|
|
7
|
+
## Usage
|
8
|
+
|
7
9
|
```
|
8
10
|
require 'redis'
|
9
11
|
StatusWorkflow.redis = Redis.new
|
10
12
|
```
|
11
13
|
|
14
|
+
You need an object that has `status`, `status_changed_at`, and `status_error`.
|
15
|
+
|
12
16
|
Expects but does not require ActiveRecord (you just have to respond to `#reload`, `#id`, and `#update_columns`)
|
13
17
|
|
14
18
|
```
|
@@ -39,6 +43,28 @@ means:
|
|
39
43
|
* from fed, i can go to sleep or run
|
40
44
|
* from run, i can go to sleep
|
41
45
|
|
46
|
+
If you want >1 status, you do
|
47
|
+
|
48
|
+
```
|
49
|
+
include StatusWorkflow
|
50
|
+
status_workflow(
|
51
|
+
nil => {
|
52
|
+
sleep: [:feeding],
|
53
|
+
feeding: [:fed],
|
54
|
+
fed: [:sleep, :run],
|
55
|
+
run: [:sleep],
|
56
|
+
},
|
57
|
+
alt: {
|
58
|
+
sleep2: [:feeding2],
|
59
|
+
feeding2: [:fed2],
|
60
|
+
fed2: [:sleep2, :run2],
|
61
|
+
run2: [:sleep2],
|
62
|
+
}
|
63
|
+
)
|
64
|
+
```
|
65
|
+
|
66
|
+
You need an object that has `alt_status`, `alt_status_changed_at`, and `alt_status_error`.
|
67
|
+
|
42
68
|
## Sponsor
|
43
69
|
|
44
70
|
<p><a href="https://www.faraday.io"><img src="https://s3.amazonaws.com/faraday-assets/files/img/logo.svg" alt="Faraday logo"/></a></p>
|
data/lib/status_workflow.rb
CHANGED
@@ -22,11 +22,15 @@ module StatusWorkflow
|
|
22
22
|
LOCK_EXPIRY = 4
|
23
23
|
LOCK_CHECK_RATE = 0.2
|
24
24
|
|
25
|
-
def status_transition!(intermediate_to_status, final_to_status)
|
25
|
+
def status_transition!(intermediate_to_status, final_to_status, prefix = nil)
|
26
26
|
intermediate_to_status = intermediate_to_status&.to_s
|
27
27
|
final_to_status = final_to_status&.to_s
|
28
|
+
prefix_ = prefix ? "#{prefix}_" : nil
|
29
|
+
status_column = "#{prefix_}status"
|
30
|
+
status_changed_at_column = "#{status_column}_changed_at"
|
31
|
+
error_column = "#{status_column}_error"
|
28
32
|
lock_obtained_at = nil
|
29
|
-
lock_key = "status_workflow/#{self.class.name}/#{id}"
|
33
|
+
lock_key = "status_workflow/#{self.class.name}/#{id}/#{status_column}"
|
30
34
|
# Give ourselves 8 seconds to get the lock, checking every 0.2 seconds
|
31
35
|
Timeout.timeout(LOCK_ACQUISITION_TIMEOUT, nil, "#{lock_key} timeout waiting for lock") do
|
32
36
|
until StatusWorkflow.redis.set(lock_key, true, nx: true, ex: LOCK_EXPIRY)
|
@@ -38,10 +42,10 @@ module StatusWorkflow
|
|
38
42
|
initial_to_status = intermediate_to_status || final_to_status
|
39
43
|
begin
|
40
44
|
# depend on #can_enter_X to reload
|
41
|
-
send "can_enter_#{initial_to_status}?", true
|
45
|
+
send "#{prefix_}can_enter_#{initial_to_status}?", true
|
42
46
|
raise TooSlow, "#{lock_key} lost lock after checking status" if Time.now - lock_obtained_at > LOCK_EXPIRY
|
43
47
|
if intermediate_to_status
|
44
|
-
update_columns
|
48
|
+
update_columns status_column => intermediate_to_status, status_changed_at_column => Time.now
|
45
49
|
raise TooSlow, "#{lock_key} lost lock after setting intermediate status #{intermediate_to_status}" if Time.now - lock_obtained_at > LOCK_EXPIRY
|
46
50
|
end
|
47
51
|
# If a block was given, start a heartbeat thread
|
@@ -51,23 +55,23 @@ module StatusWorkflow
|
|
51
55
|
loop do
|
52
56
|
StatusWorkflow.redis.expire lock_key, LOCK_EXPIRY
|
53
57
|
lock_obtained_at = Time.now
|
54
|
-
sleep LOCK_EXPIRY/2
|
58
|
+
sleep LOCK_EXPIRY/2.0
|
55
59
|
end
|
56
60
|
end
|
57
61
|
yield
|
58
62
|
rescue
|
59
63
|
# If the block errors, set status to error and record the backtrace
|
60
64
|
error = (["#{$!.class} #{$!.message}"] + $!.backtrace).join("\n")
|
61
|
-
update_columns
|
65
|
+
update_columns status_column => 'error', status_changed_at_column => Time.now, error_column => error
|
62
66
|
raise
|
63
67
|
end
|
64
68
|
end
|
65
69
|
# Success!
|
66
70
|
if intermediate_to_status
|
67
|
-
send "can_enter_#{final_to_status}?", true
|
71
|
+
send "#{prefix_}can_enter_#{final_to_status}?", true
|
68
72
|
raise TooSlow, "#{lock_key} lost lock after checking final status" if Time.now - lock_obtained_at > LOCK_EXPIRY
|
69
73
|
end
|
70
|
-
update_columns
|
74
|
+
update_columns status_column => final_to_status, status_changed_at_column => Time.now
|
71
75
|
ensure
|
72
76
|
raise TooSlow, "#{lock_key} lost lock" if Time.now - lock_obtained_at > LOCK_EXPIRY
|
73
77
|
StatusWorkflow.redis.del lock_key
|
@@ -77,27 +81,41 @@ module StatusWorkflow
|
|
77
81
|
end
|
78
82
|
|
79
83
|
module ClassMethods
|
80
|
-
def status_workflow(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
status_transition!
|
84
|
+
def status_workflow(workflows)
|
85
|
+
if workflows.first.last.is_a?(Array)
|
86
|
+
# default mode: use just status
|
87
|
+
workflows = { nil => workflows }
|
88
|
+
end
|
89
|
+
workflows.each do |prefix, transitions|
|
90
|
+
if prefix
|
91
|
+
# no this is not a mistake, the localvar is prefix_
|
92
|
+
prefix_ = "#{prefix}_"
|
93
|
+
define_method "#{prefix_}status_transition!" do |*args, &blk|
|
94
|
+
status_transition!(*(args+[prefix]), &blk)
|
95
|
+
end
|
90
96
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
raise InvalidTransition, "can't enter #{to_status} from #{status}, expected #{from_statuses.to_a.join('/')}"
|
97
|
+
transitions.inject({}) do |memo, (from_status, to_statuses)|
|
98
|
+
to_statuses.each do |to_status|
|
99
|
+
memo[to_status] ||= Set.new
|
100
|
+
memo[to_status] << from_status
|
96
101
|
end
|
97
102
|
memo
|
98
|
-
end
|
99
|
-
|
100
|
-
|
103
|
+
end.each do |to_status, from_statuses|
|
104
|
+
define_method "#{prefix_}enter_#{to_status}!" do
|
105
|
+
send "#{prefix_}status_transition!", nil, to_status
|
106
|
+
end
|
107
|
+
define_method "#{prefix_}can_enter_#{to_status}?" do |raise_error = false|
|
108
|
+
reload
|
109
|
+
status = read_attribute "#{prefix_}status"
|
110
|
+
memo = from_statuses.include? status&.to_sym
|
111
|
+
if raise_error and not memo
|
112
|
+
raise InvalidTransition, "can't enter #{to_status} from #{status}, expected #{from_statuses.to_a.join('/')}"
|
113
|
+
end
|
114
|
+
memo
|
115
|
+
end
|
116
|
+
define_method "#{prefix_}enter_#{to_status}_if_possible" do
|
117
|
+
begin; send("#{prefix_}enter_#{to_status}!"); rescue InvalidTransition; false; end
|
118
|
+
end
|
101
119
|
end
|
102
120
|
end
|
103
121
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: status_workflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seamus Abshere
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|