async_storage 0.0.1 → 0.0.2
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 +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
|