status_workflow 2.0.0 → 3.0.0

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