wcc-contentful 1.2.0 → 1.3.1
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/README.md +25 -1
- data/app/controllers/wcc/contentful/webhook_controller.rb +2 -0
- data/lib/tasks/download_schema.rake +1 -1
- data/lib/wcc/contentful/configuration.rb +37 -18
- data/lib/wcc/contentful/downloads_schema.rb +5 -4
- data/lib/wcc/contentful/engine.rb +2 -4
- data/lib/wcc/contentful/event.rb +2 -3
- data/lib/wcc/contentful/exceptions.rb +2 -3
- data/lib/wcc/contentful/indexed_representation.rb +2 -6
- data/lib/wcc/contentful/instrumentation.rb +2 -1
- data/lib/wcc/contentful/link.rb +1 -3
- data/lib/wcc/contentful/link_visitor.rb +2 -4
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +4 -8
- data/lib/wcc/contentful/middleware/store.rb +4 -6
- data/lib/wcc/contentful/model_api.rb +3 -5
- data/lib/wcc/contentful/model_builder.rb +5 -4
- data/lib/wcc/contentful/model_methods.rb +10 -12
- data/lib/wcc/contentful/model_singleton_methods.rb +1 -1
- data/lib/wcc/contentful/rich_text/node.rb +1 -3
- data/lib/wcc/contentful/rich_text.rb +1 -1
- data/lib/wcc/contentful/rspec.rb +1 -3
- data/lib/wcc/contentful/services.rb +9 -0
- data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
- data/lib/wcc/contentful/simple_client/preview.rb +17 -0
- data/lib/wcc/contentful/simple_client/response.rb +24 -19
- data/lib/wcc/contentful/simple_client.rb +13 -118
- data/lib/wcc/contentful/store/base.rb +19 -27
- data/lib/wcc/contentful/store/cdn_adapter.rb +1 -1
- data/lib/wcc/contentful/store/factory.rb +1 -1
- data/lib/wcc/contentful/store/memory_store.rb +10 -7
- data/lib/wcc/contentful/store/postgres_store.rb +52 -42
- data/lib/wcc/contentful/store/query.rb +2 -2
- data/lib/wcc/contentful/store/rspec_examples/include_param.rb +6 -4
- data/lib/wcc/contentful/store/rspec_examples/operators.rb +1 -1
- data/lib/wcc/contentful/sync_engine.rb +58 -34
- data/lib/wcc/contentful/sys.rb +2 -1
- data/lib/wcc/contentful/test/double.rb +3 -5
- data/lib/wcc/contentful/test/factory.rb +4 -6
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +9 -12
- data/wcc-contentful.gemspec +5 -5
- metadata +37 -30
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
|
4
|
+
# JSON responses. It exposes methods to query entries, assets, and content_types.
|
5
|
+
# The responses are instances of WCC::Contentful::SimpleClient::Response
|
6
|
+
# which handles paging automatically.
|
7
|
+
#
|
8
|
+
# @api Client
|
9
|
+
class WCC::Contentful::SimpleClient::Cdn < WCC::Contentful::SimpleClient
|
10
|
+
def initialize(space:, access_token:, **options)
|
11
|
+
super(
|
12
|
+
api_url: options[:api_url] || 'https://cdn.contentful.com/',
|
13
|
+
space: space,
|
14
|
+
access_token: access_token,
|
15
|
+
**options
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def client_type
|
20
|
+
'cdn'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Gets an entry by ID
|
24
|
+
def entry(key, query = {})
|
25
|
+
resp =
|
26
|
+
_instrument 'entries', id: key, type: 'Entry', query: query do
|
27
|
+
get("entries/#{key}", query)
|
28
|
+
end
|
29
|
+
resp.assert_ok!
|
30
|
+
end
|
31
|
+
|
32
|
+
# Queries entries with optional query parameters
|
33
|
+
def entries(query = {})
|
34
|
+
resp =
|
35
|
+
_instrument 'entries', type: 'Entry', query: query do
|
36
|
+
get('entries', query)
|
37
|
+
end
|
38
|
+
resp.assert_ok!
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gets an asset by ID
|
42
|
+
def asset(key, query = {})
|
43
|
+
resp =
|
44
|
+
_instrument 'entries', type: 'Asset', id: key, query: query do
|
45
|
+
get("assets/#{key}", query)
|
46
|
+
end
|
47
|
+
resp.assert_ok!
|
48
|
+
end
|
49
|
+
|
50
|
+
# Queries assets with optional query parameters
|
51
|
+
def assets(query = {})
|
52
|
+
resp =
|
53
|
+
_instrument 'entries', type: 'Asset', query: query do
|
54
|
+
get('assets', query)
|
55
|
+
end
|
56
|
+
resp.assert_ok!
|
57
|
+
end
|
58
|
+
|
59
|
+
# Queries content types with optional query parameters
|
60
|
+
def content_types(query = {})
|
61
|
+
resp =
|
62
|
+
_instrument 'content_types', query: query do
|
63
|
+
get('content_types', query)
|
64
|
+
end
|
65
|
+
resp.assert_ok!
|
66
|
+
end
|
67
|
+
|
68
|
+
# Accesses the Sync API to get a list of items that have changed since
|
69
|
+
# the last sync. Accepts a block that receives each changed item, and returns
|
70
|
+
# the next sync token.
|
71
|
+
#
|
72
|
+
# If `sync_token` is nil, an initial sync is performed.
|
73
|
+
#
|
74
|
+
# @return String the next sync token parsed from nextSyncUrl
|
75
|
+
# @example
|
76
|
+
# my_sync_token = storage.get('sync_token')
|
77
|
+
# my_sync_token = client.sync(sync_token: my_sync_token) do |item|
|
78
|
+
# storage.put(item.dig('sys', 'id'), item) }
|
79
|
+
# end
|
80
|
+
# storage.put('sync_token', my_sync_token)
|
81
|
+
def sync(sync_token: nil, **query, &block)
|
82
|
+
return sync_old(sync_token: sync_token, **query) unless block_given?
|
83
|
+
|
84
|
+
sync_token =
|
85
|
+
if sync_token
|
86
|
+
{ sync_token: sync_token }
|
87
|
+
else
|
88
|
+
{ initial: true }
|
89
|
+
end
|
90
|
+
query = query.merge(sync_token)
|
91
|
+
|
92
|
+
_instrument 'sync', sync_token: sync_token, query: query do
|
93
|
+
resp = get('sync', query)
|
94
|
+
resp = SyncResponse.new(resp)
|
95
|
+
resp.assert_ok!
|
96
|
+
|
97
|
+
resp.each_page do |page|
|
98
|
+
page.page_items.each(&block)
|
99
|
+
sync_token = resp.next_sync_token
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
sync_token
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def sync_old(sync_token: nil, **query)
|
109
|
+
ActiveSupport::Deprecation.warn('Sync without a block is deprecated, please use new block syntax instead')
|
110
|
+
|
111
|
+
sync_token =
|
112
|
+
if sync_token
|
113
|
+
{ sync_token: sync_token }
|
114
|
+
else
|
115
|
+
{ initial: true }
|
116
|
+
end
|
117
|
+
query = query.merge(sync_token)
|
118
|
+
|
119
|
+
resp =
|
120
|
+
_instrument 'sync', sync_token: sync_token, query: query do
|
121
|
+
get('sync', query)
|
122
|
+
end
|
123
|
+
resp = SyncResponse.new(resp, memoize: true)
|
124
|
+
resp.assert_ok!
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api Client
|
4
|
+
class WCC::Contentful::SimpleClient::Preview < WCC::Contentful::SimpleClient::Cdn
|
5
|
+
def initialize(space:, preview_token:, **options)
|
6
|
+
super(
|
7
|
+
**options,
|
8
|
+
api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
|
9
|
+
space: space,
|
10
|
+
access_token: preview_token
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def client_type
|
15
|
+
'preview'
|
16
|
+
end
|
17
|
+
end
|
@@ -6,9 +6,7 @@ class WCC::Contentful::SimpleClient
|
|
6
6
|
class Response
|
7
7
|
include ::WCC::Contentful::Instrumentation
|
8
8
|
|
9
|
-
attr_reader :raw_response
|
10
|
-
attr_reader :client
|
11
|
-
attr_reader :request
|
9
|
+
attr_reader :raw_response, :client, :request
|
12
10
|
|
13
11
|
delegate :status, to: :raw_response
|
14
12
|
alias_method :code, :status
|
@@ -26,7 +24,7 @@ class WCC::Contentful::SimpleClient
|
|
26
24
|
def error_message
|
27
25
|
parsed_message =
|
28
26
|
begin
|
29
|
-
raw
|
27
|
+
raw['message']
|
30
28
|
rescue JSON::ParserError
|
31
29
|
nil
|
32
30
|
end
|
@@ -105,15 +103,16 @@ class WCC::Contentful::SimpleClient
|
|
105
103
|
|
106
104
|
def includes
|
107
105
|
@includes ||=
|
108
|
-
raw
|
106
|
+
raw['includes']&.each_with_object({}) do |(_t, entries), h|
|
109
107
|
entries.each { |e| h[e.dig('sys', 'id')] = e }
|
110
108
|
end || {}
|
111
109
|
end
|
112
110
|
end
|
113
111
|
|
114
112
|
class SyncResponse < Response
|
115
|
-
def initialize(response)
|
113
|
+
def initialize(response, memoize: false)
|
116
114
|
super(response.client, response.request, response.raw_response)
|
115
|
+
@memoize = memoize
|
117
116
|
end
|
118
117
|
|
119
118
|
def next_page?
|
@@ -122,6 +121,7 @@ class WCC::Contentful::SimpleClient
|
|
122
121
|
|
123
122
|
def next_page
|
124
123
|
return unless next_page?
|
124
|
+
return @next_page if @next_page
|
125
125
|
|
126
126
|
url = raw['nextPageUrl']
|
127
127
|
next_page =
|
@@ -131,26 +131,31 @@ class WCC::Contentful::SimpleClient
|
|
131
131
|
|
132
132
|
next_page = SyncResponse.new(next_page)
|
133
133
|
next_page.assert_ok!
|
134
|
+
@next_page = next_page if @memoize
|
135
|
+
next_page
|
134
136
|
end
|
135
137
|
|
136
138
|
def next_sync_token
|
137
|
-
# If we
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
raw['nextPageUrl'] || raw['nextSyncUrl']
|
142
|
-
)
|
143
|
-
end
|
144
|
-
|
145
|
-
def each_page
|
146
|
-
raise ArgumentError, 'Not a collection response' unless page_items
|
139
|
+
# If we have iterated some pages, return the sync token of the final
|
140
|
+
# page that was iterated. Do this without maintaining a reference to
|
141
|
+
# all the pages.
|
142
|
+
return @last_sync_token if @last_sync_token
|
147
143
|
|
148
|
-
|
144
|
+
SyncResponse.parse_sync_token(raw['nextPageUrl'] || raw['nextSyncUrl'])
|
145
|
+
end
|
149
146
|
|
147
|
+
def each_page(&block)
|
150
148
|
if block_given?
|
151
|
-
|
149
|
+
super do |page|
|
150
|
+
@last_sync_token = page.next_sync_token
|
151
|
+
|
152
|
+
yield page
|
153
|
+
end
|
152
154
|
else
|
153
|
-
|
155
|
+
super.map do |page|
|
156
|
+
@last_sync_token = page.next_sync_token
|
157
|
+
page
|
158
|
+
end
|
154
159
|
end
|
155
160
|
end
|
156
161
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require_relative 'simple_client/response'
|
4
4
|
require_relative 'simple_client/management'
|
5
|
+
require_relative 'simple_client/cdn'
|
6
|
+
require_relative 'simple_client/preview'
|
5
7
|
require_relative 'instrumentation'
|
6
8
|
|
7
9
|
module WCC::Contentful
|
@@ -24,8 +26,7 @@ module WCC::Contentful
|
|
24
26
|
class SimpleClient
|
25
27
|
include WCC::Contentful::Instrumentation
|
26
28
|
|
27
|
-
attr_reader :api_url
|
28
|
-
attr_reader :space
|
29
|
+
attr_reader :api_url, :space
|
29
30
|
|
30
31
|
# Creates a new SimpleClient with the given configuration.
|
31
32
|
#
|
@@ -41,7 +42,7 @@ module WCC::Contentful
|
|
41
42
|
# @option options [Number] rate_limit_wait_timeout The maximum time to block the thread waiting
|
42
43
|
# on a rate limit response. By default will wait for one 429 and then fail on the second 429.
|
43
44
|
def initialize(api_url:, space:, access_token:, **options)
|
44
|
-
@api_url = URI.join(api_url, '/spaces/', space
|
45
|
+
@api_url = URI.join(api_url, '/spaces/', "#{space}/")
|
45
46
|
@space = space
|
46
47
|
@access_token = access_token
|
47
48
|
|
@@ -57,7 +58,7 @@ module WCC::Contentful
|
|
57
58
|
|
58
59
|
return unless options[:environment].present?
|
59
60
|
|
60
|
-
@api_url = URI.join(@api_url, 'environments/', options[:environment]
|
61
|
+
@api_url = URI.join(@api_url, 'environments/', "#{options[:environment]}/")
|
61
62
|
end
|
62
63
|
|
63
64
|
# performs an HTTP GET request to the specified path within the configured
|
@@ -84,15 +85,13 @@ module WCC::Contentful
|
|
84
85
|
case adapter
|
85
86
|
when nil
|
86
87
|
ADAPTERS.each do |a, spec|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
next
|
92
|
-
end
|
88
|
+
gem(*spec)
|
89
|
+
return load_adapter(a)
|
90
|
+
rescue Gem::LoadError
|
91
|
+
next
|
93
92
|
end
|
94
|
-
raise ArgumentError, 'Unable to load adapter! Please install one of '\
|
95
|
-
|
93
|
+
raise ArgumentError, 'Unable to load adapter! Please install one of ' \
|
94
|
+
"#{ADAPTERS.values.map(&:join).join(',')}"
|
96
95
|
when :faraday
|
97
96
|
require 'faraday'
|
98
97
|
::Faraday.new do |faraday|
|
@@ -104,8 +103,8 @@ module WCC::Contentful
|
|
104
103
|
TyphoeusAdapter.new
|
105
104
|
else
|
106
105
|
unless adapter.respond_to?(:get)
|
107
|
-
raise ArgumentError, "Adapter #{adapter} is not invokeable! Please "\
|
108
|
-
|
106
|
+
raise ArgumentError, "Adapter #{adapter} is not invokeable! Please " \
|
107
|
+
"pass use one of #{ADAPTERS.keys} or create a Faraday-compatible adapter"
|
109
108
|
end
|
110
109
|
adapter
|
111
110
|
end
|
@@ -149,109 +148,5 @@ module WCC::Contentful
|
|
149
148
|
return resp
|
150
149
|
end
|
151
150
|
end
|
152
|
-
|
153
|
-
# The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
|
154
|
-
# JSON responses. It exposes methods to query entries, assets, and content_types.
|
155
|
-
# The responses are instances of WCC::Contentful::SimpleClient::Response
|
156
|
-
# which handles paging automatically.
|
157
|
-
#
|
158
|
-
# @api Client
|
159
|
-
class Cdn < SimpleClient
|
160
|
-
def initialize(space:, access_token:, **options)
|
161
|
-
super(
|
162
|
-
api_url: options[:api_url] || 'https://cdn.contentful.com/',
|
163
|
-
space: space,
|
164
|
-
access_token: access_token,
|
165
|
-
**options
|
166
|
-
)
|
167
|
-
end
|
168
|
-
|
169
|
-
def client_type
|
170
|
-
'cdn'
|
171
|
-
end
|
172
|
-
|
173
|
-
# Gets an entry by ID
|
174
|
-
def entry(key, query = {})
|
175
|
-
resp =
|
176
|
-
_instrument 'entries', id: key, type: 'Entry', query: query do
|
177
|
-
get("entries/#{key}", query)
|
178
|
-
end
|
179
|
-
resp.assert_ok!
|
180
|
-
end
|
181
|
-
|
182
|
-
# Queries entries with optional query parameters
|
183
|
-
def entries(query = {})
|
184
|
-
resp =
|
185
|
-
_instrument 'entries', type: 'Entry', query: query do
|
186
|
-
get('entries', query)
|
187
|
-
end
|
188
|
-
resp.assert_ok!
|
189
|
-
end
|
190
|
-
|
191
|
-
# Gets an asset by ID
|
192
|
-
def asset(key, query = {})
|
193
|
-
resp =
|
194
|
-
_instrument 'entries', type: 'Asset', id: key, query: query do
|
195
|
-
get("assets/#{key}", query)
|
196
|
-
end
|
197
|
-
resp.assert_ok!
|
198
|
-
end
|
199
|
-
|
200
|
-
# Queries assets with optional query parameters
|
201
|
-
def assets(query = {})
|
202
|
-
resp =
|
203
|
-
_instrument 'entries', type: 'Asset', query: query do
|
204
|
-
get('assets', query)
|
205
|
-
end
|
206
|
-
resp.assert_ok!
|
207
|
-
end
|
208
|
-
|
209
|
-
# Queries content types with optional query parameters
|
210
|
-
def content_types(query = {})
|
211
|
-
resp =
|
212
|
-
_instrument 'content_types', query: query do
|
213
|
-
get('content_types', query)
|
214
|
-
end
|
215
|
-
resp.assert_ok!
|
216
|
-
end
|
217
|
-
|
218
|
-
# Accesses the Sync API to get a list of items that have changed since
|
219
|
-
# the last sync.
|
220
|
-
#
|
221
|
-
# If `sync_token` is nil, an initial sync is performed.
|
222
|
-
# Returns a WCC::Contentful::SimpleClient::SyncResponse
|
223
|
-
# which handles paging automatically.
|
224
|
-
def sync(sync_token: nil, **query)
|
225
|
-
sync_token =
|
226
|
-
if sync_token
|
227
|
-
{ sync_token: sync_token }
|
228
|
-
else
|
229
|
-
{ initial: true }
|
230
|
-
end
|
231
|
-
query = query.merge(sync_token)
|
232
|
-
resp =
|
233
|
-
_instrument 'sync', sync_token: sync_token, query: query do
|
234
|
-
get('sync', query)
|
235
|
-
end
|
236
|
-
resp = SyncResponse.new(resp)
|
237
|
-
resp.assert_ok!
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# @api Client
|
242
|
-
class Preview < Cdn
|
243
|
-
def initialize(space:, preview_token:, **options)
|
244
|
-
super(
|
245
|
-
**options,
|
246
|
-
api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
|
247
|
-
space: space,
|
248
|
-
access_token: preview_token
|
249
|
-
)
|
250
|
-
end
|
251
|
-
|
252
|
-
def client_type
|
253
|
-
'preview'
|
254
|
-
end
|
255
|
-
end
|
256
151
|
end
|
257
152
|
end
|
@@ -50,30 +50,30 @@ module WCC::Contentful::Store
|
|
50
50
|
# implementation calls into #set and #delete to perform the appropriate
|
51
51
|
# operations in the store.
|
52
52
|
def index(json)
|
53
|
+
# This implementation assumes that #delete and #set are individually thread-safe.
|
54
|
+
# No mutex is needed so long as the revisions are accurate.
|
53
55
|
# Subclasses can override to do this in a more performant thread-safe way.
|
54
56
|
# Example: postgres_store could do this in a stored procedure for speed
|
55
|
-
|
56
|
-
|
57
|
-
case type = json.dig('sys', 'type')
|
58
|
-
when 'DeletedEntry', 'DeletedAsset'
|
59
|
-
delete(json.dig('sys', 'id'))
|
60
|
-
else
|
61
|
-
set(json.dig('sys', 'id'), json)
|
62
|
-
end
|
63
|
-
|
64
|
-
if (prev_rev = prev&.dig('sys', 'revision')) && (next_rev = json.dig('sys', 'revision'))
|
65
|
-
if next_rev < prev_rev
|
66
|
-
# Uh oh! we overwrote an entry with a prior revision. Put the previous back.
|
67
|
-
return index(prev)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
case type
|
57
|
+
prev =
|
58
|
+
case type = json.dig('sys', 'type')
|
72
59
|
when 'DeletedEntry', 'DeletedAsset'
|
73
|
-
|
60
|
+
delete(json.dig('sys', 'id'))
|
74
61
|
else
|
75
|
-
json
|
62
|
+
set(json.dig('sys', 'id'), json)
|
76
63
|
end
|
64
|
+
|
65
|
+
if (prev_rev = prev&.dig('sys', 'revision')) &&
|
66
|
+
(next_rev = json.dig('sys', 'revision')) &&
|
67
|
+
(next_rev < prev_rev)
|
68
|
+
# Uh oh! we overwrote an entry with a prior revision. Put the previous back.
|
69
|
+
return index(prev)
|
70
|
+
end
|
71
|
+
|
72
|
+
case type
|
73
|
+
when 'DeletedEntry', 'DeletedAsset'
|
74
|
+
nil
|
75
|
+
else
|
76
|
+
json
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -107,17 +107,9 @@ module WCC::Contentful::Store
|
|
107
107
|
)
|
108
108
|
end
|
109
109
|
|
110
|
-
def initialize
|
111
|
-
@mutex = Concurrent::ReentrantReadWriteLock.new
|
112
|
-
end
|
113
|
-
|
114
110
|
def ensure_hash(val)
|
115
111
|
raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
|
116
112
|
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
attr_reader :mutex
|
121
113
|
end
|
122
114
|
end
|
123
115
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module WCC::Contentful::Store
|
4
4
|
class CDNAdapter
|
5
5
|
include WCC::Contentful::Store::Interface
|
6
|
-
#
|
6
|
+
# NOTE: CDNAdapter should not instrument store events cause it's not a store.
|
7
7
|
|
8
8
|
attr_writer :client, :preview_client
|
9
9
|
|
@@ -9,13 +9,15 @@ module WCC::Contentful::Store
|
|
9
9
|
class MemoryStore < Base
|
10
10
|
def initialize
|
11
11
|
super
|
12
|
+
|
13
|
+
@mutex = Concurrent::ReentrantReadWriteLock.new
|
12
14
|
@hash = {}
|
13
15
|
end
|
14
16
|
|
15
17
|
def set(key, value)
|
16
18
|
value = value.deep_dup.freeze
|
17
19
|
ensure_hash value
|
18
|
-
mutex.with_write_lock do
|
20
|
+
@mutex.with_write_lock do
|
19
21
|
old = @hash[key]
|
20
22
|
@hash[key] = value
|
21
23
|
old
|
@@ -23,17 +25,17 @@ module WCC::Contentful::Store
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def delete(key)
|
26
|
-
mutex.with_write_lock do
|
28
|
+
@mutex.with_write_lock do
|
27
29
|
@hash.delete(key)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
33
|
def keys
|
32
|
-
mutex.with_read_lock { @hash.keys }
|
34
|
+
@mutex.with_read_lock { @hash.keys }
|
33
35
|
end
|
34
36
|
|
35
37
|
def find(key, **_options)
|
36
|
-
mutex.with_read_lock do
|
38
|
+
@mutex.with_read_lock do
|
37
39
|
@hash[key]
|
38
40
|
end
|
39
41
|
end
|
@@ -41,11 +43,12 @@ module WCC::Contentful::Store
|
|
41
43
|
SUPPORTED_OPS = %i[eq ne in nin].freeze
|
42
44
|
|
43
45
|
def execute(query)
|
44
|
-
(query.conditions.map(&:op) - SUPPORTED_OPS).
|
45
|
-
raise ArgumentError, "Operator :#{
|
46
|
+
if bad_op = (query.conditions.map(&:op) - SUPPORTED_OPS).first
|
47
|
+
raise ArgumentError, "Operator :#{bad_op} not supported"
|
46
48
|
end
|
47
49
|
|
48
|
-
|
50
|
+
# Since @hash.values returns a new array, we only need to lock here
|
51
|
+
relation = @mutex.with_read_lock { @hash.values }
|
49
52
|
|
50
53
|
# relation is an enumerable that we apply conditions to in the form of
|
51
54
|
# Enumerable#select and Enumerable#reject.
|