wcc-contentful 1.0.7 → 1.1.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/README.md +174 -9
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +7 -7
- data/lib/wcc/contentful/instrumentation.rb +20 -2
- data/lib/wcc/contentful/link_visitor.rb +26 -30
- data/lib/wcc/contentful/middleware/store.rb +2 -1
- data/lib/wcc/contentful/model.rb +5 -128
- data/lib/wcc/contentful/model_api.rb +189 -0
- data/lib/wcc/contentful/model_builder.rb +10 -4
- data/lib/wcc/contentful/model_methods.rb +8 -10
- data/lib/wcc/contentful/model_singleton_methods.rb +8 -8
- data/lib/wcc/contentful/rspec.rb +7 -5
- data/lib/wcc/contentful/services.rb +59 -61
- data/lib/wcc/contentful/store/cdn_adapter.rb +7 -1
- data/lib/wcc/contentful/store/factory.rb +2 -6
- data/lib/wcc/contentful/store/memory_store.rb +47 -13
- data/lib/wcc/contentful/store/query.rb +22 -2
- data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +0 -103
- 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/sync_engine.rb +0 -1
- data/lib/wcc/contentful/test/attributes.rb +0 -4
- data/lib/wcc/contentful/test/double.rb +4 -2
- data/lib/wcc/contentful/test/factory.rb +4 -2
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +14 -8
- metadata +117 -37
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module/introspection'
|
4
|
+
|
5
|
+
module WCC::Contentful::ModelAPI
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include WCC::Contentful::Instrumentation
|
10
|
+
|
11
|
+
# override class-level _instrumentation from WCC::Contentful::Instrumentation
|
12
|
+
def self._instrumentation
|
13
|
+
services.instrumentation
|
14
|
+
end
|
15
|
+
|
16
|
+
# Set the registry at the top of the namespace
|
17
|
+
@registry = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
class_methods do
|
21
|
+
attr_reader :configuration
|
22
|
+
|
23
|
+
def configure(configuration = nil, schema: nil, services: nil)
|
24
|
+
configuration ||= @configuration || WCC::Contentful::Configuration.new
|
25
|
+
yield(configuration) if block_given?
|
26
|
+
|
27
|
+
@schema = schema if schema
|
28
|
+
@services = services if services
|
29
|
+
@configuration = configuration.freeze
|
30
|
+
|
31
|
+
WCC::Contentful::ModelBuilder.new(self.schema, namespace: self).build_models
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def services
|
36
|
+
@services ||=
|
37
|
+
# try looking up the class heierarchy
|
38
|
+
(superclass.services if superclass.respond_to?(:services)) ||
|
39
|
+
# create it if we have a configuration
|
40
|
+
WCC::Contentful::Services.new(configuration)
|
41
|
+
end
|
42
|
+
|
43
|
+
def store(preview = nil)
|
44
|
+
ActiveSupport::Deprecation.warn('Use services.store instead')
|
45
|
+
|
46
|
+
preview ? services.preview_store : services.store
|
47
|
+
end
|
48
|
+
|
49
|
+
def schema
|
50
|
+
return @schema if @schema
|
51
|
+
|
52
|
+
file = configuration.schema_file
|
53
|
+
schema_json = JSON.parse(File.read(file))['contentTypes']
|
54
|
+
unless schema_json
|
55
|
+
raise ArgumentError, 'Please give either a JSON array of content types or file to load from'
|
56
|
+
end
|
57
|
+
|
58
|
+
@schema = WCC::Contentful::ContentTypeIndexer.from_json_schema(schema_json).types
|
59
|
+
end
|
60
|
+
|
61
|
+
# Finds an Entry or Asset by ID in the configured contentful space
|
62
|
+
# and returns an initialized instance of the appropriate model type.
|
63
|
+
#
|
64
|
+
# Makes use of the {WCC::Contentful::Services#store configured store}
|
65
|
+
# to access the Contentful CDN.
|
66
|
+
def find(id, options: nil)
|
67
|
+
options ||= {}
|
68
|
+
store = options[:preview] ? services.preview_store : services.store
|
69
|
+
raw =
|
70
|
+
_instrumentation.instrument 'find.model.contentful.wcc', id: id, options: options do
|
71
|
+
store.find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
|
72
|
+
end
|
73
|
+
|
74
|
+
new_from_raw(raw, options) if raw.present?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a new initialized instance of the appropriate model type for the
|
78
|
+
# given raw value. The raw value must be the same format as returned from one
|
79
|
+
# of the stores for a given object.
|
80
|
+
def new_from_raw(raw, context = nil)
|
81
|
+
content_type = WCC::Contentful::Helpers.content_type_from_raw(raw)
|
82
|
+
const = resolve_constant(content_type)
|
83
|
+
const.new(raw, context)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Accepts a content type ID as a string and returns the Ruby constant
|
87
|
+
# stored in the registry that represents this content type.
|
88
|
+
def resolve_constant(content_type)
|
89
|
+
raise ArgumentError, 'content_type cannot be nil' unless content_type
|
90
|
+
|
91
|
+
const = _registry[content_type]
|
92
|
+
return const if const
|
93
|
+
|
94
|
+
const_name = WCC::Contentful::Helpers.constant_from_content_type(content_type).to_s
|
95
|
+
# #parent renamed to #module_parent in Rails 6
|
96
|
+
parent = try(:module_parent) || self.parent
|
97
|
+
|
98
|
+
loop do
|
99
|
+
begin
|
100
|
+
# The app may have defined a model and we haven't loaded it yet
|
101
|
+
const = parent.const_missing(const_name)
|
102
|
+
return const if const && const < self
|
103
|
+
rescue NameError => e
|
104
|
+
raise e unless e.message =~ /uninitialized constant (.+::)*#{const_name}$/
|
105
|
+
end
|
106
|
+
|
107
|
+
# const_missing only searches recursively up the module tree in a Rails
|
108
|
+
# context. If we're in a non-Rails app, we have to do that recursion ourselves.
|
109
|
+
# Keep looking upwards until we get to Object.
|
110
|
+
break if parent == Object
|
111
|
+
|
112
|
+
parent = parent.try(:module_parent) || parent.parent
|
113
|
+
end
|
114
|
+
|
115
|
+
# Autoloading couldn't find their model - we'll register our own.
|
116
|
+
const = const_get(
|
117
|
+
WCC::Contentful::Helpers.constant_from_content_type(content_type)
|
118
|
+
)
|
119
|
+
register_for_content_type(content_type, klass: const)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Registers a class constant to be instantiated when resolving an instance
|
123
|
+
# of the given content type. This automatically happens for the first subclass
|
124
|
+
# of a generated model type, example:
|
125
|
+
#
|
126
|
+
# class MyMenu < WCC::Contentful::Model::Menu
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# In the above case, instances of MyMenu will be instantiated whenever a 'menu'
|
130
|
+
# content type is resolved.
|
131
|
+
# The mapping can be made explicit with the optional parameters. Example:
|
132
|
+
#
|
133
|
+
# class MyFoo < WCC::Contentful::Model::Foo
|
134
|
+
# register_for_content_type 'bar' # MyFoo is assumed
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# # in initializers/wcc_contentful.rb
|
138
|
+
# WCC::Contentful::Model.register_for_content_type('bar', klass: MyFoo)
|
139
|
+
def register_for_content_type(content_type = nil, klass: nil)
|
140
|
+
klass ||= self
|
141
|
+
raise ArgumentError, "#{klass} must be a class constant!" unless klass.respond_to?(:new)
|
142
|
+
|
143
|
+
content_type ||= WCC::Contentful::Helpers.content_type_from_constant(klass)
|
144
|
+
|
145
|
+
_registry[content_type] = klass
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the current registry of content type names to constants.
|
149
|
+
def registry
|
150
|
+
return {} unless _registry
|
151
|
+
|
152
|
+
_registry.dup.freeze
|
153
|
+
end
|
154
|
+
|
155
|
+
def reload!
|
156
|
+
registry = self.registry
|
157
|
+
registry.each do |(content_type, klass)|
|
158
|
+
const_name = klass.name
|
159
|
+
begin
|
160
|
+
# the const_name is fully qualified so search from root
|
161
|
+
const = Object.const_missing(const_name)
|
162
|
+
register_for_content_type(content_type, klass: const) if const
|
163
|
+
rescue NameError => e
|
164
|
+
msg = "Error when reloading constant #{const_name} - #{e}"
|
165
|
+
if defined?(Rails) && Rails.logger
|
166
|
+
Rails.logger.error msg
|
167
|
+
else
|
168
|
+
puts msg
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Checks if a content type has already been registered to a class and returns
|
175
|
+
# that class. If nil, the generated WCC::Contentful::Model::{content_type} class
|
176
|
+
# will be resolved for this content type.
|
177
|
+
def registered?(content_type)
|
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
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -7,8 +7,11 @@ module WCC::Contentful
|
|
7
7
|
class ModelBuilder
|
8
8
|
include Helpers
|
9
9
|
|
10
|
-
|
10
|
+
attr_reader :namespace
|
11
|
+
|
12
|
+
def initialize(types, namespace: WCC::Contentful::Model)
|
11
13
|
@types = types
|
14
|
+
@namespace = namespace
|
12
15
|
end
|
13
16
|
|
14
17
|
def build_models
|
@@ -21,12 +24,13 @@ module WCC::Contentful
|
|
21
24
|
|
22
25
|
def build_model(typedef)
|
23
26
|
const = typedef.name
|
24
|
-
|
27
|
+
ns = namespace
|
28
|
+
return ns.const_get(const) if ns.const_defined?(const)
|
25
29
|
|
26
30
|
# TODO: https://github.com/dkubb/ice_nine ?
|
27
31
|
typedef = typedef.deep_dup.freeze
|
28
|
-
|
29
|
-
Class.new(
|
32
|
+
ns.const_set(const,
|
33
|
+
Class.new(namespace) do
|
30
34
|
extend ModelSingletonMethods
|
31
35
|
include ModelMethods
|
32
36
|
include Helpers
|
@@ -50,6 +54,8 @@ module WCC::Contentful
|
|
50
54
|
typedef
|
51
55
|
end
|
52
56
|
|
57
|
+
define_singleton_method(:model_namespace) { ns }
|
58
|
+
|
53
59
|
define_method(:initialize) do |raw, context = nil|
|
54
60
|
ct = content_type_from_raw(raw)
|
55
61
|
if ct != typedef.content_type
|
@@ -5,8 +5,6 @@
|
|
5
5
|
#
|
6
6
|
# @api Model
|
7
7
|
module WCC::Contentful::ModelMethods
|
8
|
-
include WCC::Contentful::Instrumentation
|
9
|
-
|
10
8
|
# The set of options keys that are specific to the Model layer and shouldn't
|
11
9
|
# be passed down to the Store layer.
|
12
10
|
MODEL_LAYER_CONTEXT_KEYS = %i[
|
@@ -40,6 +38,7 @@ module WCC::Contentful::ModelMethods
|
|
40
38
|
|
41
39
|
typedef = self.class.content_type_definition
|
42
40
|
links = fields.select { |f| %i[Asset Link].include?(typedef.fields[f].type) }
|
41
|
+
store = context[:preview] ? self.class.services.preview_store : self.class.services.store
|
43
42
|
|
44
43
|
raw_link_ids =
|
45
44
|
links.map { |field_name| raw.dig('fields', field_name, sys.locale) }
|
@@ -54,12 +53,11 @@ module WCC::Contentful::ModelMethods
|
|
54
53
|
raw =
|
55
54
|
_instrument 'resolve', id: id, depth: depth, backlinks: backlinked_ids do
|
56
55
|
# use include param to do resolution
|
57
|
-
self.class.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
}))
|
56
|
+
store.find_by(content_type: self.class.content_type,
|
57
|
+
filter: { 'sys.id' => id },
|
58
|
+
options: context.except(*MODEL_LAYER_CONTEXT_KEYS).merge!({
|
59
|
+
include: [depth, 10].min
|
60
|
+
}))
|
63
61
|
end
|
64
62
|
unless raw
|
65
63
|
raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}"
|
@@ -170,10 +168,10 @@ module WCC::Contentful::ModelMethods
|
|
170
168
|
if raw.dig('sys', 'type') == 'Link'
|
171
169
|
_instrument 'resolve',
|
172
170
|
id: self.id, depth: depth, backlinks: context[:backlinks]&.map(&:id) do
|
173
|
-
|
171
|
+
self.class.model_namespace.find(id, options: new_context)
|
174
172
|
end
|
175
173
|
else
|
176
|
-
|
174
|
+
self.class.model_namespace.new_from_raw(raw, new_context)
|
177
175
|
end
|
178
176
|
|
179
177
|
m.resolve(depth: depth - 1, context: new_context, **options) if m && depth > 1
|
@@ -12,9 +12,9 @@ module WCC::Contentful::ModelSingletonMethods
|
|
12
12
|
# WCC::Contentful::Model::Page.find(id)
|
13
13
|
def find(id, options: nil)
|
14
14
|
options ||= {}
|
15
|
-
store =
|
15
|
+
store = options[:preview] ? services.preview_store : services.store
|
16
16
|
raw =
|
17
|
-
|
17
|
+
_instrumentation.instrument 'find.model.contentful.wcc',
|
18
18
|
content_type: content_type, id: id, options: options do
|
19
19
|
store.find(id, { hint: type }.merge!(options.except(:preview)))
|
20
20
|
end
|
@@ -34,9 +34,9 @@ module WCC::Contentful::ModelSingletonMethods
|
|
34
34
|
|
35
35
|
filter.transform_keys! { |k| k.to_s.camelize(:lower) } if filter.present?
|
36
36
|
|
37
|
-
store =
|
37
|
+
store = options[:preview] ? services.preview_store : services.store
|
38
38
|
query =
|
39
|
-
|
39
|
+
_instrumentation.instrument 'find_all.model.contentful.wcc',
|
40
40
|
content_type: content_type, filter: filter, options: options do
|
41
41
|
store.find_all(content_type: content_type, options: options.except(:preview))
|
42
42
|
end
|
@@ -56,9 +56,9 @@ module WCC::Contentful::ModelSingletonMethods
|
|
56
56
|
|
57
57
|
filter.transform_keys! { |k| k.to_s.camelize(:lower) } if filter.present?
|
58
58
|
|
59
|
-
store =
|
59
|
+
store = options[:preview] ? services.preview_store : services.store
|
60
60
|
result =
|
61
|
-
|
61
|
+
_instrumentation.instrument 'find_by.model.contentful.wcc',
|
62
62
|
content_type: content_type, filter: filter, options: options do
|
63
63
|
store.find_by(content_type: content_type, filter: filter, options: options.except(:preview))
|
64
64
|
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
|
data/lib/wcc/contentful/rspec.rb
CHANGED
@@ -12,16 +12,18 @@ module WCC::Contentful::RSpec
|
|
12
12
|
# Builds out a fake Contentful entry for the given content type, and then
|
13
13
|
# stubs the Model API to return that content type for `.find` and `.find_by`
|
14
14
|
# query methods.
|
15
|
-
def contentful_stub(
|
16
|
-
const
|
17
|
-
|
15
|
+
def contentful_stub(const, **attrs)
|
16
|
+
unless const.respond_to?(:content_type_definition)
|
17
|
+
const = WCC::Contentful::Model.resolve_constant(const.to_s)
|
18
|
+
end
|
19
|
+
instance = contentful_create(const, **attrs)
|
18
20
|
|
19
21
|
# mimic what's going on inside model_singleton_methods.rb
|
20
22
|
# find, find_by, etc always return a new instance from the same raw
|
21
23
|
allow(WCC::Contentful::Model).to receive(:find)
|
22
24
|
.with(instance.id, any_args) do |_id, keyword_params|
|
23
25
|
options = keyword_params && keyword_params[:options]
|
24
|
-
contentful_create(
|
26
|
+
contentful_create(const, options, raw: instance.raw, **attrs)
|
25
27
|
end
|
26
28
|
allow(const).to receive(:find) { |id, options| WCC::Contentful::Model.find(id, **(options || {})) }
|
27
29
|
|
@@ -31,7 +33,7 @@ module WCC::Contentful::RSpec
|
|
31
33
|
filter = filter&.dup
|
32
34
|
options = filter&.delete(:options) || {}
|
33
35
|
|
34
|
-
contentful_create(
|
36
|
+
contentful_create(const, options, raw: instance.raw, **attrs)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -4,15 +4,16 @@ module WCC::Contentful
|
|
4
4
|
class Services
|
5
5
|
class << self
|
6
6
|
def instance
|
7
|
-
@singleton__instance__ ||=
|
7
|
+
@singleton__instance__ ||= # rubocop:disable Naming/MemoizedInstanceVariableName
|
8
|
+
(new(WCC::Contentful.configuration) if WCC::Contentful.configuration)
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
attr_reader :configuration
|
13
|
+
|
14
|
+
def initialize(configuration)
|
15
|
+
raise ArgumentError, 'Not yet configured!' unless configuration
|
14
16
|
|
15
|
-
def initialize(configuration = nil)
|
16
17
|
@configuration = configuration
|
17
18
|
end
|
18
19
|
|
@@ -33,10 +34,7 @@ module WCC::Contentful
|
|
33
34
|
#
|
34
35
|
# @api Store
|
35
36
|
def store
|
36
|
-
@store ||=
|
37
|
-
ensure_configured do |config|
|
38
|
-
config.store.build(self)
|
39
|
-
end
|
37
|
+
@store ||= configuration.store.build(self)
|
40
38
|
end
|
41
39
|
|
42
40
|
# An instance of {WCC::Contentful::Store::CDNAdapter} which connects to the
|
@@ -45,13 +43,11 @@ module WCC::Contentful
|
|
45
43
|
# @api Store
|
46
44
|
def preview_store
|
47
45
|
@preview_store ||=
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
).build(self)
|
54
|
-
end
|
46
|
+
WCC::Contentful::Store::Factory.new(
|
47
|
+
configuration,
|
48
|
+
:direct,
|
49
|
+
:preview
|
50
|
+
).build(self)
|
55
51
|
end
|
56
52
|
|
57
53
|
# Gets a {WCC::Contentful::SimpleClient::Cdn CDN Client} which provides
|
@@ -60,16 +56,15 @@ module WCC::Contentful
|
|
60
56
|
# @api Client
|
61
57
|
def client
|
62
58
|
@client ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
59
|
+
WCC::Contentful::SimpleClient::Cdn.new(
|
60
|
+
**configuration.connection_options,
|
61
|
+
access_token: configuration.access_token,
|
62
|
+
space: configuration.space,
|
63
|
+
default_locale: configuration.default_locale,
|
64
|
+
connection: configuration.connection,
|
65
|
+
environment: configuration.environment,
|
66
|
+
instrumentation: instrumentation
|
67
|
+
)
|
73
68
|
end
|
74
69
|
|
75
70
|
# Gets a {WCC::Contentful::SimpleClient::Cdn CDN Client} which provides
|
@@ -78,17 +73,16 @@ module WCC::Contentful
|
|
78
73
|
# @api Client
|
79
74
|
def preview_client
|
80
75
|
@preview_client ||=
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
76
|
+
if configuration.preview_token.present?
|
77
|
+
WCC::Contentful::SimpleClient::Preview.new(
|
78
|
+
**configuration.connection_options,
|
79
|
+
preview_token: configuration.preview_token,
|
80
|
+
space: configuration.space,
|
81
|
+
default_locale: configuration.default_locale,
|
82
|
+
connection: configuration.connection,
|
83
|
+
environment: configuration.environment,
|
84
|
+
instrumentation: instrumentation
|
85
|
+
)
|
92
86
|
end
|
93
87
|
end
|
94
88
|
|
@@ -98,17 +92,16 @@ module WCC::Contentful
|
|
98
92
|
# @api Client
|
99
93
|
def management_client
|
100
94
|
@management_client ||=
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
95
|
+
if configuration.management_token.present?
|
96
|
+
WCC::Contentful::SimpleClient::Management.new(
|
97
|
+
**configuration.connection_options,
|
98
|
+
management_token: configuration.management_token,
|
99
|
+
space: configuration.space,
|
100
|
+
default_locale: configuration.default_locale,
|
101
|
+
connection: configuration.connection,
|
102
|
+
environment: configuration.environment,
|
103
|
+
instrumentation: instrumentation
|
104
|
+
)
|
112
105
|
end
|
113
106
|
end
|
114
107
|
|
@@ -132,25 +125,30 @@ module WCC::Contentful
|
|
132
125
|
|
133
126
|
# Gets the configured instrumentation adapter, defaulting to ActiveSupport::Notifications
|
134
127
|
def instrumentation
|
135
|
-
return @instrumentation if @instrumentation
|
136
|
-
return ActiveSupport::Notifications if WCC::Contentful.configuration.nil?
|
137
|
-
|
138
128
|
@instrumentation ||=
|
139
|
-
|
129
|
+
configuration.instrumentation_adapter ||
|
140
130
|
ActiveSupport::Notifications
|
141
131
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
132
|
+
# Allow it to be injected into a store
|
133
|
+
alias_method :_instrumentation, :instrumentation
|
134
|
+
|
135
|
+
##
|
136
|
+
# This method enables simple dependency injection -
|
137
|
+
# If the target has a setter matching the name of one of the services,
|
138
|
+
# set that setter with the value of the service.
|
139
|
+
def inject_into(target, except: [])
|
140
|
+
(WCC::Contentful::SERVICES - except).each do |s|
|
141
|
+
next unless target.respond_to?("#{s}=")
|
142
|
+
|
143
|
+
target.public_send("#{s}=",
|
144
|
+
public_send(s))
|
145
|
+
end
|
149
146
|
end
|
150
147
|
end
|
151
148
|
|
152
|
-
SERVICES =
|
153
|
-
|
149
|
+
SERVICES =
|
150
|
+
WCC::Contentful::Services.instance_methods(false)
|
151
|
+
.select { |m| WCC::Contentful::Services.instance_method(m).arity == 0 }
|
154
152
|
|
155
153
|
# Include this module to define accessors for every method defined on the
|
156
154
|
# {Services} singleton.
|
@@ -105,6 +105,11 @@ module WCC::Contentful::Store
|
|
105
105
|
|
106
106
|
def apply_operator(operator, field, expected, context = nil)
|
107
107
|
op = operator == :eq ? nil : operator
|
108
|
+
if expected.is_a?(Array)
|
109
|
+
expected = expected.join(',')
|
110
|
+
op = :in if op.nil?
|
111
|
+
end
|
112
|
+
|
108
113
|
param = parameter(field, operator: op, context: context, locale: true)
|
109
114
|
|
110
115
|
self.class.new(
|
@@ -158,7 +163,8 @@ module WCC::Contentful::Store
|
|
158
163
|
def resolve_includes(entry, depth)
|
159
164
|
return entry unless entry && depth && depth > 0
|
160
165
|
|
161
|
-
|
166
|
+
# Dig links out of response.includes and insert them into the entry
|
167
|
+
WCC::Contentful::LinkVisitor.new(entry, :Link, depth: depth - 1).map! do |val|
|
162
168
|
resolve_link(val)
|
163
169
|
end
|
164
170
|
end
|
@@ -75,6 +75,7 @@ module WCC::Contentful::Store
|
|
75
75
|
middleware, params, configure_proc = middleware_config
|
76
76
|
middleware_options = options.merge((params || []).extract_options!)
|
77
77
|
middleware = middleware.call(memo, *params, **middleware_options)
|
78
|
+
services.inject_into(middleware, except: %i[store preview_store])
|
78
79
|
middleware&.instance_exec(&configure_proc) if configure_proc
|
79
80
|
middleware || memo
|
80
81
|
end
|
@@ -154,12 +155,7 @@ module WCC::Contentful::Store
|
|
154
155
|
end
|
155
156
|
|
156
157
|
# Inject services into the custom store class
|
157
|
-
(
|
158
|
-
next unless store.respond_to?("#{s}=")
|
159
|
-
|
160
|
-
store.public_send("#{s}=",
|
161
|
-
services.public_send(s))
|
162
|
-
end
|
158
|
+
services.inject_into(store, except: %i[store preview_store])
|
163
159
|
|
164
160
|
store
|
165
161
|
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
|