status_workflow 1.0.1 → 2.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: 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