action-handle 0.0.4 → 0.1.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
  SHA256:
3
- metadata.gz: 6332114ddd00e990464e301c32078e4cae3c6024a22419ee5c5701fa65be8de9
4
- data.tar.gz: 94e9d892a1a9759e59f37d310a7549bb71c44f383f35731b1562d019f2947af6
3
+ metadata.gz: b8371bdabf811b69aee9c0335132d921730d7cce0f7e21be4e1ac46945a5536f
4
+ data.tar.gz: aca0eaa9905028bc878426807ac2500da8f370298622d978c9d9783e00574b34
5
5
  SHA512:
6
- metadata.gz: 0c32d85457758b99474ab513c3432d196c410ed9b33cbd5eba24521f00a76e82efd78b4e290538d3e3b6f814332ec7253ec21cb683b2f365b4b770a99cba4fc8
7
- data.tar.gz: 3a7f5ea0c2dc2de541746d6c8b38ac18cb162409c41790d38411afa67a3985cd47439a72eaaee0b38bac793b482a63e3fea141ede2af796f749b892a62d8f67a
6
+ metadata.gz: 6c87b77a3710d15afad1093b1d2a7daef088f10c0532d46f88c226fe3c191f286bd1da43c340fd7855f637bd7deafb9775bed4caee79fcf7c08884446391ced7
7
+ data.tar.gz: bb3363cacbdd5bdd1f4910478297b6f4b8164b5445c3fb5f782f4c12c093e01d1a17227af96c97573cf882b9e399e4b34eb80f8a7bae9396ae815dcd8c12b563
data/Gemfile.lock CHANGED
@@ -1,39 +1,56 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- action-handle (0.0.4)
5
- connection_pool
6
- redis
4
+ action-handle (0.1.0)
5
+ connection_pool (~> 2.2)
6
+ redis (~> 4.1)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- connection_pool (2.2.2)
12
- diff-lcs (1.3)
13
- rake (10.5.0)
14
- redis (4.1.0)
15
- rspec (3.9.0)
16
- rspec-core (~> 3.9.0)
17
- rspec-expectations (~> 3.9.0)
18
- rspec-mocks (~> 3.9.0)
19
- rspec-core (3.9.0)
20
- rspec-support (~> 3.9.0)
21
- rspec-expectations (3.9.0)
11
+ activesupport (6.1.3)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ concurrent-ruby (1.1.8)
18
+ connection_pool (2.2.3)
19
+ diff-lcs (1.4.4)
20
+ fakeredis (0.8.0)
21
+ redis (~> 4.1)
22
+ i18n (1.8.9)
23
+ concurrent-ruby (~> 1.0)
24
+ minitest (5.14.4)
25
+ rake (13.0.3)
26
+ redis (4.2.5)
27
+ rspec (3.10.0)
28
+ rspec-core (~> 3.10.0)
29
+ rspec-expectations (~> 3.10.0)
30
+ rspec-mocks (~> 3.10.0)
31
+ rspec-core (3.10.1)
32
+ rspec-support (~> 3.10.0)
33
+ rspec-expectations (3.10.1)
22
34
  diff-lcs (>= 1.2.0, < 2.0)
23
- rspec-support (~> 3.9.0)
24
- rspec-mocks (3.9.0)
35
+ rspec-support (~> 3.10.0)
36
+ rspec-mocks (3.10.2)
25
37
  diff-lcs (>= 1.2.0, < 2.0)
26
- rspec-support (~> 3.9.0)
27
- rspec-support (3.9.0)
38
+ rspec-support (~> 3.10.0)
39
+ rspec-support (3.10.2)
40
+ tzinfo (2.0.4)
41
+ concurrent-ruby (~> 1.0)
42
+ zeitwerk (2.4.2)
28
43
 
29
44
  PLATFORMS
30
45
  ruby
31
46
 
32
47
  DEPENDENCIES
33
48
  action-handle!
49
+ activesupport (~> 6.0)
34
50
  bundler (~> 2.0)
35
- rake (~> 10.0)
36
- rspec
51
+ fakeredis (~> 0.8)
52
+ rake (~> 13.0)
53
+ rspec (~> 3.9)
37
54
 
38
55
  BUNDLED WITH
39
- 2.1.0
56
+ 2.2.9
data/README.md CHANGED
@@ -1,5 +1,104 @@
1
1
  # ActionHandle
2
2
 
3
+ This gem allows distributed locking with data for actions that are asynchoronous, distributed or simply have various state dependent rules.
4
+
5
+ ## Usage examples
6
+
7
+ ### ActiveJob conflicts
8
+
9
+ Let's say we have some resource managed by a handle
10
+ ```ruby
11
+ class ResourceHandle < ActionHandle::Base; end
12
+ ```
13
+
14
+ And two async jobs with different priorities
15
+ ```ruby
16
+ class NiceToHaveJob
17
+ def perform
18
+ handle = ResourceHandle.new('resource_name', 'nice')
19
+
20
+ return unless handle.create
21
+
22
+ # execution code
23
+
24
+ handle.expire
25
+ end
26
+ end
27
+
28
+ class ImportantJob
29
+ def perform
30
+ handle = ResourceHandle.new('resource_name', 'vip')
31
+
32
+ return if handle.value == 'vip'
33
+
34
+ handle.claim # ignores current owner
35
+
36
+ # execution code
37
+
38
+ handle.expire
39
+ end
40
+ end
41
+ ```
42
+
43
+ `NiceToHaveJob` will only perform on free resource, while `ImportantJob` will always perform unless another `vip` is already performing.
44
+
45
+ ### Not so serious story time example
46
+ Let's say we have a room and a few users who try to book it
47
+
48
+ First we define a room handle
49
+ ```ruby
50
+ class RoomHandle < ActionHandle::Base
51
+ prefix :room
52
+ ttl 30.minutes
53
+ end
54
+ ```
55
+
56
+ Then we create a handle with room number and user
57
+ ```ruby
58
+ RoomHandle.create(101, 'Tom', 15.minutes)
59
+ #=> true
60
+ ```
61
+
62
+ Tom booked this room with a ttl of an hour. Another user tries to do the same
63
+
64
+ ```ruby
65
+ handle = RoomHandle.new(101, 'Jack')
66
+ handle.create
67
+ #=> false
68
+
69
+ handle.value
70
+ #=> 'Tom'
71
+ ```
72
+
73
+ Jack fails to book the room and sees that Tom is using it so decides to try later. Most of ttl has passed but Tom is still in the room and extends his handle (a pretty dick move considering Jack is waiting)
74
+
75
+ ```ruby
76
+ RoomHandle.renew(101, 'Tom', 1.hour)
77
+ #=> true
78
+ ```
79
+
80
+ More than a standart ttl has passed and Jack tries to book again
81
+
82
+ ```ruby
83
+ RoomHandle.create(101, 'Jack')
84
+ #=> false
85
+ ```
86
+
87
+ Jack fails to book the room and stays angry on Tom for taking too long and contacts managament. Meaniwhile Tom is done with his things and leaves the room.
88
+
89
+ ```ruby
90
+ RoomHandle.expire(101, 'Tom')
91
+ #=> true
92
+ ```
93
+
94
+ Managament decides to excersise their power and kick Tom out, but sees that no one is using the room
95
+
96
+ ```ruby
97
+ RoomHandle.new(101).taken?
98
+ #=> false
99
+ ```
100
+
101
+
3
102
  ## Installation
4
103
 
5
104
  Add this line to your application's Gemfile:
@@ -16,6 +115,27 @@ Or install it yourself as:
16
115
 
17
116
  $ gem install action-handle
18
117
 
118
+ ## Configuration
119
+
120
+ ```ruby
121
+ ActionHandle.configure do
122
+ # simply current redis
123
+ adapter :redis
124
+
125
+ # redis with pool
126
+ adapter :redis
127
+ redis_pool ConnectionPool.new(size: 5, timeout: 3) { Redis.new }
128
+
129
+ # rails cache
130
+ adapter :cache
131
+
132
+ # adapters directly (for ex. custom adapters)
133
+ adapter Adapters::RedisPool.new(
134
+ ConnectionPool.new(size: 5, timeout: 3) { Redis.new }
135
+ )
136
+ end
137
+ ```
138
+
19
139
  ## Development
20
140
 
21
141
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interaction prompt that will allow you to experiment.
@@ -28,10 +28,12 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.require_paths = %w[lib]
30
30
 
31
- spec.add_dependency 'connection_pool'
32
- spec.add_dependency 'redis'
31
+ spec.add_dependency 'connection_pool', '~> 2.2'
32
+ spec.add_dependency 'redis', '~> 4.1'
33
33
 
34
+ spec.add_development_dependency 'activesupport', '~> 6.0'
34
35
  spec.add_development_dependency 'bundler', '~> 2.0'
35
- spec.add_development_dependency 'rake', '~> 10.0'
36
- spec.add_development_dependency 'rspec'
36
+ spec.add_development_dependency 'fakeredis', '~> 0.8'
37
+ spec.add_development_dependency 'rake', '~> 13.0'
38
+ spec.add_development_dependency 'rspec', '~> 3.9'
37
39
  end
data/lib/action_handle.rb CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  require 'action_handle/version'
4
4
  require 'action_handle/configuration'
5
- require 'action_handle/base'
6
5
 
7
6
  require 'action_handle/adapters/base'
8
- require 'action_handle/adapters/rails_cache'
7
+ require 'action_handle/adapters/cache_store'
9
8
  require 'action_handle/adapters/redis_pool'
10
9
 
10
+ require 'action_handle/base'
11
+
11
12
  module ActionHandle
12
- def self.configure
13
- yield(Configuration)
13
+ def self.configure(&block)
14
+ Configuration.instance_eval(&block)
14
15
  end
15
16
  end
@@ -9,6 +9,14 @@ module ActionHandle
9
9
 
10
10
  def_delegators Configuration, :logger, :silence_errors
11
11
 
12
+ def create(_key, _value, _ttl)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def renew(_key, _value, _ttl)
17
+ raise NotImplementedError
18
+ end
19
+
12
20
  def taken?(_key)
13
21
  raise NotImplementedError
14
22
  end
@@ -17,7 +25,7 @@ module ActionHandle
17
25
  raise NotImplementedError
18
26
  end
19
27
 
20
- def info(_key)
28
+ def value(_key)
21
29
  raise NotImplementedError
22
30
  end
23
31
 
@@ -2,7 +2,25 @@
2
2
 
3
3
  module ActionHandle
4
4
  module Adapters
5
- class RailsCache < Base
5
+ class CacheStore < Base
6
+ attr_reader :client
7
+
8
+ def initialize(cache = nil)
9
+ @client = cache
10
+ end
11
+
12
+ def create(key, value, ttl)
13
+ perform_with_expectation('OK') do
14
+ client.write(key, value, expires_in: ttl) unless taken?(key)
15
+ end
16
+ end
17
+
18
+ def renew(key, value, ttl)
19
+ perform_with_expectation('OK') do
20
+ client.write(key, value, expires_in: ttl) if current?(key, value)
21
+ end
22
+ end
23
+
6
24
  def taken?(key)
7
25
  perform_with_expectation(true) do
8
26
  client.exist?(key)
@@ -15,27 +33,21 @@ module ActionHandle
15
33
  end
16
34
  end
17
35
 
18
- def info(key)
36
+ def value(key)
19
37
  safely_perform { client.read(key) }
20
38
  end
21
39
 
22
40
  def claim(key, value, ttl)
23
- perform_with_expectation(true) do
41
+ perform_with_expectation('OK') do
24
42
  client.write(key, value, expires_in: ttl)
25
43
  end
26
44
  end
27
45
 
28
46
  def expire(key)
29
47
  perform_with_expectation(true) do
30
- client.delete(key)
48
+ client.delete(key).to_i > 0
31
49
  end
32
50
  end
33
-
34
- private
35
-
36
- def client
37
- ::Rails.cache
38
- end
39
51
  end
40
52
  end
41
53
  end
@@ -16,9 +16,25 @@ module ActionHandle
16
16
  @pool = pool || CurrentRedisWapper.new
17
17
  end
18
18
 
19
+ def create(key, value, ttl)
20
+ perform_with_expectation(true) do
21
+ @pool.with do |client|
22
+ client.set(key, value, ex: ttl, nx: true)
23
+ end
24
+ end
25
+ end
26
+
27
+ def renew(key, value, ttl)
28
+ perform_with_expectation(true) do
29
+ @pool.with do |client|
30
+ client.expire(key, ttl) if current?(key, value)
31
+ end
32
+ end
33
+ end
34
+
19
35
  def taken?(key)
20
36
  perform_with_expectation(true) do
21
- @pool.with { |client| client.exists(key) }
37
+ @pool.with { |client| client.exists?(key) }
22
38
  end
23
39
  end
24
40
 
@@ -28,7 +44,7 @@ module ActionHandle
28
44
  end
29
45
  end
30
46
 
31
- def info(key)
47
+ def value(key)
32
48
  safely_perform do
33
49
  @pool.with { |client| client.get(key) }
34
50
  end
@@ -1,43 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module ActionHandle
6
4
  class Base
7
- extend Forwardable
5
+ BUILTIN_ADAPTERS = {
6
+ redis: Adapters::RedisPool,
7
+ cache: Adapters::CacheStore
8
+ }.freeze
9
+
10
+ class << self
11
+ def prefix(string = nil)
12
+ @prefix = string if string
13
+ @prefix
14
+ end
15
+
16
+ def ttl(amount = nil)
17
+ @ttl = amount if amount
18
+ @ttl ||= 100
19
+ @ttl
20
+ end
21
+
22
+ def create(*args, &block)
23
+ new(*args, &block).create
24
+ end
25
+
26
+ def expire(*args, &block)
27
+ new(*args, &block).expire
28
+ end
29
+
30
+ def renew(*args, &block)
31
+ new(*args, &block).renew
32
+ end
33
+
34
+ def claim(*args, &block)
35
+ new(*args, &block).claim
36
+ end
37
+
38
+ def value(*args, &block)
39
+ new(*args, &block).value
40
+ end
41
+ end
8
42
 
9
- def_delegator Configuration, :adapter
43
+ attr_reader :key, :instance_value
44
+
45
+ def initialize(key, instance_value = nil)
46
+ @key = key
47
+ @instance_value = instance_value
48
+ end
49
+
50
+ def create
51
+ adapter.create(handle_key, instance_value, ttl)
52
+ end
53
+
54
+ def renew
55
+ adapter.renew(handle_key, instance_value, ttl)
56
+ end
10
57
 
11
58
  def taken?
12
- adapter.taken?(key)
59
+ adapter.taken?(handle_key)
13
60
  end
14
61
 
15
62
  def current?
16
- adapter.current?(key, value)
63
+ adapter.current?(handle_key, instance_value)
17
64
  end
18
65
 
19
- def info
20
- adapter.info(key)
66
+ def value
67
+ adapter.value(handle_key)
21
68
  end
22
69
 
23
70
  def claim
24
- adapter.claim(key, value, ttl)
71
+ adapter.claim(handle_key, instance_value, ttl)
25
72
  end
26
73
 
27
74
  def expire
28
- adapter.expire(key)
75
+ adapter.expire(handle_key)
29
76
  end
30
77
 
31
78
  def ttl
32
- defined?(self.class::TTL) ? self.class::TTL : 100
79
+ self.class.ttl
33
80
  end
34
81
 
35
- def value
36
- true
82
+ def handle_key
83
+ ['AH', self.class.prefix, key].join('/')
37
84
  end
38
85
 
39
- def key
40
- raise NotImplementedError, 'must define `key`'
86
+ def adapter
87
+ @adapter ||=
88
+ case Configuration.adapter
89
+ when Symbol, String
90
+ klass = BUILTIN_ADAPTERS[Configuration.adapter.to_sym]
91
+
92
+ klass&.new(*Configuration.redis_pool)
93
+ else
94
+ Configuration.adapter
95
+ end
41
96
  end
42
97
  end
43
98
  end
@@ -8,12 +8,20 @@ module ActionHandle
8
8
 
9
9
  module_function
10
10
 
11
- def adapter
12
- @adapter ||= Adapters::RedisPool.new
11
+ def adapter(obj = nil)
12
+ @adapter = obj if obj
13
+
14
+ @adapter ||= :redis
15
+ end
16
+
17
+ def redis_pool
18
+ @redis_pool
13
19
  end
14
20
 
15
- def silence_errors
16
- @silence_errors ||= :no
21
+ def silence_errors(value = nil)
22
+ @silence_errors = value if value
23
+
24
+ @silence_errors ||= false
17
25
  end
18
26
  end
19
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionHandle
4
- VERSION = '0.0.4'.freeze
4
+ VERSION = '0.1.0'
5
5
  end
metadata CHANGED
@@ -1,43 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action-handle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rbviz
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-15 00:00:00.000000000 Z
11
+ date: 2021-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: redis
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '4.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '6.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '0'
54
+ version: '6.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +66,48 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeredis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rake
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
87
  - - "~>"
60
88
  - !ruby/object:Gem::Version
61
- version: '10.0'
89
+ version: '13.0'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: '10.0'
96
+ version: '13.0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rspec
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
- - - ">="
101
+ - - "~>"
74
102
  - !ruby/object:Gem::Version
75
- version: '0'
103
+ version: '3.9'
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
- - - ">="
108
+ - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: '0'
110
+ version: '3.9'
83
111
  description: Allows distributed handle/lock creation and managament
84
112
  email:
85
113
  - inbox@rbviz.com
@@ -101,7 +129,7 @@ files:
101
129
  - bin/setup
102
130
  - lib/action_handle.rb
103
131
  - lib/action_handle/adapters/base.rb
104
- - lib/action_handle/adapters/rails_cache.rb
132
+ - lib/action_handle/adapters/cache_store.rb
105
133
  - lib/action_handle/adapters/redis_pool.rb
106
134
  - lib/action_handle/base.rb
107
135
  - lib/action_handle/configuration.rb
@@ -112,7 +140,7 @@ licenses:
112
140
  metadata:
113
141
  homepage_uri: https://github.com/rbviz/action-handle
114
142
  source_code_uri: https://github.com/rbviz/action-handle
115
- post_install_message:
143
+ post_install_message:
116
144
  rdoc_options: []
117
145
  require_paths:
118
146
  - lib
@@ -127,8 +155,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
155
  - !ruby/object:Gem::Version
128
156
  version: '0'
129
157
  requirements: []
130
- rubygems_version: 3.0.6
131
- signing_key:
158
+ rubygems_version: 3.2.3
159
+ signing_key:
132
160
  specification_version: 4
133
161
  summary: Backend agnostic resource handle
134
162
  test_files: []