wcc-contentful 0.3.0 → 1.0.0.pre.rc2
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 +5 -5
- data/.rspec +1 -1
- data/Guardfile +43 -0
- data/README.md +161 -11
- data/Rakefile +3 -6
- data/app/controllers/wcc/contentful/webhook_controller.rb +25 -24
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +36 -2
- data/bin/console +4 -3
- data/bin/rails +2 -0
- data/config/routes.rb +1 -1
- data/doc +1 -0
- data/lib/tasks/download_schema.rake +12 -0
- data/lib/wcc/contentful.rb +69 -45
- data/lib/wcc/contentful/active_record_shim.rb +72 -0
- data/lib/wcc/contentful/configuration.rb +177 -46
- data/lib/wcc/contentful/content_type_indexer.rb +14 -0
- data/lib/wcc/contentful/downloads_schema.rb +112 -0
- data/lib/wcc/contentful/engine.rb +33 -14
- data/lib/wcc/contentful/event.rb +171 -0
- data/lib/wcc/contentful/events.rb +41 -0
- data/lib/wcc/contentful/exceptions.rb +3 -33
- data/lib/wcc/contentful/indexed_representation.rb +2 -2
- data/lib/wcc/contentful/instrumentation.rb +31 -0
- data/lib/wcc/contentful/link.rb +28 -0
- data/lib/wcc/contentful/link_visitor.rb +122 -0
- data/lib/wcc/contentful/middleware.rb +7 -0
- data/lib/wcc/contentful/middleware/store.rb +158 -0
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +114 -0
- data/lib/wcc/contentful/model.rb +37 -4
- data/lib/wcc/contentful/model_builder.rb +1 -0
- data/lib/wcc/contentful/model_methods.rb +40 -15
- data/lib/wcc/contentful/model_singleton_methods.rb +47 -30
- data/lib/wcc/contentful/rake.rb +4 -0
- data/lib/wcc/contentful/rspec.rb +46 -0
- data/lib/wcc/contentful/services.rb +61 -27
- data/lib/wcc/contentful/simple_client.rb +81 -25
- data/lib/wcc/contentful/simple_client/management.rb +43 -10
- data/lib/wcc/contentful/simple_client/response.rb +61 -22
- data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +17 -17
- data/lib/wcc/contentful/store.rb +7 -66
- data/lib/wcc/contentful/store/README.md +85 -0
- data/lib/wcc/contentful/store/base.rb +34 -119
- data/lib/wcc/contentful/store/cdn_adapter.rb +71 -12
- data/lib/wcc/contentful/store/factory.rb +186 -0
- data/lib/wcc/contentful/store/instrumentation.rb +55 -0
- data/lib/wcc/contentful/store/interface.rb +82 -0
- data/lib/wcc/contentful/store/memory_store.rb +27 -24
- data/lib/wcc/contentful/store/postgres_store.rb +268 -101
- data/lib/wcc/contentful/store/postgres_store/schema_1.sql +73 -0
- data/lib/wcc/contentful/store/postgres_store/schema_2.sql +21 -0
- data/lib/wcc/contentful/store/query.rb +246 -0
- data/lib/wcc/contentful/store/query/interface.rb +63 -0
- data/lib/wcc/contentful/store/rspec_examples.rb +48 -0
- data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +629 -0
- data/lib/wcc/contentful/store/rspec_examples/include_param.rb +283 -0
- data/lib/wcc/contentful/store/rspec_examples/nested_queries.rb +342 -0
- data/lib/wcc/contentful/sync_engine.rb +181 -0
- data/lib/wcc/contentful/test.rb +7 -0
- data/lib/wcc/contentful/test/attributes.rb +56 -0
- data/lib/wcc/contentful/test/double.rb +76 -0
- data/lib/wcc/contentful/test/factory.rb +101 -0
- data/lib/wcc/contentful/version.rb +1 -1
- data/wcc-contentful.gemspec +28 -14
- metadata +248 -152
- data/.circleci/config.yml +0 -51
- data/.gitignore +0 -26
- data/.rubocop.yml +0 -242
- data/.rubocop_todo.yml +0 -19
- data/.travis.yml +0 -5
- data/CHANGELOG.md +0 -180
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -8
- data/LICENSE.txt +0 -21
- data/app/jobs/wcc/contentful/delayed_sync_job.rb +0 -63
- data/lib/generators/wcc/USAGE +0 -24
- data/lib/generators/wcc/model_generator.rb +0 -90
- data/lib/generators/wcc/templates/.keep +0 -0
- data/lib/generators/wcc/templates/Procfile +0 -3
- data/lib/generators/wcc/templates/contentful_shell_wrapper +0 -385
- data/lib/generators/wcc/templates/menu/generated_add_menus.ts +0 -192
- data/lib/generators/wcc/templates/menu/models/menu.rb +0 -23
- data/lib/generators/wcc/templates/menu/models/menu_button.rb +0 -23
- data/lib/generators/wcc/templates/page/generated_add_pages.ts +0 -50
- data/lib/generators/wcc/templates/page/models/page.rb +0 -23
- data/lib/generators/wcc/templates/release +0 -9
- data/lib/generators/wcc/templates/wcc_contentful.rb +0 -17
- data/lib/wcc/contentful/client_ext.rb +0 -28
- data/lib/wcc/contentful/graphql.rb +0 -14
- data/lib/wcc/contentful/graphql/builder.rb +0 -177
- data/lib/wcc/contentful/graphql/types.rb +0 -54
- data/lib/wcc/contentful/model/dropdown_menu.rb +0 -7
- data/lib/wcc/contentful/model/menu.rb +0 -6
- data/lib/wcc/contentful/model/menu_button.rb +0 -16
- data/lib/wcc/contentful/model/page.rb +0 -8
- data/lib/wcc/contentful/model/redirect.rb +0 -19
- data/lib/wcc/contentful/model_validators.rb +0 -121
- data/lib/wcc/contentful/model_validators/dsl.rb +0 -166
- data/lib/wcc/contentful/simple_client/http_adapter.rb +0 -24
- data/lib/wcc/contentful/store/lazy_cache_store.rb +0 -161
@@ -3,15 +3,15 @@
|
|
3
3
|
gem 'typhoeus'
|
4
4
|
require 'typhoeus'
|
5
5
|
|
6
|
-
class TyphoeusAdapter
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
class WCC::Contentful::SimpleClient::TyphoeusAdapter
|
7
|
+
def get(url, params = {}, headers = {})
|
8
|
+
req = OpenStruct.new(params: params, headers: headers)
|
9
|
+
yield req if block_given?
|
10
|
+
Response.new(
|
11
11
|
Typhoeus.get(
|
12
12
|
url,
|
13
|
-
params:
|
14
|
-
headers: headers
|
13
|
+
params: req.params,
|
14
|
+
headers: req.headers
|
15
15
|
)
|
16
16
|
)
|
17
17
|
end
|
@@ -19,7 +19,7 @@ class TyphoeusAdapter
|
|
19
19
|
def post(url, body, headers = {}, proxy = {})
|
20
20
|
raise NotImplementedError, 'Proxying Not Yet Implemented' if proxy[:host]
|
21
21
|
|
22
|
-
|
22
|
+
Response.new(
|
23
23
|
Typhoeus.post(
|
24
24
|
url,
|
25
25
|
body: body.to_json,
|
@@ -28,15 +28,15 @@ class TyphoeusAdapter
|
|
28
28
|
)
|
29
29
|
end
|
30
30
|
|
31
|
-
Response
|
32
|
-
|
33
|
-
delegate :body, to: :raw
|
34
|
-
delegate :to_s, to: :body
|
35
|
-
delegate :code, to: :raw
|
36
|
-
delegate :headers, to: :raw
|
31
|
+
class Response < SimpleDelegator
|
32
|
+
delegate :to_s, to: :body
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
34
|
+
def raw
|
35
|
+
__getobj__
|
41
36
|
end
|
37
|
+
|
38
|
+
def status
|
39
|
+
code
|
40
|
+
end
|
41
|
+
end
|
42
42
|
end
|
data/lib/wcc/contentful/store.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'store/
|
4
|
-
require_relative 'store/memory_store'
|
5
|
-
require_relative 'store/cdn_adapter'
|
6
|
-
require_relative 'store/lazy_cache_store'
|
3
|
+
require_relative 'store/factory'
|
7
4
|
|
8
5
|
# The "Store" is the middle layer in the WCC::Contentful gem. It exposes an API
|
9
6
|
# that implements the configured content delivery strategy.
|
@@ -18,84 +15,28 @@ require_relative 'store/lazy_cache_store'
|
|
18
15
|
#
|
19
16
|
# lazy_sync:: Uses the Contentful CDN in combination with an ActiveSupport::Cache
|
20
17
|
# implementation in order to respond with the cached data where possible,
|
21
|
-
# saving your CDN quota. The cache is kept up-to-date via the Sync
|
22
|
-
# and the WCC::Contentful::
|
18
|
+
# saving your CDN quota. The cache is kept up-to-date via the Sync Engine
|
19
|
+
# and the WCC::Contentful::SyncEngine::Job. It is correct, but not complete.
|
23
20
|
#
|
24
21
|
# eager_sync:: Uses one of the full store implementations to store the entirety
|
25
22
|
# of the Contentful space locally. All queries are run against this
|
26
|
-
# local copy, which is kept up to date via the Sync
|
27
|
-
# WCC::Contentful::
|
23
|
+
# local copy, which is kept up to date via the Sync Engine and the
|
24
|
+
# WCC::Contentful::SyncEngine::Job. The local store is correct and complete.
|
28
25
|
#
|
29
26
|
# The currently configured store is available on WCC::Contentful::Services.instance.store
|
30
27
|
module WCC::Contentful::Store
|
31
28
|
SYNC_STORES = {
|
32
|
-
memory: ->(_config) { WCC::Contentful::Store::MemoryStore.new },
|
29
|
+
memory: ->(_config, *_options) { WCC::Contentful::Store::MemoryStore.new },
|
33
30
|
postgres: ->(config, *options) {
|
34
31
|
require_relative 'store/postgres_store'
|
35
32
|
WCC::Contentful::Store::PostgresStore.new(config, *options)
|
36
33
|
}
|
37
34
|
}.freeze
|
38
35
|
|
39
|
-
|
36
|
+
PRESETS = %i[
|
40
37
|
eager_sync
|
41
38
|
lazy_sync
|
42
39
|
direct
|
43
40
|
custom
|
44
41
|
].freeze
|
45
|
-
|
46
|
-
Factory =
|
47
|
-
Struct.new(:config, :services, :cdn_method, :content_delivery_params) do
|
48
|
-
def build_sync_store
|
49
|
-
unless respond_to?("build_#{cdn_method}")
|
50
|
-
raise ArgumentError, "Don't know how to build content delivery method #{cdn_method}"
|
51
|
-
end
|
52
|
-
|
53
|
-
public_send("build_#{cdn_method}", config, *content_delivery_params)
|
54
|
-
end
|
55
|
-
|
56
|
-
def validate!
|
57
|
-
unless CDN_METHODS.include?(cdn_method)
|
58
|
-
raise ArgumentError, "Please use one of #{CDN_METHODS} instead of #{cdn_method}"
|
59
|
-
end
|
60
|
-
|
61
|
-
return unless respond_to?("validate_#{cdn_method}")
|
62
|
-
|
63
|
-
public_send("validate_#{cdn_method}", config, *content_delivery_params)
|
64
|
-
end
|
65
|
-
|
66
|
-
def build_eager_sync(config, store = nil, *options)
|
67
|
-
store = SYNC_STORES[store].call(config, *options) if store.is_a?(Symbol)
|
68
|
-
store || MemoryStore.new
|
69
|
-
end
|
70
|
-
|
71
|
-
def build_lazy_sync(_config, *options)
|
72
|
-
WCC::Contentful::Store::LazyCacheStore.new(
|
73
|
-
services.client,
|
74
|
-
cache: ActiveSupport::Cache.lookup_store(*options)
|
75
|
-
)
|
76
|
-
end
|
77
|
-
|
78
|
-
def build_direct(_config, *options)
|
79
|
-
if options.find { |array| array[:preview] == true }
|
80
|
-
CDNAdapter.new(services.preview_client)
|
81
|
-
else
|
82
|
-
CDNAdapter.new(services.client)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def build_custom(config, *options)
|
87
|
-
store = config.store
|
88
|
-
return store unless store&.respond_to?(:new)
|
89
|
-
|
90
|
-
store.new(config, options)
|
91
|
-
end
|
92
|
-
|
93
|
-
def validate_eager_sync(_config, store = nil, *_options)
|
94
|
-
return unless store.is_a?(Symbol)
|
95
|
-
|
96
|
-
return if SYNC_STORES.key?(store)
|
97
|
-
|
98
|
-
raise ArgumentError, "Please use one of #{SYNC_STORES.keys}"
|
99
|
-
end
|
100
|
-
end
|
101
42
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Store API
|
2
|
+
|
3
|
+
The Store layer is used by the Model API to access Contentful data in a raw form.
|
4
|
+
The Store layer returns entries as hashes parsed from JSON, conforming to the
|
5
|
+
object structure returned from the Contentful CDN.
|
6
|
+
|
7
|
+
## Public Interface
|
8
|
+
|
9
|
+
See WCC::Contentful::Store::Interface in wcc/contentful/store/interface.rb
|
10
|
+
|
11
|
+
This is the interface consumed by higher layers, such as the Model and GraphQL
|
12
|
+
APIs. It implements the following methods:
|
13
|
+
|
14
|
+
* `index?` - Returns boolean true if the SyncEngine should call the store's `index(json)`
|
15
|
+
method with the results of a Sync.
|
16
|
+
* `index(json)` - Updates the store with the latest data. The JSON can be an Entry,
|
17
|
+
Asset, DeletedEntry, or DeletedAsset.
|
18
|
+
* `find(id)` - Finds an entry or asset by it's ID.
|
19
|
+
* `find_all(content_type:, options: nil)` - Returns a query object that can be
|
20
|
+
enumerated to lazily iterate over all values of a content type. Query conditions
|
21
|
+
can be applied on the query object before it is enumerated to restrict the result
|
22
|
+
set.
|
23
|
+
* `find_by(content_type:, filter: nil, options: nil)` - Returns the first entry
|
24
|
+
of the given content type which matches the filter.
|
25
|
+
Note: assets have the special content
|
26
|
+
type of `Asset` (capital A)
|
27
|
+
|
28
|
+
## Implementing your own store
|
29
|
+
|
30
|
+
The most straightforward way to implement a store is to include the
|
31
|
+
WCC::Contentful::Store::Interface module and then override all the defined
|
32
|
+
methods. The easiest way however, is to inherit from WCC::Contentful::Store::Base
|
33
|
+
and override the `set`, `delete`, `find`, and `execute` methods.
|
34
|
+
|
35
|
+
Let's take a look at the MemoryStore for a simplistic example. The MemoryStore
|
36
|
+
stores entries in a simple Ruby hash keyed by entry ID. `set` is simply
|
37
|
+
assigning to the key in the hash and returning the old value. `delete` and `find`
|
38
|
+
are likewise simple. The only complex method is `execute`, because it powers the
|
39
|
+
`find_by` and `find_all` query methods.
|
40
|
+
|
41
|
+
The query passed in to `execute` is an instance of WCC::Contentful::Store::Query.
|
42
|
+
This object contains a set of WCC::Contentful::Store::Query::Condition structs.
|
43
|
+
Each struct is a tuple of `path`, `op`, and `expected`. `op` is one of
|
44
|
+
WCC::Contentful::Store::Query::Interface::OPERATORS, `path` is an array of fields
|
45
|
+
pointing to a value in the JSON representation of an entry, and `expected` is the
|
46
|
+
expected value that should be compared to the value selected by `path`.
|
47
|
+
|
48
|
+
Since the MemoryStore only implements the equality operator, it simply digs
|
49
|
+
out the value at the given path using `val = entry.dig(*condition.path)`
|
50
|
+
and compares it using Contentful's definition of equality:
|
51
|
+
```rb
|
52
|
+
# For arrays, equality is defined as does the array include the expected value.
|
53
|
+
# See https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/array-equality-inequality
|
54
|
+
if val.is_a? Array
|
55
|
+
val.include?(condition.expected)
|
56
|
+
else
|
57
|
+
val == condition.expected
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### RSpec shared examples
|
62
|
+
|
63
|
+
To ensure you have implemented all the appropriate behavior, there
|
64
|
+
are a set of rspec shared examples in wcc/contentful/store/rspec_examples.rb which
|
65
|
+
you can include in your specs for your store implementation. Let's look at
|
66
|
+
spec/wcc/contentful/store/memory_store_spec.rb to see how it's used:
|
67
|
+
|
68
|
+
```rb
|
69
|
+
require 'wcc/contentful/store/rspec_examples'
|
70
|
+
|
71
|
+
RSpec.describe WCC::Contentful::Store::MemoryStore do
|
72
|
+
subject { WCC::Contentful::Store::MemoryStore.new }
|
73
|
+
|
74
|
+
it_behaves_like 'contentful store', {
|
75
|
+
# memory store does not support JOINs like `Player.find_by(team: { slug: 'dallas-cowboys' })
|
76
|
+
nested_queries: false,
|
77
|
+
# Memory store supports resolving includes, but it does so in the most naiive
|
78
|
+
# way possible (by recursing down the entry's links and calling #find on every one)
|
79
|
+
include_param: 0
|
80
|
+
}
|
81
|
+
```
|
82
|
+
|
83
|
+
The hash passed to the shared examples describes the features that the store
|
84
|
+
supports. Any key not provided causes the specs to be given the 'pending'
|
85
|
+
attribute. You can disable a set of specs by providing `false` for that key.
|
@@ -1,20 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './interface'
|
4
|
+
|
3
5
|
# @api Store
|
4
6
|
module WCC::Contentful::Store
|
5
7
|
# This is the base class for stores which implement #index, and therefore
|
6
8
|
# must be kept up-to-date via the Sync API.
|
7
|
-
# @abstract At a minimum subclasses should override {#find}, {#
|
9
|
+
# @abstract At a minimum subclasses should override {#find}, {#execute}, {#set},
|
8
10
|
# and #{delete}. As an alternative to overriding set and delete, the subclass
|
9
11
|
# can override {#index}. Index is called when a webhook triggers a sync, to
|
10
12
|
# update the store.
|
13
|
+
#
|
14
|
+
# To implement a new store, you should include the rspec_examples in your rspec
|
15
|
+
# tests for the store. See spec/wcc/contentful/store/memory_store_spec.rb for
|
16
|
+
# an example.
|
11
17
|
class Base
|
12
|
-
|
13
|
-
# @abstract Subclasses should implement this at a minimum to provide data
|
14
|
-
# to the WCC::Contentful::Model API.
|
15
|
-
def find(_id)
|
16
|
-
raise NotImplementedError, "#{self.class} does not implement #find"
|
17
|
-
end
|
18
|
+
include WCC::Contentful::Store::Interface
|
18
19
|
|
19
20
|
# Sets the value of the entry with the given ID in the store.
|
20
21
|
# @abstract
|
@@ -28,6 +29,22 @@ module WCC::Contentful::Store
|
|
28
29
|
raise NotImplementedError, "#{self.class} does not implement #delete"
|
29
30
|
end
|
30
31
|
|
32
|
+
# Executes a WCC::Contentful::Store::Query object created by {#find_all} or
|
33
|
+
# {#find_by}. Implementations should override this to translate the query's
|
34
|
+
# conditions into a query against the datastore.
|
35
|
+
#
|
36
|
+
# For a very naiive implementation see WCC::Contentful::Store::MemoryStore#execute
|
37
|
+
# @abstract
|
38
|
+
def execute(_query)
|
39
|
+
raise NotImplementedError, "#{self.class} does not implement #execute"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns true if this store can persist entries and assets which are
|
43
|
+
# retrieved from the sync API.
|
44
|
+
def index?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
31
48
|
# Processes a data point received via the Sync API. This can be a published
|
32
49
|
# entry or asset, or a 'DeletedEntry' or 'DeletedAsset'. The default
|
33
50
|
# implementation calls into #set and #delete to perform the appropriate
|
@@ -76,17 +93,19 @@ module WCC::Contentful::Store
|
|
76
93
|
|
77
94
|
# Finds all entries of the given content type. A content type is required.
|
78
95
|
#
|
79
|
-
#
|
80
|
-
#
|
96
|
+
# Subclasses may override this to provide their own query implementation,
|
97
|
+
# or else override #execute to run the query after it has been parsed.
|
81
98
|
# @param [String] content_type The ID of the content type to search for.
|
82
99
|
# @param [Hash] options An optional set of additional parameters to the query
|
83
100
|
# defining for example include depth. Not all store implementations respect all options.
|
84
101
|
# @return [Query] A query object that exposes methods to apply filters
|
85
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
86
102
|
def find_all(content_type:, options: nil)
|
87
|
-
|
103
|
+
Query.new(
|
104
|
+
self,
|
105
|
+
content_type: content_type,
|
106
|
+
options: options
|
107
|
+
)
|
88
108
|
end
|
89
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
90
109
|
|
91
110
|
def initialize
|
92
111
|
@mutex = Concurrent::ReentrantReadWriteLock.new
|
@@ -96,114 +115,10 @@ module WCC::Contentful::Store
|
|
96
115
|
raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
|
97
116
|
end
|
98
117
|
|
99
|
-
|
118
|
+
private
|
100
119
|
|
101
120
|
attr_reader :mutex
|
102
|
-
|
103
|
-
# The base class for query objects returned by find_all. Subclasses should
|
104
|
-
# override the #result method to return an array-like containing the query
|
105
|
-
# results.
|
106
|
-
class Query
|
107
|
-
delegate :first, to: :result
|
108
|
-
delegate :map, to: :result
|
109
|
-
delegate :count, to: :result
|
110
|
-
|
111
|
-
OPERATORS = %i[
|
112
|
-
eq
|
113
|
-
ne
|
114
|
-
all
|
115
|
-
in
|
116
|
-
nin
|
117
|
-
exists
|
118
|
-
lt
|
119
|
-
lte
|
120
|
-
gt
|
121
|
-
gte
|
122
|
-
query
|
123
|
-
match
|
124
|
-
].freeze
|
125
|
-
|
126
|
-
# @abstract Subclasses should provide this in order to fetch the results
|
127
|
-
# of the query.
|
128
|
-
def result
|
129
|
-
raise NotImplementedError
|
130
|
-
end
|
131
|
-
|
132
|
-
def initialize(store)
|
133
|
-
@store = store
|
134
|
-
end
|
135
|
-
|
136
|
-
# @abstract Subclasses can either override this method to properly respond
|
137
|
-
# to find_by query objects, or they can define a method for each supported
|
138
|
-
# operator. Ex. `#eq`, `#ne`, `#gt`.
|
139
|
-
def apply_operator(operator, field, expected, context = nil)
|
140
|
-
respond_to?(operator) ||
|
141
|
-
raise(ArgumentError, "Operator not implemented: #{operator}")
|
142
|
-
|
143
|
-
public_send(operator, field, expected, context)
|
144
|
-
end
|
145
|
-
|
146
|
-
# Called with a filter object by {Base#find_by} in order to apply the filter.
|
147
|
-
def apply(filter, context = nil)
|
148
|
-
filter.reduce(self) do |query, (field, value)|
|
149
|
-
if value.is_a?(Hash)
|
150
|
-
if op?(k = value.keys.first)
|
151
|
-
query.apply_operator(k.to_sym, field.to_s, value[k], context)
|
152
|
-
else
|
153
|
-
query.nested_conditions(field, value, context)
|
154
|
-
end
|
155
|
-
else
|
156
|
-
query.apply_operator(:eq, field.to_s, value)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
protected
|
162
|
-
|
163
|
-
# naive implementation recursively descends the graph to turns links into
|
164
|
-
# the actual entry data. This calls {Base#find} for each link and so it is
|
165
|
-
# very inefficient.
|
166
|
-
#
|
167
|
-
# @abstract Override this to provide a more efficient implementation for
|
168
|
-
# a given store.
|
169
|
-
def resolve_includes(entry, depth)
|
170
|
-
return entry unless entry && depth && depth > 0 && fields = entry['fields']
|
171
|
-
|
172
|
-
fields.each do |(_name, locales)|
|
173
|
-
# TODO: handle non-* locale
|
174
|
-
locales.each do |(locale, val)|
|
175
|
-
locales[locale] =
|
176
|
-
if val.is_a? Array
|
177
|
-
val.map { |e| resolve_link(e, depth) }
|
178
|
-
else
|
179
|
-
resolve_link(val, depth)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
entry
|
185
|
-
end
|
186
|
-
|
187
|
-
def resolve_link(val, depth)
|
188
|
-
return val unless val.is_a?(Hash) && val.dig('sys', 'type') == 'Link'
|
189
|
-
return val unless included = @store.find(val.dig('sys', 'id'))
|
190
|
-
|
191
|
-
resolve_includes(included, depth - 1)
|
192
|
-
end
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
def op?(key)
|
197
|
-
OPERATORS.include?(key.to_sym)
|
198
|
-
end
|
199
|
-
|
200
|
-
def sys?(field)
|
201
|
-
field.to_s =~ /sys\./
|
202
|
-
end
|
203
|
-
|
204
|
-
def id?(field)
|
205
|
-
field.to_sym == :id
|
206
|
-
end
|
207
|
-
end
|
208
121
|
end
|
209
122
|
end
|
123
|
+
|
124
|
+
require_relative './query'
|
@@ -2,13 +2,30 @@
|
|
2
2
|
|
3
3
|
module WCC::Contentful::Store
|
4
4
|
class CDNAdapter
|
5
|
-
|
5
|
+
include WCC::Contentful::Store::Interface
|
6
|
+
# Note: CDNAdapter should not instrument store events cause it's not a store.
|
7
|
+
|
8
|
+
attr_writer :client, :preview_client
|
9
|
+
|
10
|
+
def client
|
11
|
+
@preview ? @preview_client : @client
|
12
|
+
end
|
13
|
+
|
14
|
+
# The CDNAdapter cannot index data coming back from the Sync API.
|
15
|
+
def index?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def index
|
20
|
+
raise NotImplementedError, 'Cannot put data to the CDN!'
|
21
|
+
end
|
6
22
|
|
7
23
|
# Intentionally not implementing write methods
|
8
24
|
|
9
|
-
def initialize(client)
|
25
|
+
def initialize(client = nil, preview: false)
|
10
26
|
super()
|
11
27
|
@client = client
|
28
|
+
@preview = preview
|
12
29
|
end
|
13
30
|
|
14
31
|
def find(key, hint: nil, **options)
|
@@ -37,39 +54,61 @@ module WCC::Contentful::Store
|
|
37
54
|
|
38
55
|
def find_all(content_type:, options: nil)
|
39
56
|
Query.new(
|
40
|
-
|
41
|
-
client:
|
57
|
+
self,
|
58
|
+
client: client,
|
42
59
|
relation: { content_type: content_type },
|
43
60
|
options: options
|
44
61
|
)
|
45
62
|
end
|
46
63
|
|
47
|
-
class Query
|
64
|
+
class Query
|
65
|
+
include WCC::Contentful::Store::Query::Interface
|
66
|
+
include Enumerable
|
67
|
+
|
68
|
+
# by default all enumerable methods delegated to the lazy enumerable
|
69
|
+
delegate(*(Enumerable.instance_methods - Module.instance_methods), to: :to_enum)
|
70
|
+
|
71
|
+
# response.count gets the number of items
|
48
72
|
delegate :count, to: :response
|
49
73
|
|
50
|
-
def
|
74
|
+
def to_enum
|
51
75
|
return response.items unless @options[:include]
|
52
76
|
|
53
77
|
response.items.map { |e| resolve_includes(e, @options[:include]) }
|
54
78
|
end
|
55
79
|
|
56
|
-
def initialize(store
|
80
|
+
def initialize(store, client:, relation:, options: nil, **extra)
|
57
81
|
raise ArgumentError, 'Client cannot be nil' unless client.present?
|
58
82
|
raise ArgumentError, 'content_type must be provided' unless relation[:content_type].present?
|
59
83
|
|
60
|
-
|
84
|
+
@store = store
|
61
85
|
@client = client
|
62
86
|
@relation = relation
|
63
87
|
@options = options || {}
|
64
88
|
@extra = extra || {}
|
65
89
|
end
|
66
90
|
|
91
|
+
# Called with a filter object by {Base#find_by} in order to apply the filter.
|
92
|
+
def apply(filter, context = nil)
|
93
|
+
filter.reduce(self) do |query, (field, value)|
|
94
|
+
if value.is_a?(Hash)
|
95
|
+
if op?(k = value.keys.first)
|
96
|
+
query.apply_operator(k.to_sym, field.to_s, value[k], context)
|
97
|
+
else
|
98
|
+
query.nested_conditions(field, value, context)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
query.apply_operator(:eq, field.to_s, value)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
67
106
|
def apply_operator(operator, field, expected, context = nil)
|
68
107
|
op = operator == :eq ? nil : operator
|
69
108
|
param = parameter(field, operator: op, context: context, locale: true)
|
70
109
|
|
71
110
|
self.class.new(
|
72
|
-
|
111
|
+
@store,
|
73
112
|
client: @client,
|
74
113
|
relation: @relation.merge(param => expected),
|
75
114
|
options: @options,
|
@@ -85,7 +124,7 @@ module WCC::Contentful::Store
|
|
85
124
|
end
|
86
125
|
end
|
87
126
|
|
88
|
-
|
127
|
+
WCC::Contentful::Store::Query::Interface::OPERATORS.each do |op|
|
89
128
|
define_method(op) do |field, expected, context = nil|
|
90
129
|
apply_operator(op, field, expected, context)
|
91
130
|
end
|
@@ -93,6 +132,18 @@ module WCC::Contentful::Store
|
|
93
132
|
|
94
133
|
private
|
95
134
|
|
135
|
+
def op?(key)
|
136
|
+
WCC::Contentful::Store::Query::Interface::OPERATORS.include?(key.to_sym)
|
137
|
+
end
|
138
|
+
|
139
|
+
def sys?(field)
|
140
|
+
field.to_s =~ /sys\./
|
141
|
+
end
|
142
|
+
|
143
|
+
def id?(field)
|
144
|
+
field.to_sym == :id
|
145
|
+
end
|
146
|
+
|
96
147
|
def response
|
97
148
|
@response ||=
|
98
149
|
if @relation[:content_type] == 'Asset'
|
@@ -104,11 +155,19 @@ module WCC::Contentful::Store
|
|
104
155
|
end
|
105
156
|
end
|
106
157
|
|
107
|
-
def
|
158
|
+
def resolve_includes(entry, depth)
|
159
|
+
return entry unless entry && depth && depth > 0
|
160
|
+
|
161
|
+
WCC::Contentful::LinkVisitor.new(entry, :Link, :Asset, depth: depth - 1).map! do |val|
|
162
|
+
resolve_link(val)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def resolve_link(val)
|
108
167
|
return val unless val.is_a?(Hash) && val.dig('sys', 'type') == 'Link'
|
109
168
|
return val unless included = response.includes[val.dig('sys', 'id')]
|
110
169
|
|
111
|
-
|
170
|
+
included
|
112
171
|
end
|
113
172
|
|
114
173
|
def parameter(field, operator: nil, context: nil, locale: false)
|