wcc-contentful 0.2.2 → 0.3.0.pre.rc
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/.rspec +0 -1
- data/README.md +181 -8
- data/app/controllers/wcc/contentful/webhook_controller.rb +42 -2
- data/app/jobs/wcc/contentful/delayed_sync_job.rb +52 -3
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +43 -0
- data/bin/console +4 -3
- data/bin/rails +2 -0
- data/config/initializers/mime_types.rb +10 -1
- data/lib/wcc/contentful.rb +14 -142
- data/lib/wcc/contentful/client_ext.rb +17 -4
- data/lib/wcc/contentful/configuration.rb +25 -84
- data/lib/wcc/contentful/engine.rb +19 -0
- data/lib/wcc/contentful/exceptions.rb +25 -28
- data/lib/wcc/contentful/graphql.rb +0 -1
- data/lib/wcc/contentful/graphql/types.rb +1 -1
- data/lib/wcc/contentful/helpers.rb +3 -2
- data/lib/wcc/contentful/indexed_representation.rb +6 -0
- data/lib/wcc/contentful/model.rb +68 -34
- data/lib/wcc/contentful/model_builder.rb +65 -67
- data/lib/wcc/contentful/model_methods.rb +189 -0
- data/lib/wcc/contentful/model_singleton_methods.rb +83 -0
- data/lib/wcc/contentful/services.rb +146 -0
- data/lib/wcc/contentful/simple_client.rb +35 -33
- data/lib/wcc/contentful/simple_client/http_adapter.rb +9 -0
- data/lib/wcc/contentful/simple_client/management.rb +81 -0
- data/lib/wcc/contentful/simple_client/response.rb +61 -37
- data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +12 -0
- data/lib/wcc/contentful/store.rb +45 -18
- data/lib/wcc/contentful/store/base.rb +128 -8
- data/lib/wcc/contentful/store/cdn_adapter.rb +92 -22
- data/lib/wcc/contentful/store/lazy_cache_store.rb +94 -9
- data/lib/wcc/contentful/store/memory_store.rb +13 -8
- data/lib/wcc/contentful/store/postgres_store.rb +44 -11
- data/lib/wcc/contentful/sys.rb +28 -0
- data/lib/wcc/contentful/version.rb +1 -1
- data/wcc-contentful.gemspec +3 -9
- metadata +87 -107
- data/.circleci/config.yml +0 -51
- data/.gitignore +0 -26
- data/.rubocop.yml +0 -243
- data/.rubocop_todo.yml +0 -13
- data/.travis.yml +0 -5
- data/CHANGELOG.md +0 -45
- data/CODE_OF_CONDUCT.md +0 -74
- data/Guardfile +0 -58
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -8
- 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 -90
- 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/model/menu.rb +0 -7
- data/lib/wcc/contentful/model/menu_button.rb +0 -15
- 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 -115
- data/lib/wcc/contentful/model_validators/dsl.rb +0 -165
@@ -22,6 +22,24 @@ class WCC::Contentful::SimpleClient
|
|
22
22
|
raw.dig('message') || "#{code}: #{raw_response.message}"
|
23
23
|
end
|
24
24
|
|
25
|
+
def next_page?
|
26
|
+
return unless raw.key? 'items'
|
27
|
+
|
28
|
+
raw['items'].length + raw['skip'] < raw['total']
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_page
|
32
|
+
return unless next_page?
|
33
|
+
|
34
|
+
@next_page ||= @client.get(
|
35
|
+
@request[:url],
|
36
|
+
(@request[:query] || {}).merge({
|
37
|
+
skip: raw['items'].length + raw['skip']
|
38
|
+
})
|
39
|
+
)
|
40
|
+
@next_page.assert_ok!
|
41
|
+
end
|
42
|
+
|
25
43
|
def initialize(client, request, raw_response)
|
26
44
|
@client = client
|
27
45
|
@request = request
|
@@ -31,33 +49,20 @@ class WCC::Contentful::SimpleClient
|
|
31
49
|
|
32
50
|
def assert_ok!
|
33
51
|
return self if code >= 200 && code < 300
|
52
|
+
|
34
53
|
raise ApiError[code], self
|
35
54
|
end
|
36
55
|
|
37
56
|
def each_page(&block)
|
38
57
|
raise ArgumentError, 'Not a collection response' unless raw['items']
|
39
58
|
|
40
|
-
memoized_pages = (@memoized_pages ||= [self])
|
41
59
|
ret =
|
42
60
|
Enumerator.new do |y|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
skip_amt = current_page.raw['items'].length + current_page.raw['skip']
|
49
|
-
break if current_page.raw['items'].empty? || skip_amt >= current_page.raw['total']
|
50
|
-
|
51
|
-
page_index += 1
|
52
|
-
if page_index < memoized_pages.length
|
53
|
-
current_page = memoized_pages[page_index]
|
54
|
-
else
|
55
|
-
current_page = @client.get(
|
56
|
-
@request[:url],
|
57
|
-
(@request[:query] || {}).merge({ skip: skip_amt })
|
58
|
-
)
|
59
|
-
current_page.assert_ok!
|
60
|
-
memoized_pages.push(current_page)
|
61
|
+
y << self
|
62
|
+
|
63
|
+
if next_page?
|
64
|
+
next_page.each_page.each do |page|
|
65
|
+
y << page
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
@@ -81,8 +86,21 @@ class WCC::Contentful::SimpleClient
|
|
81
86
|
|
82
87
|
def first
|
83
88
|
raise ArgumentError, 'Not a collection response' unless raw['items']
|
89
|
+
|
84
90
|
raw['items'].first
|
85
91
|
end
|
92
|
+
|
93
|
+
def includes
|
94
|
+
@includes ||=
|
95
|
+
raw.dig('includes')&.each_with_object({}) do |(_t, entries), h|
|
96
|
+
entries.each { |e| h[e.dig('sys', 'id')] = e }
|
97
|
+
end || {}
|
98
|
+
|
99
|
+
return @includes unless @next_page
|
100
|
+
|
101
|
+
# This could be more efficient - maybe not worth worrying about
|
102
|
+
@includes.merge(@next_page.includes)
|
103
|
+
end
|
86
104
|
end
|
87
105
|
|
88
106
|
class SyncResponse < Response
|
@@ -90,31 +108,36 @@ class WCC::Contentful::SimpleClient
|
|
90
108
|
super(response.client, response.request, response.raw_response)
|
91
109
|
end
|
92
110
|
|
111
|
+
def next_page?
|
112
|
+
raw['nextPageUrl'].present?
|
113
|
+
end
|
114
|
+
|
115
|
+
def next_page
|
116
|
+
return unless next_page?
|
117
|
+
|
118
|
+
@next_page ||= SyncResponse.new(@client.get(raw['nextPageUrl']))
|
119
|
+
@next_page.assert_ok!
|
120
|
+
end
|
121
|
+
|
93
122
|
def next_sync_token
|
94
|
-
|
123
|
+
# If we haven't grabbed the next page yet, then our next "sync" will be getting
|
124
|
+
# the next page. We could just as easily call sync again with that token.
|
125
|
+
@next_page&.next_sync_token ||
|
126
|
+
@next_sync_token ||= SyncResponse.parse_sync_token(
|
127
|
+
raw['nextPageUrl'] || raw['nextSyncUrl']
|
128
|
+
)
|
95
129
|
end
|
96
130
|
|
97
131
|
def each_page
|
98
132
|
raise ArgumentError, 'Not a collection response' unless raw['items']
|
99
133
|
|
100
|
-
memoized_pages = (@memoized_pages ||= [self])
|
101
134
|
ret =
|
102
135
|
Enumerator.new do |y|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
break if current_page.raw['items'].empty?
|
109
|
-
|
110
|
-
page_index += 1
|
111
|
-
if page_index < memoized_pages.length
|
112
|
-
current_page = memoized_pages[page_index]
|
113
|
-
else
|
114
|
-
current_page = @client.get(raw['nextSyncUrl'])
|
115
|
-
current_page.assert_ok!
|
116
|
-
@next_sync_token = SyncResponse.parse_sync_token(current_page.raw['nextSyncUrl'])
|
117
|
-
memoized_pages.push(current_page)
|
136
|
+
y << self
|
137
|
+
|
138
|
+
if next_page?
|
139
|
+
next_page.each_page.each do |page|
|
140
|
+
y << page
|
118
141
|
end
|
119
142
|
end
|
120
143
|
end
|
@@ -127,7 +150,8 @@ class WCC::Contentful::SimpleClient
|
|
127
150
|
end
|
128
151
|
|
129
152
|
def count
|
130
|
-
|
153
|
+
raise NotImplementedError,
|
154
|
+
'Sync does not return an accurate total. Use #items.count instead.'
|
131
155
|
end
|
132
156
|
|
133
157
|
def self.parse_sync_token(url)
|
@@ -16,6 +16,18 @@ class TyphoeusAdapter
|
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
|
+
def post(url, body, headers = {}, proxy = {})
|
20
|
+
raise NotImplementedError, 'Proxying Not Yet Implemented' if proxy[:host]
|
21
|
+
|
22
|
+
TyphoeusAdapter::Response.new(
|
23
|
+
Typhoeus.post(
|
24
|
+
url,
|
25
|
+
body: body.to_json,
|
26
|
+
headers: headers
|
27
|
+
)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
19
31
|
Response =
|
20
32
|
Struct.new(:raw) do
|
21
33
|
delegate :body, to: :raw
|
data/lib/wcc/contentful/store.rb
CHANGED
@@ -1,20 +1,38 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require_relative 'store/base'
|
5
4
|
require_relative 'store/memory_store'
|
6
|
-
require_relative 'store/lazy_cache_store'
|
7
5
|
require_relative 'store/cdn_adapter'
|
6
|
+
require_relative 'store/lazy_cache_store'
|
8
7
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
8
|
+
# The "Store" is the middle layer in the WCC::Contentful gem. It exposes an API
|
9
|
+
# that implements the configured content delivery strategy.
|
10
|
+
#
|
11
|
+
# The different content delivery strategies require different store implementations.
|
12
|
+
#
|
13
|
+
# direct:: Uses the WCC::Contentful::Store::CDNAdapter to wrap the Contentful CDN,
|
14
|
+
# providing an API consistent with the other stores. Any query made to
|
15
|
+
# the CDNAdapter will be immediately passed through to the API.
|
16
|
+
# The CDNAdapter does not implement #index because it does not care about
|
17
|
+
# updates coming from the Sync API.
|
18
|
+
#
|
19
|
+
# lazy_sync:: Uses the Contentful CDN in combination with an ActiveSupport::Cache
|
20
|
+
# 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 API
|
22
|
+
# and the WCC::Contentful::DelayedSyncJob. It is correct, but not complete.
|
23
|
+
#
|
24
|
+
# eager_sync:: Uses one of the full store implementations to store the entirety
|
25
|
+
# of the Contentful space locally. All queries are run against this
|
26
|
+
# local copy, which is kept up to date via the Sync API and the
|
27
|
+
# WCC::Contentful::DelayedSyncJob. The local store is correct and complete.
|
28
|
+
#
|
29
|
+
# The currently configured store is available on WCC::Contentful::Services.instance.store
|
12
30
|
module WCC::Contentful::Store
|
13
31
|
SYNC_STORES = {
|
14
32
|
memory: ->(_config) { WCC::Contentful::Store::MemoryStore.new },
|
15
|
-
postgres: ->(
|
33
|
+
postgres: ->(config, *options) {
|
16
34
|
require_relative 'store/postgres_store'
|
17
|
-
WCC::Contentful::Store::PostgresStore.new(
|
35
|
+
WCC::Contentful::Store::PostgresStore.new(config, *options)
|
18
36
|
}
|
19
37
|
}.freeze
|
20
38
|
|
@@ -22,10 +40,11 @@ module WCC::Contentful::Store
|
|
22
40
|
eager_sync
|
23
41
|
lazy_sync
|
24
42
|
direct
|
43
|
+
custom
|
25
44
|
].freeze
|
26
45
|
|
27
46
|
Factory =
|
28
|
-
Struct.new(:config, :cdn_method, :content_delivery_params) do
|
47
|
+
Struct.new(:config, :services, :cdn_method, :content_delivery_params) do
|
29
48
|
def build_sync_store
|
30
49
|
unless respond_to?("build_#{cdn_method}")
|
31
50
|
raise ArgumentError, "Don't know how to build content delivery method #{cdn_method}"
|
@@ -36,38 +55,46 @@ module WCC::Contentful::Store
|
|
36
55
|
|
37
56
|
def validate!
|
38
57
|
unless CDN_METHODS.include?(cdn_method)
|
39
|
-
raise ArgumentError, "Please use one of #{CDN_METHODS}
|
58
|
+
raise ArgumentError, "Please use one of #{CDN_METHODS} instead of #{cdn_method}"
|
40
59
|
end
|
41
60
|
|
42
61
|
return unless respond_to?("validate_#{cdn_method}")
|
62
|
+
|
43
63
|
public_send("validate_#{cdn_method}", config, *content_delivery_params)
|
44
64
|
end
|
45
65
|
|
46
|
-
def build_eager_sync(config, store = nil, *
|
47
|
-
|
48
|
-
store = SYNC_STORES[store].call(config) if store.is_a?(Symbol)
|
66
|
+
def build_eager_sync(config, store = nil, *options)
|
67
|
+
store = SYNC_STORES[store].call(config, *options) if store.is_a?(Symbol)
|
49
68
|
store || MemoryStore.new
|
50
69
|
end
|
51
70
|
|
52
|
-
def build_lazy_sync(
|
71
|
+
def build_lazy_sync(_config, *options)
|
53
72
|
WCC::Contentful::Store::LazyCacheStore.new(
|
54
|
-
|
73
|
+
services.client,
|
55
74
|
cache: ActiveSupport::Cache.lookup_store(*options)
|
56
75
|
)
|
57
76
|
end
|
58
77
|
|
59
|
-
def build_direct(
|
78
|
+
def build_direct(_config, *options)
|
60
79
|
if options.find { |array| array[:preview] == true }
|
61
|
-
CDNAdapter.new(
|
80
|
+
CDNAdapter.new(services.preview_client)
|
62
81
|
else
|
63
|
-
CDNAdapter.new(
|
82
|
+
CDNAdapter.new(services.client)
|
64
83
|
end
|
65
84
|
end
|
66
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
|
+
|
67
93
|
def validate_eager_sync(_config, store = nil, *_options)
|
68
94
|
return unless store.is_a?(Symbol)
|
69
95
|
|
70
|
-
return if SYNC_STORES.
|
96
|
+
return if SYNC_STORES.key?(store)
|
97
|
+
|
71
98
|
raise ArgumentError, "Please use one of #{SYNC_STORES.keys}"
|
72
99
|
end
|
73
100
|
end
|
@@ -1,20 +1,37 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
3
|
+
# @api Store
|
4
4
|
module WCC::Contentful::Store
|
5
|
+
# This is the base class for stores which implement #index, and therefore
|
6
|
+
# must be kept up-to-date via the Sync API.
|
7
|
+
# @abstract At a minimum subclasses should override {#find}, {#find_all}, {#set},
|
8
|
+
# and #{delete}. As an alternative to overriding set and delete, the subclass
|
9
|
+
# can override {#index}. Index is called when a webhook triggers a sync, to
|
10
|
+
# update the store.
|
5
11
|
class Base
|
12
|
+
# Finds an entry by it's ID. The returned entry is a JSON hash
|
13
|
+
# @abstract Subclasses should implement this at a minimum to provide data
|
14
|
+
# to the WCC::Contentful::Model API.
|
6
15
|
def find(_id)
|
7
16
|
raise NotImplementedError, "#{self.class} does not implement #find"
|
8
17
|
end
|
9
18
|
|
19
|
+
# Sets the value of the entry with the given ID in the store.
|
20
|
+
# @abstract
|
10
21
|
def set(_id, _value)
|
11
22
|
raise NotImplementedError, "#{self.class} does not implement #set"
|
12
23
|
end
|
13
24
|
|
25
|
+
# Removes the entry by ID from the store.
|
26
|
+
# @abstract
|
14
27
|
def delete(_id)
|
15
28
|
raise NotImplementedError, "#{self.class} does not implement #delete"
|
16
29
|
end
|
17
30
|
|
31
|
+
# Processes a data point received via the Sync API. This can be a published
|
32
|
+
# entry or asset, or a 'DeletedEntry' or 'DeletedAsset'. The default
|
33
|
+
# implementation calls into #set and #delete to perform the appropriate
|
34
|
+
# operations in the store.
|
18
35
|
def index(json)
|
19
36
|
# Subclasses can override to do this in a more performant thread-safe way.
|
20
37
|
# Example: postgres_store could do this in a stored procedure for speed
|
@@ -43,15 +60,30 @@ module WCC::Contentful::Store
|
|
43
60
|
end
|
44
61
|
end
|
45
62
|
|
46
|
-
|
63
|
+
# Finds the first entry matching the given filter. A content type is required.
|
64
|
+
#
|
65
|
+
# @param [String] content_type The ID of the content type to search for.
|
66
|
+
# @param [Hash] filter A set of key-value pairs defining filter operations.
|
67
|
+
# See WCC::Contentful::Store::Base::Query
|
68
|
+
# @param [Hash] options An optional set of additional parameters to the query
|
69
|
+
# defining for example include depth. Not all store implementations respect all options.
|
70
|
+
def find_by(content_type:, filter: nil, options: nil)
|
47
71
|
# default implementation - can be overridden
|
48
|
-
q = find_all(content_type: content_type)
|
72
|
+
q = find_all(content_type: content_type, options: { limit: 1 }.merge!(options || {}))
|
49
73
|
q = q.apply(filter) if filter
|
50
74
|
q.first
|
51
75
|
end
|
52
76
|
|
77
|
+
# Finds all entries of the given content type. A content type is required.
|
78
|
+
#
|
79
|
+
# @abstract Subclasses should implement this at a minimum to provide data
|
80
|
+
# to the {WCC::Contentful::Model} API.
|
81
|
+
# @param [String] content_type The ID of the content type to search for.
|
82
|
+
# @param [Hash] options An optional set of additional parameters to the query
|
83
|
+
# defining for example include depth. Not all store implementations respect all options.
|
84
|
+
# @return [Query] A query object that exposes methods to apply filters
|
53
85
|
# rubocop:disable Lint/UnusedMethodArgument
|
54
|
-
def find_all(content_type:)
|
86
|
+
def find_all(content_type:, options: nil)
|
55
87
|
raise NotImplementedError, "#{self.class} does not implement find_all"
|
56
88
|
end
|
57
89
|
# rubocop:enable Lint/UnusedMethodArgument
|
@@ -60,29 +92,117 @@ module WCC::Contentful::Store
|
|
60
92
|
@mutex = Concurrent::ReentrantReadWriteLock.new
|
61
93
|
end
|
62
94
|
|
95
|
+
def ensure_hash(val)
|
96
|
+
raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
|
97
|
+
end
|
98
|
+
|
63
99
|
protected
|
64
100
|
|
65
101
|
attr_reader :mutex
|
66
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.
|
67
106
|
class Query
|
68
107
|
delegate :first, to: :result
|
69
108
|
delegate :map, to: :result
|
70
109
|
delegate :count, to: :result
|
71
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.
|
72
128
|
def result
|
73
129
|
raise NotImplementedError
|
74
130
|
end
|
75
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.
|
76
147
|
def apply(filter, context = nil)
|
77
148
|
filter.reduce(self) do |query, (field, value)|
|
78
149
|
if value.is_a?(Hash)
|
79
|
-
k = value.keys.first
|
80
|
-
|
81
|
-
|
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
|
82
155
|
else
|
83
|
-
query.eq
|
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
|
84
181
|
end
|
85
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
|
86
206
|
end
|
87
207
|
end
|
88
208
|
end
|