wcc-contentful 1.1.0 → 1.2.0
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/app/jobs/wcc/contentful/webhook_enable_job.rb +8 -8
- data/lib/wcc/contentful/active_record_shim.rb +2 -2
- data/lib/wcc/contentful/content_type_indexer.rb +2 -0
- data/lib/wcc/contentful/event.rb +2 -8
- data/lib/wcc/contentful/indexed_representation.rb +1 -0
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +1 -1
- data/lib/wcc/contentful/model.rb +0 -6
- data/lib/wcc/contentful/model_api.rb +16 -9
- data/lib/wcc/contentful/model_builder.rb +3 -0
- data/lib/wcc/contentful/model_singleton_methods.rb +3 -3
- data/lib/wcc/contentful/rich_text/node.rb +62 -0
- data/lib/wcc/contentful/rich_text.rb +105 -0
- data/lib/wcc/contentful/simple_client/response.rb +25 -29
- data/lib/wcc/contentful/store/cdn_adapter.rb +15 -6
- data/lib/wcc/contentful/store/memory_store.rb +47 -13
- data/lib/wcc/contentful/store/query.rb +21 -1
- data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +0 -85
- data/lib/wcc/contentful/store/rspec_examples/operators/eq.rb +92 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/in.rb +131 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/ne.rb +77 -0
- data/lib/wcc/contentful/store/rspec_examples/operators/nin.rb +80 -0
- data/lib/wcc/contentful/store/rspec_examples/operators.rb +50 -0
- data/lib/wcc/contentful/store/rspec_examples.rb +2 -0
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +1 -1
- data/wcc-contentful.gemspec +1 -1
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f05ea585d54f7ca83c96e018021f88776ebb148cc40b7d719d6199b3a3cd9d0c
|
4
|
+
data.tar.gz: 0da490d12beb9938ec51c0ac40e140420966667b72afebc5c9895cf197308ea8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 979360a35356b041dbe0f8bbd4f0e5bfcaf91909ccc567322ceef8493de25300ea09a081e755142fe23bf9cfd5ea86ca7f919d3218d5dec44a98bcab5921af7d
|
7
|
+
data.tar.gz: 9e5f29e70fdcbccffd72aeb8b896e3518d2236cd699704289e8a3fdf1392201dc49671bab0eb83948e79ee079366de90b46add4ce2027e065db2da28b2be4e85
|
@@ -11,20 +11,19 @@ module WCC::Contentful
|
|
11
11
|
args = default_configuration.merge!(args)
|
12
12
|
|
13
13
|
client = WCC::Contentful::SimpleClient::Management.new(
|
14
|
-
args
|
14
|
+
**args
|
15
15
|
)
|
16
|
-
enable_webhook(client, args.slice(:
|
16
|
+
enable_webhook(client, args.slice(:receive_url, :webhook_username, :webhook_password))
|
17
17
|
end
|
18
18
|
|
19
|
-
def enable_webhook(client,
|
20
|
-
|
21
|
-
webhook = client.webhook_definitions.items.find { |w| w['url'] == expected_url }
|
19
|
+
def enable_webhook(client, receive_url:, webhook_username: nil, webhook_password: nil)
|
20
|
+
webhook = client.webhook_definitions.items.find { |w| w['url'] == receive_url }
|
22
21
|
logger.debug "existing webhook: #{webhook.inspect}" if webhook
|
23
22
|
return if webhook
|
24
23
|
|
25
24
|
body = {
|
26
25
|
'name' => 'WCC::Contentful webhook',
|
27
|
-
'url' =>
|
26
|
+
'url' => receive_url,
|
28
27
|
'topics' => [
|
29
28
|
'*.publish',
|
30
29
|
'*.unpublish'
|
@@ -50,13 +49,14 @@ module WCC::Contentful
|
|
50
49
|
|
51
50
|
{
|
52
51
|
management_token: config.management_token,
|
53
|
-
app_url: config.app_url,
|
54
52
|
space: config.space,
|
55
53
|
environment: config.environment,
|
56
54
|
default_locale: config.default_locale,
|
57
55
|
connection: config.connection,
|
58
56
|
webhook_username: config.webhook_username,
|
59
|
-
webhook_password: config.webhook_password
|
57
|
+
webhook_password: config.webhook_password,
|
58
|
+
|
59
|
+
receive_url: URI.join(config.app_url, 'webhook/receive').to_s
|
60
60
|
}
|
61
61
|
end
|
62
62
|
|
@@ -64,13 +64,13 @@ module WCC::Contentful::ActiveRecordShim
|
|
64
64
|
}
|
65
65
|
}
|
66
66
|
|
67
|
-
find_all(filter).each_slice(batch_size, &block)
|
67
|
+
find_all(**filter).each_slice(batch_size, &block)
|
68
68
|
end
|
69
69
|
|
70
70
|
def where(**conditions)
|
71
71
|
# TODO: return a Query object that implements more of the ActiveRecord query interface
|
72
72
|
# https://guides.rubyonrails.org/active_record_querying.html#conditions
|
73
|
-
find_all(conditions)
|
73
|
+
find_all(**conditions)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
data/lib/wcc/contentful/event.rb
CHANGED
@@ -7,10 +7,10 @@ module WCC::Contentful::Event
|
|
7
7
|
|
8
8
|
# Creates an Event out of a raw value received by a webhook or given from
|
9
9
|
# the Contentful Sync API.
|
10
|
-
def self.from_raw(raw, context = nil)
|
10
|
+
def self.from_raw(raw, context = nil, source: nil)
|
11
11
|
const = Registry.instance.get(raw.dig('sys', 'type'))
|
12
12
|
|
13
|
-
const.new(raw, context)
|
13
|
+
const.new(raw, context, source: source)
|
14
14
|
end
|
15
15
|
|
16
16
|
class Registry
|
@@ -134,12 +134,6 @@ class WCC::Contentful::Event::SyncComplete
|
|
134
134
|
include WCC::Contentful::Event
|
135
135
|
|
136
136
|
def initialize(items, context = nil, source: nil)
|
137
|
-
items =
|
138
|
-
items.map do |item|
|
139
|
-
next item if item.is_a? WCC::Contentful::Event
|
140
|
-
|
141
|
-
WCC::Contentful::Event.from_raw(item, context, source: source)
|
142
|
-
end
|
143
137
|
@items = items.freeze
|
144
138
|
@source = source
|
145
139
|
@sys = WCC::Contentful::Sys.new(
|
@@ -20,7 +20,7 @@ module WCC::Contentful::Middleware::Store
|
|
20
20
|
event = 'miss'
|
21
21
|
# if it's not a contentful ID don't hit the API.
|
22
22
|
# Store a nil object if we can't find the object on the CDN.
|
23
|
-
(store.find(key, options) || nil_obj(key)) if key =~ /^\w+$/
|
23
|
+
(store.find(key, **options) || nil_obj(key)) if key =~ /^\w+$/
|
24
24
|
end
|
25
25
|
_instrument(event, key: key, options: options)
|
26
26
|
|
data/lib/wcc/contentful/model.rb
CHANGED
@@ -41,10 +41,6 @@ require_relative './model_api'
|
|
41
41
|
class WCC::Contentful::Model
|
42
42
|
include WCC::Contentful::ModelAPI
|
43
43
|
|
44
|
-
# The Model base class maintains a registry which is best expressed as a
|
45
|
-
# class var.
|
46
|
-
# rubocop:disable Style/ClassVars
|
47
|
-
|
48
44
|
class << self
|
49
45
|
def const_missing(name)
|
50
46
|
type = WCC::Contentful::Helpers.content_type_from_constant(name)
|
@@ -53,5 +49,3 @@ class WCC::Contentful::Model
|
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
56
|
-
|
57
|
-
# rubocop:enable Style/ClassVars
|
@@ -13,9 +13,8 @@ module WCC::Contentful::ModelAPI
|
|
13
13
|
services.instrumentation
|
14
14
|
end
|
15
15
|
|
16
|
-
#
|
17
|
-
|
18
|
-
@@registry = {} # rubocop:disable Style/ClassVars
|
16
|
+
# Set the registry at the top of the namespace
|
17
|
+
@registry = {}
|
19
18
|
end
|
20
19
|
|
21
20
|
class_methods do
|
@@ -69,7 +68,7 @@ module WCC::Contentful::ModelAPI
|
|
69
68
|
store = options[:preview] ? services.preview_store : services.store
|
70
69
|
raw =
|
71
70
|
_instrumentation.instrument 'find.model.contentful.wcc', id: id, options: options do
|
72
|
-
store.find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
|
71
|
+
store.find(id, **options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
|
73
72
|
end
|
74
73
|
|
75
74
|
new_from_raw(raw, options) if raw.present?
|
@@ -89,7 +88,7 @@ module WCC::Contentful::ModelAPI
|
|
89
88
|
def resolve_constant(content_type)
|
90
89
|
raise ArgumentError, 'content_type cannot be nil' unless content_type
|
91
90
|
|
92
|
-
const =
|
91
|
+
const = _registry[content_type]
|
93
92
|
return const if const
|
94
93
|
|
95
94
|
const_name = WCC::Contentful::Helpers.constant_from_content_type(content_type).to_s
|
@@ -143,14 +142,14 @@ module WCC::Contentful::ModelAPI
|
|
143
142
|
|
144
143
|
content_type ||= WCC::Contentful::Helpers.content_type_from_constant(klass)
|
145
144
|
|
146
|
-
|
145
|
+
_registry[content_type] = klass
|
147
146
|
end
|
148
147
|
|
149
148
|
# Returns the current registry of content type names to constants.
|
150
149
|
def registry
|
151
|
-
return {} unless
|
150
|
+
return {} unless _registry
|
152
151
|
|
153
|
-
|
152
|
+
_registry.dup.freeze
|
154
153
|
end
|
155
154
|
|
156
155
|
def reload!
|
@@ -176,7 +175,15 @@ module WCC::Contentful::ModelAPI
|
|
176
175
|
# that class. If nil, the generated WCC::Contentful::Model::{content_type} class
|
177
176
|
# will be resolved for this content type.
|
178
177
|
def registered?(content_type)
|
179
|
-
|
178
|
+
_registry[content_type]
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
def _registry
|
184
|
+
# If calling register_for_content_type in a subclass, look up the superclass
|
185
|
+
# chain until we get to the model namespace which defines the registry
|
186
|
+
@registry || superclass._registry
|
180
187
|
end
|
181
188
|
end
|
182
189
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative './link'
|
4
4
|
require_relative './sys'
|
5
|
+
require_relative './rich_text'
|
5
6
|
|
6
7
|
module WCC::Contentful
|
7
8
|
class ModelBuilder
|
@@ -95,6 +96,8 @@ module WCC::Contentful
|
|
95
96
|
#
|
96
97
|
# when :DateTime
|
97
98
|
# raw_value = Time.parse(raw_value).localtime
|
99
|
+
when :RichText
|
100
|
+
raw_value = WCC::Contentful::RichText.tokenize(raw_value)
|
98
101
|
when :Int
|
99
102
|
raw_value = Integer(raw_value)
|
100
103
|
when :Float
|
@@ -16,7 +16,7 @@ module WCC::Contentful::ModelSingletonMethods
|
|
16
16
|
raw =
|
17
17
|
_instrumentation.instrument 'find.model.contentful.wcc',
|
18
18
|
content_type: content_type, id: id, options: options do
|
19
|
-
store.find(id, { hint: type }.merge!(options.except(:preview)))
|
19
|
+
store.find(id, **{ hint: type }.merge!(options.except(:preview)))
|
20
20
|
end
|
21
21
|
new(raw, options) if raw.present?
|
22
22
|
end
|
@@ -69,9 +69,9 @@ module WCC::Contentful::ModelSingletonMethods
|
|
69
69
|
def inherited(subclass)
|
70
70
|
# If another different class is already registered for this content type,
|
71
71
|
# don't auto-register this one.
|
72
|
-
return if
|
72
|
+
return if model_namespace.registered?(content_type)
|
73
73
|
|
74
|
-
|
74
|
+
model_namespace.register_for_content_type(content_type, klass: subclass)
|
75
75
|
end
|
76
76
|
|
77
77
|
class ModelQuery
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful::RichText
|
4
|
+
module Node
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def keys
|
8
|
+
members.map(&:to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
included do
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
alias_method :node_type, :nodeType
|
15
|
+
|
16
|
+
# Make the nodes read-only
|
17
|
+
undef_method :[]=
|
18
|
+
members.each do |member|
|
19
|
+
undef_method("#{member}=")
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override each so it has a Hash-like interface rather than Struct-like.
|
23
|
+
# The goal being to mimic a JSON-parsed hash representation of the raw
|
24
|
+
def each
|
25
|
+
members.map do |key|
|
26
|
+
tuple = [key.to_s, self.[](key)]
|
27
|
+
yield tuple if block_given?
|
28
|
+
tuple
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class_methods do
|
34
|
+
# Default value for node_type covers most cases
|
35
|
+
def node_type
|
36
|
+
name.demodulize.underscore.dasherize
|
37
|
+
end
|
38
|
+
|
39
|
+
def tokenize(raw, context = nil)
|
40
|
+
unless raw['nodeType'] == node_type
|
41
|
+
raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'"
|
42
|
+
end
|
43
|
+
|
44
|
+
values =
|
45
|
+
members.map do |symbol|
|
46
|
+
val = raw[symbol.to_s]
|
47
|
+
|
48
|
+
case symbol
|
49
|
+
when :content
|
50
|
+
WCC::Contentful::RichText.tokenize(val, context)
|
51
|
+
# when :data
|
52
|
+
# TODO: resolve links...
|
53
|
+
else
|
54
|
+
val
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
new(*values)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './rich_text/node'
|
4
|
+
|
5
|
+
##
|
6
|
+
# This module contains a number of structs representing nodes in a Contentful
|
7
|
+
# rich text field. When the Model layer parses a Rich Text field from
|
8
|
+
# Contentful, it is turned into a WCC::Contentful::RichText::Document node.
|
9
|
+
# The {WCC::Contentful::RichText::Document#content content} method of this
|
10
|
+
# node is an Array containing paragraph, blockquote, entry, and other nodes.
|
11
|
+
#
|
12
|
+
# The various structs in the RichText object model are designed to mimic the
|
13
|
+
# Hash interface, so that the indexing operator `#[]` and the `#dig` method
|
14
|
+
# can be used to traverse the data. The data can also be accessed by the
|
15
|
+
# attribute reader methods defined on the structs. Both of these are considered
|
16
|
+
# part of the public API of the model and will not change.
|
17
|
+
#
|
18
|
+
# In a future release we plan to implement automatic link resolution. When that
|
19
|
+
# happens, the `.data` attribute of embedded entries and assets will return a
|
20
|
+
# new class that is able to resolve the `.target` automatically into a full
|
21
|
+
# entry or asset. This future class will still respect the hash accessor methods
|
22
|
+
# `#[]`, `#dig`, `#keys`, and `#each`, so it is safe to use those.
|
23
|
+
module WCC::Contentful::RichText
|
24
|
+
##
|
25
|
+
# Recursively converts a raw JSON-parsed hash into the RichText object model.
|
26
|
+
def self.tokenize(raw, context = nil)
|
27
|
+
return unless raw
|
28
|
+
return raw.map { |c| tokenize(c, context) } if raw.is_a?(Array)
|
29
|
+
|
30
|
+
klass =
|
31
|
+
case raw['nodeType']
|
32
|
+
when 'document'
|
33
|
+
Document
|
34
|
+
when 'paragraph'
|
35
|
+
Paragraph
|
36
|
+
when 'blockquote'
|
37
|
+
Blockquote
|
38
|
+
when 'text'
|
39
|
+
Text
|
40
|
+
when 'embedded-entry-inline'
|
41
|
+
EmbeddedEntryInline
|
42
|
+
when 'embedded-entry-block'
|
43
|
+
EmbeddedEntryBlock
|
44
|
+
when 'embedded-asset-block'
|
45
|
+
EmbeddedAssetBlock
|
46
|
+
when /heading\-(\d+)/
|
47
|
+
size = Regexp.last_match(1)
|
48
|
+
const_get("Heading#{size}")
|
49
|
+
else
|
50
|
+
Unknown
|
51
|
+
end
|
52
|
+
|
53
|
+
klass.tokenize(raw, context)
|
54
|
+
end
|
55
|
+
|
56
|
+
Document =
|
57
|
+
Struct.new(:nodeType, :data, :content) do
|
58
|
+
include WCC::Contentful::RichText::Node
|
59
|
+
end
|
60
|
+
|
61
|
+
Paragraph =
|
62
|
+
Struct.new(:nodeType, :data, :content) do
|
63
|
+
include WCC::Contentful::RichText::Node
|
64
|
+
end
|
65
|
+
|
66
|
+
Blockquote =
|
67
|
+
Struct.new(:nodeType, :data, :content) do
|
68
|
+
include WCC::Contentful::RichText::Node
|
69
|
+
end
|
70
|
+
|
71
|
+
Text =
|
72
|
+
Struct.new(:nodeType, :value, :marks, :data) do
|
73
|
+
include WCC::Contentful::RichText::Node
|
74
|
+
end
|
75
|
+
|
76
|
+
EmbeddedEntryInline =
|
77
|
+
Struct.new(:nodeType, :data, :content) do
|
78
|
+
include WCC::Contentful::RichText::Node
|
79
|
+
end
|
80
|
+
|
81
|
+
EmbeddedEntryBlock =
|
82
|
+
Struct.new(:nodeType, :data, :content) do
|
83
|
+
include WCC::Contentful::RichText::Node
|
84
|
+
end
|
85
|
+
|
86
|
+
EmbeddedAssetBlock =
|
87
|
+
Struct.new(:nodeType, :data, :content) do
|
88
|
+
include WCC::Contentful::RichText::Node
|
89
|
+
end
|
90
|
+
|
91
|
+
(1..5).each do |i|
|
92
|
+
struct =
|
93
|
+
Struct.new(:nodeType, :data, :content) do
|
94
|
+
include WCC::Contentful::RichText::Node
|
95
|
+
end
|
96
|
+
sz = i
|
97
|
+
struct.define_singleton_method(:node_type) { "heading-#{sz}" }
|
98
|
+
const_set("Heading#{sz}", struct)
|
99
|
+
end
|
100
|
+
|
101
|
+
Unknown =
|
102
|
+
Struct.new(:nodeType, :data, :content) do
|
103
|
+
include WCC::Contentful::RichText::Node
|
104
|
+
end
|
105
|
+
end
|
@@ -49,7 +49,6 @@ class WCC::Contentful::SimpleClient
|
|
49
49
|
|
50
50
|
def next_page
|
51
51
|
return unless next_page?
|
52
|
-
return @next_page if @next_page
|
53
52
|
|
54
53
|
query = (@request[:query] || {}).merge({
|
55
54
|
skip: page_items.length + skip
|
@@ -58,7 +57,7 @@ class WCC::Contentful::SimpleClient
|
|
58
57
|
_instrument 'page', url: @request[:url], query: query do
|
59
58
|
@client.get(@request[:url], query)
|
60
59
|
end
|
61
|
-
|
60
|
+
np.assert_ok!
|
62
61
|
end
|
63
62
|
|
64
63
|
def initialize(client, request, raw_response)
|
@@ -77,16 +76,7 @@ class WCC::Contentful::SimpleClient
|
|
77
76
|
def each_page(&block)
|
78
77
|
raise ArgumentError, 'Not a collection response' unless page_items
|
79
78
|
|
80
|
-
ret =
|
81
|
-
Enumerator.new do |y|
|
82
|
-
y << self
|
83
|
-
|
84
|
-
if next_page?
|
85
|
-
next_page.each_page.each do |page|
|
86
|
-
y << page
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
79
|
+
ret = PaginatingEnumerable.new(self)
|
90
80
|
|
91
81
|
if block_given?
|
92
82
|
ret.map(&block)
|
@@ -118,11 +108,6 @@ class WCC::Contentful::SimpleClient
|
|
118
108
|
raw.dig('includes')&.each_with_object({}) do |(_t, entries), h|
|
119
109
|
entries.each { |e| h[e.dig('sys', 'id')] = e }
|
120
110
|
end || {}
|
121
|
-
|
122
|
-
return @includes unless @next_page
|
123
|
-
|
124
|
-
# This could be more efficient - maybe not worth worrying about
|
125
|
-
@includes.merge(@next_page.includes)
|
126
111
|
end
|
127
112
|
end
|
128
113
|
|
@@ -144,8 +129,8 @@ class WCC::Contentful::SimpleClient
|
|
144
129
|
@client.get(url)
|
145
130
|
end
|
146
131
|
|
147
|
-
|
148
|
-
|
132
|
+
next_page = SyncResponse.new(next_page)
|
133
|
+
next_page.assert_ok!
|
149
134
|
end
|
150
135
|
|
151
136
|
def next_sync_token
|
@@ -160,16 +145,7 @@ class WCC::Contentful::SimpleClient
|
|
160
145
|
def each_page
|
161
146
|
raise ArgumentError, 'Not a collection response' unless page_items
|
162
147
|
|
163
|
-
ret =
|
164
|
-
Enumerator.new do |y|
|
165
|
-
y << self
|
166
|
-
|
167
|
-
if next_page?
|
168
|
-
next_page.each_page.each do |page|
|
169
|
-
y << page
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
148
|
+
ret = PaginatingEnumerable.new(self)
|
173
149
|
|
174
150
|
if block_given?
|
175
151
|
ret.map(&block)
|
@@ -190,6 +166,26 @@ class WCC::Contentful::SimpleClient
|
|
190
166
|
end
|
191
167
|
end
|
192
168
|
|
169
|
+
class PaginatingEnumerable
|
170
|
+
include Enumerable
|
171
|
+
|
172
|
+
def initialize(initial_page)
|
173
|
+
raise ArgumentError, 'Must provide initial page' unless initial_page.present?
|
174
|
+
|
175
|
+
@initial_page = initial_page
|
176
|
+
end
|
177
|
+
|
178
|
+
def each
|
179
|
+
page = @initial_page
|
180
|
+
yield page
|
181
|
+
|
182
|
+
while page.next_page?
|
183
|
+
page = page.next_page
|
184
|
+
yield page
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
193
189
|
class ApiError < StandardError
|
194
190
|
attr_reader :response
|
195
191
|
|
@@ -72,9 +72,13 @@ module WCC::Contentful::Store
|
|
72
72
|
delegate :count, to: :response
|
73
73
|
|
74
74
|
def to_enum
|
75
|
-
return response.
|
75
|
+
return response.each_page.flat_map(&:page_items) unless @options[:include]
|
76
76
|
|
77
|
-
response.
|
77
|
+
response.each_page
|
78
|
+
.flat_map { |page| page.page_items.each_with_object(page).to_a }
|
79
|
+
.map do |e, page|
|
80
|
+
resolve_includes(e, page.includes, depth: @options[:include])
|
81
|
+
end
|
78
82
|
end
|
79
83
|
|
80
84
|
def initialize(store, client:, relation:, options: nil, **extra)
|
@@ -105,6 +109,11 @@ module WCC::Contentful::Store
|
|
105
109
|
|
106
110
|
def apply_operator(operator, field, expected, context = nil)
|
107
111
|
op = operator == :eq ? nil : operator
|
112
|
+
if expected.is_a?(Array)
|
113
|
+
expected = expected.join(',')
|
114
|
+
op = :in if op.nil?
|
115
|
+
end
|
116
|
+
|
108
117
|
param = parameter(field, operator: op, context: context, locale: true)
|
109
118
|
|
110
119
|
self.class.new(
|
@@ -155,18 +164,18 @@ module WCC::Contentful::Store
|
|
155
164
|
end
|
156
165
|
end
|
157
166
|
|
158
|
-
def resolve_includes(entry, depth)
|
167
|
+
def resolve_includes(entry, includes, depth:)
|
159
168
|
return entry unless entry && depth && depth > 0
|
160
169
|
|
161
170
|
# Dig links out of response.includes and insert them into the entry
|
162
171
|
WCC::Contentful::LinkVisitor.new(entry, :Link, depth: depth - 1).map! do |val|
|
163
|
-
resolve_link(val)
|
172
|
+
resolve_link(val, includes)
|
164
173
|
end
|
165
174
|
end
|
166
175
|
|
167
|
-
def resolve_link(val)
|
176
|
+
def resolve_link(val, includes)
|
168
177
|
return val unless val.is_a?(Hash) && val.dig('sys', 'type') == 'Link'
|
169
|
-
return val unless included =
|
178
|
+
return val unless included = includes[val.dig('sys', 'id')]
|
170
179
|
|
171
180
|
included
|
172
181
|
end
|
@@ -38,7 +38,13 @@ module WCC::Contentful::Store
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
SUPPORTED_OPS = %i[eq ne in nin].freeze
|
42
|
+
|
41
43
|
def execute(query)
|
44
|
+
(query.conditions.map(&:op) - SUPPORTED_OPS).each do |op|
|
45
|
+
raise ArgumentError, "Operator :#{op} not supported"
|
46
|
+
end
|
47
|
+
|
42
48
|
relation = mutex.with_read_lock { @hash.values }
|
43
49
|
|
44
50
|
# relation is an enumerable that we apply conditions to in the form of
|
@@ -56,21 +62,49 @@ module WCC::Contentful::Store
|
|
56
62
|
# For each condition, we apply a new Enumerable#select with a block that
|
57
63
|
# enforces the condition.
|
58
64
|
query.conditions.reduce(relation) do |memo, condition|
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
__send__("apply_#{condition.op}", memo, condition)
|
66
|
+
end
|
67
|
+
end
|
62
68
|
|
63
|
-
|
64
|
-
val = entry.dig(*condition.path)
|
69
|
+
private
|
65
70
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def apply_eq(memo, condition)
|
72
|
+
memo.select { |entry| eq?(entry, condition) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def apply_ne(memo, condition)
|
76
|
+
memo.reject { |entry| eq?(entry, condition) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def eq?(entry, condition)
|
80
|
+
# The condition's path tells us where to find the value in the JSON object
|
81
|
+
val = entry.dig(*condition.path)
|
82
|
+
|
83
|
+
# For arrays, equality is defined as does the array include the expected value.
|
84
|
+
# See https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/array-equality-inequality
|
85
|
+
if val.is_a? Array
|
86
|
+
val.include?(condition.expected)
|
87
|
+
else
|
88
|
+
val == condition.expected
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def apply_in(memo, condition)
|
93
|
+
memo.select { |entry| in?(entry, condition) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def apply_nin(memo, condition)
|
97
|
+
memo.reject { |entry| in?(entry, condition) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def in?(entry, condition)
|
101
|
+
val = entry.dig(*condition.path)
|
102
|
+
|
103
|
+
if val.is_a? Array
|
104
|
+
# TODO: detect if in ruby 3.1 and use val.intersect?(condition.expected)
|
105
|
+
val.any? { |item| condition.expected.include?(item) }
|
106
|
+
else
|
107
|
+
condition.expected.include?(val)
|
74
108
|
end
|
75
109
|
end
|
76
110
|
end
|