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
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WCC::Contentful::Graphql::Types
|
4
|
-
DateTimeType =
|
5
|
-
GraphQL::ScalarType.define do
|
6
|
-
name 'DateTime'
|
7
|
-
|
8
|
-
coerce_result ->(value, _ctx) { Time.zone.parse(value) }
|
9
|
-
end
|
10
|
-
|
11
|
-
HashType =
|
12
|
-
GraphQL::ScalarType.define do
|
13
|
-
name 'Hash'
|
14
|
-
|
15
|
-
coerce_result ->(value, _ctx) {
|
16
|
-
return value if value.is_a? Array
|
17
|
-
return value.to_h if value.respond_to?(:to_h)
|
18
|
-
return JSON.parse(value) if value.is_a? String
|
19
|
-
|
20
|
-
raise ArgumentError, "Cannot coerce value '#{value}' to a hash"
|
21
|
-
}
|
22
|
-
end
|
23
|
-
|
24
|
-
CoordinatesType =
|
25
|
-
GraphQL::ObjectType.define do
|
26
|
-
name 'Coordinates'
|
27
|
-
|
28
|
-
field :lat, !types.Float, hash_key: 'lat'
|
29
|
-
field :lon, !types.Float, hash_key: 'lon'
|
30
|
-
end
|
31
|
-
|
32
|
-
AnyScalarInputType =
|
33
|
-
GraphQL::ScalarType.define do
|
34
|
-
name 'Any'
|
35
|
-
end
|
36
|
-
|
37
|
-
FilterType =
|
38
|
-
GraphQL::InputObjectType.define do
|
39
|
-
name 'filter'
|
40
|
-
|
41
|
-
argument :field, !types.String
|
42
|
-
argument :eq, AnyScalarInputType
|
43
|
-
end
|
44
|
-
|
45
|
-
BuildUnionType =
|
46
|
-
->(from_types, union_type_name) do
|
47
|
-
possible_types = from_types.values.reject { |t| t.is_a? GraphQL::UnionType }
|
48
|
-
|
49
|
-
GraphQL::UnionType.define do
|
50
|
-
name union_type_name
|
51
|
-
possible_types possible_types
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class WCC::Contentful::Model::MenuButton < WCC::Contentful::Model
|
4
|
-
validate_field :text, :String, :required
|
5
|
-
validate_field :icon, :Asset, :optional
|
6
|
-
validate_field :external_link, :String, :optional
|
7
|
-
validate_field :link, :Link, :optional, link_to: 'page'
|
8
|
-
|
9
|
-
# Gets either the external link or the slug from the referenced page.
|
10
|
-
# Example usage: `<%= link_to button.title, button.href %>`
|
11
|
-
def href
|
12
|
-
return external_link if external_link
|
13
|
-
|
14
|
-
link&.try(:slug) || link&.try(:url)
|
15
|
-
end
|
16
|
-
end
|
@@ -1,8 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class WCC::Contentful::Model::Page < WCC::Contentful::Model
|
4
|
-
validate_field :title, :String
|
5
|
-
validate_field :slug, :String
|
6
|
-
validate_field :subpages, :Array, link_to: %w[page]
|
7
|
-
validate_field :sections, :Array, link_to: /^section/
|
8
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class WCC::Contentful::Model::Redirect < WCC::Contentful::Model
|
4
|
-
def href
|
5
|
-
if !url.nil?
|
6
|
-
url
|
7
|
-
elsif valid_page_reference?(pageReference)
|
8
|
-
"/#{pageReference.url}"
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def valid_page_reference?(page_ref)
|
13
|
-
if !page_ref.nil? && !defined?(page_ref.url).nil?
|
14
|
-
true
|
15
|
-
else
|
16
|
-
false
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'dry-validation'
|
4
|
-
|
5
|
-
require_relative 'model_validators/dsl'
|
6
|
-
|
7
|
-
module WCC::Contentful::ModelValidators
|
8
|
-
def schema
|
9
|
-
return if validations.nil? || validations.empty?
|
10
|
-
|
11
|
-
all_field_validations =
|
12
|
-
validations.each_with_object({}) do |(content_type, procs), h|
|
13
|
-
next if procs.empty?
|
14
|
-
|
15
|
-
# "page": {
|
16
|
-
# "sys": { ... }
|
17
|
-
# "fields": {
|
18
|
-
# "title": { ... },
|
19
|
-
# "sections": { ... },
|
20
|
-
# ...
|
21
|
-
# }
|
22
|
-
# }
|
23
|
-
h[content_type] =
|
24
|
-
Dry::Validation.Schema do
|
25
|
-
# Had to dig through the internals of Dry::Validation to find
|
26
|
-
# this magic incantation
|
27
|
-
procs.each { |dsl| instance_eval(&dsl.to_proc) }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
Dry::Validation.Schema do
|
32
|
-
all_field_validations.each do |content_type, fields_schema|
|
33
|
-
required(content_type).schema do
|
34
|
-
required('fields').schema(fields_schema)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def validations
|
41
|
-
# This needs to be a class variable so that subclasses defined in application
|
42
|
-
# code can add to the total package of model validations
|
43
|
-
# rubocop:disable Style/ClassVars
|
44
|
-
@@validations ||= {}
|
45
|
-
# rubocop:enable Style/ClassVars
|
46
|
-
end
|
47
|
-
|
48
|
-
# Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
|
49
|
-
# to validate the 'fields' object of a content type.
|
50
|
-
def validate_fields(&block)
|
51
|
-
raise ArgumentError, 'validate_fields requires a block' unless block_given?
|
52
|
-
|
53
|
-
dsl = ProcDsl.new(Proc.new(&block))
|
54
|
-
|
55
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
56
|
-
(validations[ct] ||= []) << dsl
|
57
|
-
end
|
58
|
-
|
59
|
-
# Validates a single field is of the expected type.
|
60
|
-
# Type expectations are one of:
|
61
|
-
#
|
62
|
-
# [:String] the field type must be `Symbol` or `Text`
|
63
|
-
# [:Int] the field type must be `Integer`
|
64
|
-
# [:Float] the field type must be `Number`
|
65
|
-
# [:DateTime] the field type must be 'Date'
|
66
|
-
# [:Asset] the field must be a link and the `linkType` must be `Asset`
|
67
|
-
# [:Link] the field must be a link and the `linkType` must be `Entry`.
|
68
|
-
# [:Location] the field type must be `Location`
|
69
|
-
# [:Boolean] the field type must be `Boolean`
|
70
|
-
# [:Json] the field type must be `Json` - a json blob.
|
71
|
-
# [:Array] the field must be a List.
|
72
|
-
#
|
73
|
-
# Additional validation options can be enforced:
|
74
|
-
#
|
75
|
-
# [:required] the 'Required Field' checkbox must be checked
|
76
|
-
# [:optional] the 'Required Field' checkbox must not be checked
|
77
|
-
# [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
|
78
|
-
# checked in the 'Accept only specified entry type' validations
|
79
|
-
# Example:
|
80
|
-
# validate_field :button, :Link, link_to: ['button', 'altButton']
|
81
|
-
#
|
82
|
-
# [:items] (only `:Array` type) the items of the list must be of the given type.
|
83
|
-
# Example:
|
84
|
-
# validate_field :my_strings, :Array, items: :String
|
85
|
-
#
|
86
|
-
# Examples:
|
87
|
-
# see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
|
88
|
-
def validate_field(field, type, *options)
|
89
|
-
dsl = FieldDsl.new(field, type, options)
|
90
|
-
|
91
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
92
|
-
(validations[ct] ||= []) << dsl
|
93
|
-
end
|
94
|
-
|
95
|
-
def no_validate_field(field)
|
96
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
97
|
-
return unless v = validations[ct]
|
98
|
-
|
99
|
-
field = field.to_s.camelize(:lower) unless field.is_a?(String)
|
100
|
-
v.reject! { |dsl| dsl.try(:field) == field }
|
101
|
-
end
|
102
|
-
|
103
|
-
# Accepts a content types response from the API and transforms it
|
104
|
-
# to be acceptible for the validator.
|
105
|
-
def self.transform_content_types_for_validation(content_types)
|
106
|
-
if !content_types.is_a?(Array) && items = content_types.try(:[], 'items')
|
107
|
-
content_types = items
|
108
|
-
end
|
109
|
-
|
110
|
-
# Transform the array into a hash keyed by content type ID
|
111
|
-
content_types.each_with_object({}) do |ct, ct_hash|
|
112
|
-
# Transform the fields into a hash keyed by field ID
|
113
|
-
ct['fields'] =
|
114
|
-
ct['fields'].each_with_object({}) do |f, f_hash|
|
115
|
-
f_hash[f['id']] = f
|
116
|
-
end
|
117
|
-
|
118
|
-
ct_hash[ct.dig('sys', 'id')] = ct
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
@@ -1,166 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WCC::Contentful::ModelValidators
|
4
|
-
class ProcDsl
|
5
|
-
def to_proc
|
6
|
-
@proc
|
7
|
-
end
|
8
|
-
|
9
|
-
def initialize(proc)
|
10
|
-
@proc = proc
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class FieldDsl
|
15
|
-
attr_reader :field
|
16
|
-
|
17
|
-
# "sections": {
|
18
|
-
# "id": "sections",
|
19
|
-
# "name": "Sections",
|
20
|
-
# "type": "Array",
|
21
|
-
# "localized": false,
|
22
|
-
# "required": false,
|
23
|
-
# "validations": [],
|
24
|
-
# "disabled": false,
|
25
|
-
# "omitted": false,
|
26
|
-
# "items": {
|
27
|
-
# "type": "Link",
|
28
|
-
# "validations": [
|
29
|
-
# {
|
30
|
-
# "linkContentType": [
|
31
|
-
# "Section"
|
32
|
-
# ]
|
33
|
-
# }
|
34
|
-
# ],
|
35
|
-
# "linkType": "Entry"
|
36
|
-
# }
|
37
|
-
# }
|
38
|
-
|
39
|
-
def schema
|
40
|
-
return @field_schema if @field_schema
|
41
|
-
|
42
|
-
# example: required('type').value(...)
|
43
|
-
type_pred = parse_type_predicate(@type)
|
44
|
-
|
45
|
-
# example: [required('required').value(eq?: true), ...]
|
46
|
-
procs =
|
47
|
-
@options.map do |opt|
|
48
|
-
if opt.is_a?(Hash)
|
49
|
-
opt.map { |k, v| parse_option(k, v) }
|
50
|
-
else
|
51
|
-
parse_option(opt)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
@field_schema =
|
56
|
-
Dry::Validation.Schema do
|
57
|
-
instance_eval(&type_pred)
|
58
|
-
|
59
|
-
procs.flatten.each { |p| instance_eval(&p) }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_proc
|
64
|
-
f = field
|
65
|
-
s = schema
|
66
|
-
proc { required(f).schema(s) }
|
67
|
-
end
|
68
|
-
|
69
|
-
def initialize(field, field_type, options)
|
70
|
-
@field = field.to_s.camelize(:lower) unless field.is_a?(String)
|
71
|
-
@type = field_type
|
72
|
-
@options = options
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def parse_type_predicate(type)
|
78
|
-
case type
|
79
|
-
when :String
|
80
|
-
proc { required('type').value(included_in?: %w[Symbol Text]) }
|
81
|
-
when :Int
|
82
|
-
proc { required('type').value(eql?: 'Integer') }
|
83
|
-
when :Float
|
84
|
-
proc { required('type').value(eql?: 'Number') }
|
85
|
-
when :DateTime
|
86
|
-
proc { required('type').value(eql?: 'Date') }
|
87
|
-
when :Asset
|
88
|
-
proc {
|
89
|
-
required('type').value(eql?: 'Link')
|
90
|
-
required('linkType').value(eql?: 'Asset')
|
91
|
-
}
|
92
|
-
else
|
93
|
-
proc { required('type').value(eql?: type.to_s.camelize) }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def parse_option(option, option_arg = nil)
|
98
|
-
case option
|
99
|
-
when :required
|
100
|
-
proc { required('required').value(eql?: true) }
|
101
|
-
when :optional
|
102
|
-
proc { required('required').value(eql?: false) }
|
103
|
-
when :link_to
|
104
|
-
link_to_proc = parse_field_link_to(option_arg)
|
105
|
-
return link_to_proc unless @type.to_s.camelize == 'Array'
|
106
|
-
|
107
|
-
proc {
|
108
|
-
required('items').schema do
|
109
|
-
required('type').value(eql?: 'Link')
|
110
|
-
instance_eval(&link_to_proc)
|
111
|
-
end
|
112
|
-
}
|
113
|
-
when :items
|
114
|
-
type_pred = parse_type_predicate(option_arg)
|
115
|
-
proc {
|
116
|
-
required('items').schema do
|
117
|
-
instance_eval(&type_pred)
|
118
|
-
end
|
119
|
-
}
|
120
|
-
else
|
121
|
-
raise ArgumentError, "unknown validation requirement: #{option}"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def parse_field_link_to(option_arg)
|
126
|
-
raise ArgumentError, 'validation link_to: requires an argument' unless option_arg
|
127
|
-
|
128
|
-
# this works because a Link can only have one validation in its "validations" array -
|
129
|
-
# this will fail if Contentful ever changes that.
|
130
|
-
|
131
|
-
# the 'validations' schema needs to be optional because if we get the content
|
132
|
-
# types from the CDN instead of the management API, sometimes the validations
|
133
|
-
# don't get sent back.
|
134
|
-
|
135
|
-
# "validations": [
|
136
|
-
# {
|
137
|
-
# "linkContentType": [
|
138
|
-
# "section-CardSearch",
|
139
|
-
# "section-Faq",
|
140
|
-
# "section-Testimonials",
|
141
|
-
# "section-VideoHighlight"
|
142
|
-
# ]
|
143
|
-
# }
|
144
|
-
# ]
|
145
|
-
|
146
|
-
if option_arg.is_a?(Regexp)
|
147
|
-
return proc {
|
148
|
-
optional('validations').each do
|
149
|
-
schema do
|
150
|
-
required('linkContentType').each(format?: option_arg)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
}
|
154
|
-
end
|
155
|
-
|
156
|
-
option_arg = [option_arg] unless option_arg.is_a?(Array)
|
157
|
-
proc {
|
158
|
-
optional('validations').each do
|
159
|
-
schema do
|
160
|
-
required('linkContentType').value(eql?: option_arg)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
}
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
gem 'http'
|
4
|
-
require 'http'
|
5
|
-
|
6
|
-
class HttpAdapter
|
7
|
-
def call(url, query, headers = {}, proxy = {})
|
8
|
-
if proxy[:host]
|
9
|
-
HTTP[headers].via(proxy[:host], proxy[:port], proxy[:username], proxy[:password])
|
10
|
-
.get(url, params: query)
|
11
|
-
else
|
12
|
-
HTTP[headers].get(url, params: query)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def post(url, body, headers = {}, proxy = {})
|
17
|
-
if proxy[:host]
|
18
|
-
HTTP[headers].via(proxy[:host], proxy[:port], proxy[:username], proxy[:password])
|
19
|
-
.post(url, json: body)
|
20
|
-
else
|
21
|
-
HTTP[headers].post(url, json: body)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,161 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WCC::Contentful::Store
|
4
|
-
class LazyCacheStore
|
5
|
-
def initialize(client, cache: nil)
|
6
|
-
@cdn = CDNAdapter.new(client)
|
7
|
-
@cache = cache || ActiveSupport::Cache::MemoryStore.new
|
8
|
-
@client = client
|
9
|
-
end
|
10
|
-
|
11
|
-
def find(key, **options)
|
12
|
-
found =
|
13
|
-
@cache.fetch(key) do
|
14
|
-
# if it's not a contentful ID don't hit the API.
|
15
|
-
# Store a nil object if we can't find the object on the CDN.
|
16
|
-
(@cdn.find(key, options) || nil_obj(key)) if key =~ /^\w+$/
|
17
|
-
end
|
18
|
-
|
19
|
-
case found.try(:dig, 'sys', 'type')
|
20
|
-
when 'Nil', 'DeletedEntry', 'DeletedAsset'
|
21
|
-
nil
|
22
|
-
else
|
23
|
-
found
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# TODO: https://github.com/watermarkchurch/wcc-contentful/issues/18
|
28
|
-
# figure out how to cache the results of a find_by query, ex:
|
29
|
-
# `find_by('slug' => '/about')`
|
30
|
-
def find_by(content_type:, filter: nil, options: nil)
|
31
|
-
if filter.keys == ['sys.id']
|
32
|
-
# Direct ID lookup, like what we do in `WCC::Contentful::ModelMethods.resolve`
|
33
|
-
# We can return just this item. Stores are not required to implement :include option.
|
34
|
-
if found = @cache.read(filter['sys.id'])
|
35
|
-
return found
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
q = find_all(content_type: content_type, options: { limit: 1 }.merge!(options || {}))
|
40
|
-
q = q.apply(filter) if filter
|
41
|
-
q.first
|
42
|
-
end
|
43
|
-
|
44
|
-
def find_all(content_type:, options: nil)
|
45
|
-
Query.new(
|
46
|
-
store: self,
|
47
|
-
client: @client,
|
48
|
-
relation: { content_type: content_type },
|
49
|
-
cache: @cache,
|
50
|
-
options: options
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
# #index is called whenever the sync API comes back with more data.
|
55
|
-
def index(json)
|
56
|
-
id = json.dig('sys', 'id')
|
57
|
-
return unless prev = @cache.read(id)
|
58
|
-
|
59
|
-
if (prev_rev = prev&.dig('sys', 'revision')) && (next_rev = json.dig('sys', 'revision'))
|
60
|
-
return prev if next_rev < prev_rev
|
61
|
-
end
|
62
|
-
|
63
|
-
# we also set deletes in the cache - no need to go hit the API when we know
|
64
|
-
# this is a nil object
|
65
|
-
ensure_hash json
|
66
|
-
@cache.write(id, json)
|
67
|
-
|
68
|
-
case json.dig('sys', 'type')
|
69
|
-
when 'DeletedEntry', 'DeletedAsset'
|
70
|
-
nil
|
71
|
-
else
|
72
|
-
json
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def set(key, value)
|
77
|
-
ensure_hash value
|
78
|
-
old = @cache.read(key)
|
79
|
-
@cache.write(key, value)
|
80
|
-
old
|
81
|
-
end
|
82
|
-
|
83
|
-
def delete(key)
|
84
|
-
old = @cache.read(key)
|
85
|
-
@cache.delete(key)
|
86
|
-
old
|
87
|
-
end
|
88
|
-
|
89
|
-
def nil_obj(id)
|
90
|
-
{
|
91
|
-
'sys' => {
|
92
|
-
'id' => id,
|
93
|
-
'type' => 'Nil',
|
94
|
-
'revision' => 1
|
95
|
-
}
|
96
|
-
}
|
97
|
-
end
|
98
|
-
|
99
|
-
def ensure_hash(val)
|
100
|
-
raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
|
101
|
-
end
|
102
|
-
|
103
|
-
class Query < CDNAdapter::Query
|
104
|
-
def initialize(cache:, **extra)
|
105
|
-
super(cache: cache, **extra)
|
106
|
-
@cache = cache
|
107
|
-
end
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
def response
|
112
|
-
# Disabling because the superclass already took `@response`
|
113
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
114
|
-
@wrapped_response ||= ResponseWrapper.new(super, @cache)
|
115
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
116
|
-
end
|
117
|
-
|
118
|
-
ResponseWrapper =
|
119
|
-
Struct.new(:response, :cache) do
|
120
|
-
delegate :count, to: :response
|
121
|
-
|
122
|
-
def items
|
123
|
-
@items ||=
|
124
|
-
response.items.map do |item|
|
125
|
-
id = item.dig('sys', 'id')
|
126
|
-
prev = cache.read(id)
|
127
|
-
unless (prev_rev = prev&.dig('sys', 'revision')) &&
|
128
|
-
(next_rev = item.dig('sys', 'revision')) &&
|
129
|
-
next_rev < prev_rev
|
130
|
-
|
131
|
-
cache.write(id, item)
|
132
|
-
end
|
133
|
-
|
134
|
-
item
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def includes
|
139
|
-
@includes ||= IncludesWrapper.new(response, cache)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
IncludesWrapper =
|
144
|
-
Struct.new(:response, :cache) do
|
145
|
-
def [](id)
|
146
|
-
return unless item = response.includes[id]
|
147
|
-
|
148
|
-
prev = cache.read(id)
|
149
|
-
unless (prev_rev = prev&.dig('sys', 'revision')) &&
|
150
|
-
(next_rev = item.dig('sys', 'revision')) &&
|
151
|
-
next_rev < prev_rev
|
152
|
-
|
153
|
-
cache.write(id, item)
|
154
|
-
end
|
155
|
-
|
156
|
-
item
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|