async_storage 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/Gemfile.lock +1 -1
- data/lib/async_storage/allocator.rb +165 -0
- data/lib/async_storage/repo.rb +18 -87
- data/lib/async_storage/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: add79afe0dfa294abeee5fb5102e05f5d5fbecfc70d700c5b25fc4f17cf9881f
|
4
|
+
data.tar.gz: e0a8841f106ac58f7146938749732c43d99e0f56dacfcc4816d62c251bf34d2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28800339448b6668b3da60bb5ad80d5e998c26ef42ebd3c06c881d49b09bfe7a8aed7893323578f3c0171183f705a5ca58fc9b871aa420425f161896e0e02d2a
|
7
|
+
data.tar.gz: dff1ad907077f87c95977f14b56c4a8c9d49f394f781a1c164a53266c5a0ee3cae45078728b41b1020d47b3bb9c7949bf9e5286f83c21fc8e39c49735a286c36
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'async_storage/naming'
|
4
|
+
|
5
|
+
module AsyncStorage
|
6
|
+
class Allocator
|
7
|
+
CTRL = {
|
8
|
+
enqueued: '0',
|
9
|
+
executed: '1',
|
10
|
+
missing: nil,
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :@repo, :resolver_class, :expires_in
|
15
|
+
|
16
|
+
attr_reader :naming
|
17
|
+
|
18
|
+
# @param repo [AsyncStorage::Repo] An instance of Repo
|
19
|
+
# @param args [Array] An array with arguments to be fowarded to resolver#call
|
20
|
+
def initialize(repo, *args)
|
21
|
+
@repo = repo
|
22
|
+
@args = args
|
23
|
+
@naming = AsyncStorage::Naming.new(repo.resolver_class, *args)
|
24
|
+
# It's different than the config.namespace.
|
25
|
+
# Thinking about a directory structure.. The global namespace would be the root directory.
|
26
|
+
# And the namespace under Repo level would be the subdirectory.
|
27
|
+
@naming.prefix = repo.namespace if repo.namespace
|
28
|
+
end
|
29
|
+
|
30
|
+
# Async get value with a given key
|
31
|
+
#
|
32
|
+
# @return [Object, NilClass] Return both stale or fresh object. If does not exist async call the retriever and return nil
|
33
|
+
def get
|
34
|
+
connection do |redis|
|
35
|
+
raw_head = redis.get(naming.head)
|
36
|
+
case raw_head
|
37
|
+
when CTRL[:executed], CTRL[:enqueued]
|
38
|
+
read_body(redis) # Try to deliver stale content
|
39
|
+
when CTRL[:missing]
|
40
|
+
return update!(redis) unless async?
|
41
|
+
|
42
|
+
perform_async(redis) # Enqueue background job to resolve content
|
43
|
+
redis.set(naming.head, CTRL[:enqueued])
|
44
|
+
read_body(redis) # Try to deliver stale content
|
45
|
+
else
|
46
|
+
raise AsyncStorage::Error, format('the key %<k>s have an invalid value. Only "1" or "0" values are expected. And we got %<v>p', v: raw_head, k: naming.head)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sync get value with a given value
|
52
|
+
#
|
53
|
+
# @return [Object] Return the result from resolver
|
54
|
+
def get!
|
55
|
+
connection do |redis|
|
56
|
+
raw_head = redis.get(naming.head)
|
57
|
+
case raw_head
|
58
|
+
when CTRL[:executed]
|
59
|
+
read_body(redis) || begin
|
60
|
+
update!(redis) unless redis.exists?(naming.body)
|
61
|
+
end
|
62
|
+
when CTRL[:missing], CTRL[:enqueued]
|
63
|
+
update!(redis)
|
64
|
+
else
|
65
|
+
raise AsyncStorage::Error, format('the key %<k>s have an invalid value. Only "1" or "0" values are expected. And we got %<v>p', v: raw_head, k: naming.head)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Expire object the object with a given key. The stale object will not be removed
|
71
|
+
#
|
72
|
+
# @return [Boolean] True or False according to the object existence
|
73
|
+
def invalidate
|
74
|
+
connection do |redis|
|
75
|
+
redis.del(naming.head) == 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Delete object with a given key.
|
80
|
+
#
|
81
|
+
# @return [Boolean] True or False according to the object existence
|
82
|
+
def invalidate!
|
83
|
+
connection do |redis|
|
84
|
+
redis.multi do |cli|
|
85
|
+
cli.del(naming.body)
|
86
|
+
cli.del(naming.head)
|
87
|
+
end.include?(1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Invalidate object with the given key and update content according to the strategy
|
92
|
+
#
|
93
|
+
# @return [Object, NilClass] Stale object or nil when it does not exist
|
94
|
+
def refresh
|
95
|
+
value = get(*@args)
|
96
|
+
invalidate(*@args)
|
97
|
+
value
|
98
|
+
end
|
99
|
+
|
100
|
+
# Fetch data from resolver and store it into redis
|
101
|
+
#
|
102
|
+
# @return [Object] Return the result from resolver
|
103
|
+
def refresh!
|
104
|
+
connection { |redis| update!(redis) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check if a fresh value exist.
|
108
|
+
#
|
109
|
+
# @return [Boolean] True or False according the object existence
|
110
|
+
def exist?
|
111
|
+
connection { |redis| redis.exists?(naming.head) && redis.exists?(naming.body) }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Check if object with a given key is stale
|
115
|
+
#
|
116
|
+
# @return [NilClass, Boolean] Return nil if the object does not exist or true/false according to the object freshness state
|
117
|
+
def stale?
|
118
|
+
connection { |redis| redis.exists?(naming.body) && redis.ttl(naming.head) < 0 }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check if a fresh object exists into the storage
|
122
|
+
#
|
123
|
+
# @return [Boolean] true/false according to the object existence and freshness
|
124
|
+
def fresh?
|
125
|
+
connection { |redis| redis.exists?(naming.body) && redis.ttl(naming.head) > 0 }
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def async?
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
def perform_async(redis)
|
135
|
+
# @TODO Enqueue a real background job here. It's only working on sync mode
|
136
|
+
# redis.set(name.head, CTRL[:enqueued])
|
137
|
+
refresh!
|
138
|
+
end
|
139
|
+
|
140
|
+
def update!(redis)
|
141
|
+
payload = resolver_class.new.(*@args)
|
142
|
+
|
143
|
+
json = AsyncStorage::JSON.dump(payload, mode: :compat)
|
144
|
+
redis.multi do |cli|
|
145
|
+
cli.set(naming.body, json)
|
146
|
+
cli.set(naming.head, CTRL[:executed])
|
147
|
+
cli.expire(naming.head, expires_in) if expires_in
|
148
|
+
end
|
149
|
+
AsyncStorage::JSON.load(json)
|
150
|
+
end
|
151
|
+
|
152
|
+
def read_body(redis)
|
153
|
+
raw = redis.get(naming.body)
|
154
|
+
return unless raw
|
155
|
+
|
156
|
+
AsyncStorage::JSON.load(raw)
|
157
|
+
end
|
158
|
+
|
159
|
+
def connection
|
160
|
+
return unless block_given?
|
161
|
+
|
162
|
+
AsyncStorage.redis_pool.with { |redis| yield(redis) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/async_storage/repo.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'async_storage/
|
3
|
+
require 'async_storage/allocator'
|
4
4
|
|
5
5
|
module AsyncStorage
|
6
6
|
class Repo
|
@@ -36,150 +36,81 @@ module AsyncStorage
|
|
36
36
|
#
|
37
37
|
# @return [Object, NilClass] Return both stale or fresh object. If does not exist async call the retriever and return nil
|
38
38
|
def get(*args)
|
39
|
-
|
40
|
-
raw_head = redis.get(naming.head)
|
41
|
-
case raw_head
|
42
|
-
when CTRL[:executed], CTRL[:enqueued]
|
43
|
-
read(redis, naming.body) # Try to deliver stale content
|
44
|
-
when CTRL[:missing]
|
45
|
-
return update!(redis, naming, *args) unless async?
|
46
|
-
|
47
|
-
perform_async(*args) # Enqueue background job to resolve content
|
48
|
-
redis.set(naming.head, CTRL[:enqueued])
|
49
|
-
read(redis, naming.body) # Try to deliver stale content
|
50
|
-
else
|
51
|
-
raise AsyncStorage::Error, format('the key %<k>s have an invalid value. Only "1" or "0" values are expected. And we got %<v>p', v: raw_head, k: naming.head)
|
52
|
-
end
|
53
|
-
end
|
39
|
+
alloc(*args).get
|
54
40
|
end
|
55
41
|
|
56
42
|
# Sync get value with a given value
|
57
43
|
#
|
58
44
|
# @return [Object] Return the result from resolver
|
59
45
|
def get!(*args)
|
60
|
-
|
61
|
-
raw_head = redis.get(naming.head)
|
62
|
-
case raw_head
|
63
|
-
when CTRL[:executed]
|
64
|
-
read(redis, naming.body) || begin
|
65
|
-
update!(redis, naming, *args) unless redis.exists?(naming.body)
|
66
|
-
end
|
67
|
-
when CTRL[:missing], CTRL[:enqueued]
|
68
|
-
update!(redis, naming, *args)
|
69
|
-
else
|
70
|
-
raise AsyncStorage::Error, format('the key %<k>s have an invalid value. Only "1" or "0" values are expected. And we got %<v>p', v: raw_head, k: naming.head)
|
71
|
-
end
|
72
|
-
end
|
46
|
+
alloc(*args).get!
|
73
47
|
end
|
74
48
|
|
75
49
|
# Expire object the object with a given key. The stale object will not be removed
|
76
50
|
#
|
77
51
|
# @return [Boolean] True or False according to the object existence
|
78
52
|
def invalidate(*args)
|
79
|
-
|
80
|
-
redis.del(naming.head) == 1
|
81
|
-
end
|
53
|
+
alloc(*args).invalidate
|
82
54
|
end
|
83
55
|
|
84
56
|
# Delete object with a given key.
|
85
57
|
#
|
86
58
|
# @return [Boolean] True or False according to the object existence
|
87
59
|
def invalidate!(*args)
|
88
|
-
|
89
|
-
redis.multi do |cli|
|
90
|
-
cli.del(naming.body)
|
91
|
-
cli.del(naming.head)
|
92
|
-
end.include?(1)
|
93
|
-
end
|
60
|
+
alloc(*args).invalidate!
|
94
61
|
end
|
95
62
|
|
96
63
|
# Invalidate object with the given key and update content according to the strategy
|
97
64
|
#
|
98
65
|
# @return [Object, NilClass] Stale object or nil when it does not exist
|
99
66
|
def refresh(*args)
|
100
|
-
|
101
|
-
invalidate(*args)
|
102
|
-
value
|
67
|
+
alloc(*args).refresh
|
103
68
|
end
|
104
69
|
|
105
70
|
# Fetch data from resolver and store it into redis
|
106
71
|
#
|
107
72
|
# @return [Object] Return the result from resolver
|
108
73
|
def refresh!(*args)
|
109
|
-
|
74
|
+
alloc(*args).refresh!
|
110
75
|
end
|
111
76
|
|
112
77
|
# Check if a fresh value exist.
|
113
78
|
#
|
114
79
|
# @return [Boolean] True or False according the object existence
|
115
80
|
def exist?(*args)
|
116
|
-
|
81
|
+
alloc(*args).exist?
|
117
82
|
end
|
118
83
|
|
119
84
|
# Check if object with a given key is stale
|
120
85
|
#
|
121
86
|
# @return [NilClass, Boolean] Return nil if the object does not exist or true/false according to the object freshness state
|
122
87
|
def stale?(*args)
|
123
|
-
|
88
|
+
alloc(*args).stale?
|
124
89
|
end
|
125
90
|
|
126
91
|
# Check if a fresh object exists into the storage
|
127
92
|
#
|
128
93
|
# @return [Boolean] true/false according to the object existence and freshness
|
129
94
|
def fresh?(*args)
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
private
|
134
|
-
|
135
|
-
def async?
|
136
|
-
false
|
137
|
-
end
|
138
|
-
|
139
|
-
def perform_async(*args)
|
140
|
-
# @TODO Enqueue a real background job here. It's only working on sync mode
|
141
|
-
# redis.set(name.head, CTRL[:enqueued])
|
142
|
-
refresh!(*args)
|
95
|
+
alloc(*args).fresh?
|
143
96
|
end
|
144
97
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
redis.multi do |cli|
|
151
|
-
cli.set(naming.body, json)
|
152
|
-
cli.set(naming.head, CTRL[:executed])
|
153
|
-
cli.expire(naming.head, expires_in) if expires_in
|
154
|
-
end
|
155
|
-
AsyncStorage::JSON.load(json)
|
156
|
-
end
|
157
|
-
|
158
|
-
def read(redis, key)
|
159
|
-
return unless key
|
160
|
-
|
161
|
-
raw = redis.get(key)
|
162
|
-
return unless raw
|
163
|
-
|
164
|
-
AsyncStorage::JSON.load(raw)
|
98
|
+
# Build an Allocator instance
|
99
|
+
#
|
100
|
+
# @param [*Array] list of parameters to be forwaded to the resolver#call
|
101
|
+
def alloc(*args)
|
102
|
+
Allocator.new(self, *args)
|
165
103
|
end
|
166
104
|
|
167
105
|
def expires_in
|
168
106
|
@options[:expires_in] || AsyncStorage.config.expires_in
|
169
107
|
end
|
170
108
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
naming = build_naming(*args)
|
175
|
-
AsyncStorage.redis_pool.with { |redis| yield(redis, naming) }
|
109
|
+
def namespace
|
110
|
+
@options[:namespace]
|
176
111
|
end
|
177
112
|
|
178
|
-
|
179
|
-
naming = AsyncStorage::Naming.new(resolver_class, *args)
|
180
|
-
naming.prefix = @options[:namespace] if @options[:namespace]
|
181
|
-
naming
|
182
|
-
end
|
113
|
+
protected
|
183
114
|
|
184
115
|
def validate_resolver_class!(klass)
|
185
116
|
unless klass.is_a?(Class)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async_storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcos G. Zimmermann
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- bin/console
|
58
58
|
- bin/setup
|
59
59
|
- lib/async_storage.rb
|
60
|
+
- lib/async_storage/allocator.rb
|
60
61
|
- lib/async_storage/bath_actions.rb
|
61
62
|
- lib/async_storage/config.rb
|
62
63
|
- lib/async_storage/json.rb
|