status_workflow 1.0.1 → 2.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: 31fac79b181eaf226d6f199406c58b23f2860674
4
- data.tar.gz: abb35fc730390397b21692c0a0f398a9628253e1
3
+ metadata.gz: 1a93bf66014b35a93bc1b23d36acba7374f81e4f
4
+ data.tar.gz: 53143e1a773298c5d6b60156aefc01638f2e7c49
5
5
  SHA512:
6
- metadata.gz: e7f31170e7845fcd47a2f0f9ab5e68c08b6a7eae0226f784b8060d563a4ffa812ca21d7ff79230e248fb899d2a325d57299ce830ade5ffb4776d412247889a85
7
- data.tar.gz: 271e5943314d59d02ca2a8e48e2cd5750077fbea2986605c2ca3d0074de16efca5bbb407cbd5fc76b6d1316bb4a80629c8ddff2d877b574a9d9e7da5c1b9f50c
6
+ metadata.gz: 914fa3e10e9d3ca35fea3cc79f963e8d5023daa714155cd8db34a642e223c8a9d47024e5da58a93f628a202f31d750d679d1c4c45e77be9b5d3e7c500c8cc619
7
+ data.tar.gz: 042ea0d80d56d10d7a47b4474423284159106db9e0abd2a238081f07b61b369cad6125fb33d4906bfc31ee5f0b3a88606c1e72f02c3d2a065f7c8553509fce88
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
13
  *.gem
14
+ Gemfile.lock
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ 2.0.0 / 2018-09-30
2
+
3
+ Breaking changes
4
+
5
+ * Get rid of block form on enter_X!
6
+
7
+ Enhancements
8
+
9
+ * Add `status_transition!(intermediate_status, final_status)`
10
+
1
11
  1.0.1 / 2018-09-28
2
12
 
3
13
  Enhancements
@@ -4,6 +4,7 @@ require 'set'
4
4
 
5
5
  module StatusWorkflow
6
6
  class InvalidTransition < StandardError; end
7
+ class TooSlow < StandardError; end
7
8
 
8
9
  def self.included(klass)
9
10
  klass.extend ClassMethods
@@ -21,6 +22,60 @@ module StatusWorkflow
21
22
  LOCK_EXPIRY = 4
22
23
  LOCK_CHECK_RATE = 0.2
23
24
 
25
+ def status_transition!(intermediate_to_status, final_to_status)
26
+ intermediate_to_status = intermediate_to_status&.to_s
27
+ final_to_status = final_to_status&.to_s
28
+ lock_obtained_at = nil
29
+ lock_key = "status_workflow/#{self.class.name}/#{id}"
30
+ # Give ourselves 8 seconds to get the lock, checking every 0.2 seconds
31
+ Timeout.timeout(LOCK_ACQUISITION_TIMEOUT, nil, "#{lock_key} timeout waiting for lock") do
32
+ until StatusWorkflow.redis.set(lock_key, true, nx: true, ex: LOCK_EXPIRY)
33
+ sleep LOCK_CHECK_RATE
34
+ end
35
+ lock_obtained_at = Time.now
36
+ end
37
+ heartbeat = nil
38
+ initial_to_status = intermediate_to_status || final_to_status
39
+ begin
40
+ # depend on #can_enter_X to reload
41
+ send "can_enter_#{initial_to_status}?", true
42
+ raise TooSlow, "#{lock_key} lost lock after checking status" if Time.now - lock_obtained_at > LOCK_EXPIRY
43
+ if intermediate_to_status
44
+ update_columns status: intermediate_to_status, status_changed_at: Time.now
45
+ raise TooSlow, "#{lock_key} lost lock after setting intermediate status #{intermediate_to_status}" if Time.now - lock_obtained_at > LOCK_EXPIRY
46
+ end
47
+ # If a block was given, start a heartbeat thread
48
+ if block_given?
49
+ begin
50
+ heartbeat = Thread.new do
51
+ loop do
52
+ StatusWorkflow.redis.expire lock_key, LOCK_EXPIRY
53
+ lock_obtained_at = Time.now
54
+ sleep LOCK_EXPIRY/2
55
+ end
56
+ end
57
+ yield
58
+ rescue
59
+ # If the block errors, set status to error and record the backtrace
60
+ error = (["#{$!.class} #{$!.message}"] + $!.backtrace).join("\n")
61
+ update_columns status: 'error', status_changed_at: Time.now, error: error
62
+ raise
63
+ end
64
+ end
65
+ # Success!
66
+ if intermediate_to_status
67
+ send "can_enter_#{final_to_status}?", true
68
+ raise TooSlow, "#{lock_key} lost lock after checking final status" if Time.now - lock_obtained_at > LOCK_EXPIRY
69
+ end
70
+ update_columns status: final_to_status, status_changed_at: Time.now
71
+ ensure
72
+ raise TooSlow, "#{lock_key} lost lock" if Time.now - lock_obtained_at > LOCK_EXPIRY
73
+ StatusWorkflow.redis.del lock_key
74
+ heartbeat.kill if heartbeat
75
+ end
76
+ true
77
+ end
78
+
24
79
  module ClassMethods
25
80
  def status_workflow(transitions)
26
81
  transitions.inject({}) do |memo, (from_status, to_statuses)|
@@ -30,49 +85,16 @@ module StatusWorkflow
30
85
  end
31
86
  memo
32
87
  end.each do |to_status, from_statuses|
33
- define_method "enter_#{to_status}!" do |&blk|
34
- lock_key = "status_workflow/#{self.class.name}/#{id}"
35
- # Give ourselves 8 seconds to get the lock, checking every 0.2 seconds
36
- Timeout.timeout(LOCK_ACQUISITION_TIMEOUT, nil, "timeout waiting for #{self.class.name}/#{id} lock") do
37
- until StatusWorkflow.redis.set(lock_key, true, nx: true, ex: LOCK_EXPIRY)
38
- sleep LOCK_CHECK_RATE
39
- end
40
- end
41
- heartbeat = nil
42
- begin
43
- # Give ourselves 2 seconds to check the status of the lock
44
- Timeout.timeout(2, nil, "timeout waiting for #{self.class.name}/#{id} status check") do
45
- # depend on #can_enter_X to reload
46
- raise InvalidTransition, "can't enter #{to_status} from #{status}, expected #{from_statuses.to_a.join('/')}" unless send("can_enter_#{to_status}?")
47
- end
48
- # If a block was given, start a heartbeat thread
49
- if blk
50
- begin
51
- heartbeat = Thread.new do
52
- loop do
53
- StatusWorkflow.redis.expire lock_key, LOCK_EXPIRY
54
- sleep LOCK_EXPIRY/2
55
- end
56
- end
57
- blk.call
58
- rescue
59
- # If the block errors, set status to error and record the backtrace
60
- error = (["#{$!.class} #{$!.message}"] + $!.backtrace).join("\n")
61
- update_columns status: 'error', status_changed_at: Time.now, error: error
62
- raise
63
- end
64
- end
65
- # Success!
66
- update_columns status: to_status, status_changed_at: Time.now
67
- ensure
68
- StatusWorkflow.redis.del lock_key
69
- heartbeat.kill if heartbeat
70
- end
71
- true
88
+ define_method "enter_#{to_status}!" do
89
+ status_transition! nil, to_status
72
90
  end
73
- define_method "can_enter_#{to_status}?" do
91
+ define_method "can_enter_#{to_status}?" do |raise_error = false|
74
92
  reload
75
- from_statuses.include? status&.to_sym
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('/')}"
96
+ end
97
+ memo
76
98
  end
77
99
  define_method "enter_#{to_status}_if_possible" do
78
100
  begin; send("enter_#{to_status}!"); rescue InvalidTransition; false; end
@@ -1,3 +1,3 @@
1
1
  module StatusWorkflow
2
- VERSION = '1.0.1'
2
+ VERSION = '2.0.0'
3
3
  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: 1.0.1
4
+ version: 2.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-28 00:00:00.000000000 Z
11
+ date: 2018-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -135,7 +135,6 @@ files:
135
135
  - CHANGELOG
136
136
  - CODE_OF_CONDUCT.md
137
137
  - Gemfile
138
- - Gemfile.lock
139
138
  - LICENSE.txt
140
139
  - README.md
141
140
  - Rakefile
data/Gemfile.lock DELETED
@@ -1,67 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- status_workflow (1.0.0)
5
- redis
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activemodel (5.2.1)
11
- activesupport (= 5.2.1)
12
- activerecord (5.2.1)
13
- activemodel (= 5.2.1)
14
- activesupport (= 5.2.1)
15
- arel (>= 9.0)
16
- activesupport (5.2.1)
17
- concurrent-ruby (~> 1.0, >= 1.0.2)
18
- i18n (>= 0.7, < 2)
19
- minitest (~> 5.1)
20
- tzinfo (~> 1.1)
21
- arel (9.0.0)
22
- coderay (1.1.2)
23
- concurrent-ruby (1.0.5)
24
- database_cleaner (1.7.0)
25
- diff-lcs (1.3)
26
- i18n (1.1.0)
27
- concurrent-ruby (~> 1.0)
28
- method_source (0.9.0)
29
- minitest (5.11.3)
30
- pg (1.1.3)
31
- pry (0.11.3)
32
- coderay (~> 1.1.0)
33
- method_source (~> 0.9.0)
34
- rake (10.5.0)
35
- redis (4.0.2)
36
- rspec (3.8.0)
37
- rspec-core (~> 3.8.0)
38
- rspec-expectations (~> 3.8.0)
39
- rspec-mocks (~> 3.8.0)
40
- rspec-core (3.8.0)
41
- rspec-support (~> 3.8.0)
42
- rspec-expectations (3.8.1)
43
- diff-lcs (>= 1.2.0, < 2.0)
44
- rspec-support (~> 3.8.0)
45
- rspec-mocks (3.8.0)
46
- diff-lcs (>= 1.2.0, < 2.0)
47
- rspec-support (~> 3.8.0)
48
- rspec-support (3.8.0)
49
- thread_safe (0.3.6)
50
- tzinfo (1.2.5)
51
- thread_safe (~> 0.1)
52
-
53
- PLATFORMS
54
- ruby
55
-
56
- DEPENDENCIES
57
- activerecord
58
- bundler (~> 1.16)
59
- database_cleaner
60
- pg
61
- pry
62
- rake (~> 10.0)
63
- rspec (~> 3.0)
64
- status_workflow!
65
-
66
- BUNDLED WITH
67
- 1.16.5