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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a93bf66014b35a93bc1b23d36acba7374f81e4f
4
- data.tar.gz: 53143e1a773298c5d6b60156aefc01638f2e7c49
3
+ metadata.gz: 130b12c5b45db744a86537b671118ce01f0b1be9
4
+ data.tar.gz: 912a5a9530aa43e9cf1c0c96af814352f2acbf49
5
5
  SHA512:
6
- metadata.gz: 914fa3e10e9d3ca35fea3cc79f963e8d5023daa714155cd8db34a642e223c8a9d47024e5da58a93f628a202f31d750d679d1c4c45e77be9b5d3e7c500c8cc619
7
- data.tar.gz: 042ea0d80d56d10d7a47b4474423284159106db9e0abd2a238081f07b61b369cad6125fb33d4906bfc31ee5f0b3a88606c1e72f02c3d2a065f7c8553509fce88
6
+ metadata.gz: f943c7f5188f83493092bd7474878f8f4f47acdc262c1cd08d4fe581d4a1056bf38b4f537b5eab8713af0016e34c2602d1a389ad39c0ae95f3a6c7a99117edd2
7
+ data.tar.gz: 484a3306cb84e9f2bfe0f100c51dac8c9cbbccc798943bf5d6f36b07c0e1bf560c15ad0580b1ae19cbc9dd4fda2c6d97e22ceba6341b92a1b1c92ad0159409b1
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ 3.0.0 / 2018-10-24
2
+
3
+ Breaking changes
4
+
5
+ * error -> status_error column name
6
+
7
+ Enhancements
8
+
9
+ * status_workflow(alt: { a: [:b] }) if you want to use alt_status
10
+
1
11
  2.0.0 / 2018-09-30
2
12
 
3
13
  Breaking changes
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>
@@ -1,3 +1,3 @@
1
1
  module StatusWorkflow
2
- VERSION = '2.0.0'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -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 status: intermediate_to_status, status_changed_at: Time.now
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 status: 'error', status_changed_at: Time.now, error: error
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 status: final_to_status, status_changed_at: Time.now
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(transitions)
81
- transitions.inject({}) do |memo, (from_status, to_statuses)|
82
- to_statuses.each do |to_status|
83
- memo[to_status] ||= Set.new
84
- memo[to_status] << from_status
85
- end
86
- memo
87
- end.each do |to_status, from_statuses|
88
- define_method "enter_#{to_status}!" do
89
- status_transition! nil, to_status
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
- define_method "can_enter_#{to_status}?" do |raise_error = false|
92
- reload
93
- memo = from_statuses.include? status&.to_sym
94
- if raise_error and not memo
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
- define_method "enter_#{to_status}_if_possible" do
100
- begin; send("enter_#{to_status}!"); rescue InvalidTransition; false; end
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: 2.0.0
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-09-30 00:00:00.000000000 Z
11
+ date: 2018-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis