wcc-contentful 0.4.0.pre.beta → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Guardfile +43 -0
- data/README.md +205 -17
- data/Rakefile +5 -0
- data/app/controllers/wcc/contentful/webhook_controller.rb +25 -24
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +36 -2
- data/config/routes.rb +1 -1
- data/doc-static/wcc-contentful.png +0 -0
- data/lib/tasks/download_schema.rake +12 -0
- data/lib/wcc/contentful.rb +70 -16
- 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 -0
- 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 -3
- 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 +13 -8
- 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 +253 -107
- 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/attributes.rb +17 -5
- data/lib/wcc/contentful/test/factory.rb +22 -46
- data/lib/wcc/contentful/version.rb +1 -1
- data/wcc-contentful.gemspec +22 -11
- metadata +295 -144
- data/Gemfile +0 -6
- data/app/jobs/wcc/contentful/delayed_sync_job.rb +0 -63
- 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/simple_client/http_adapter.rb +0 -24
- data/lib/wcc/contentful/store/lazy_cache_store.rb +0 -161
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wcc/contentful/event'
|
4
|
+
require 'wisper'
|
5
|
+
|
6
|
+
begin
|
7
|
+
gem 'activejob'
|
8
|
+
require 'active_job'
|
9
|
+
rescue Gem::LoadError # rubocop:disable Lint/HandleExceptions
|
10
|
+
# suppress
|
11
|
+
end
|
12
|
+
|
13
|
+
module WCC::Contentful
|
14
|
+
# The SyncEngine is used to keep the currently configured store up to date
|
15
|
+
# using the Sync API. It is available on the WCC::Contentful::Services instance,
|
16
|
+
# and the application is responsible to periodically call #next in order to hit
|
17
|
+
# the sync API and update the store.
|
18
|
+
#
|
19
|
+
# If you have mounted the WCC::Contentful::Engine, AND the configured store is
|
20
|
+
# one that can be synced (i.e. it responds to `:index`), then
|
21
|
+
# the WCC::Contentful::WebhookController will call #next automatically anytime
|
22
|
+
# a webhook is received. Otherwise you should hook up to the Webhook events
|
23
|
+
# and call the sync engine via your initializer:
|
24
|
+
# WCC::Contentful::Events.subscribe(proc do |event|
|
25
|
+
# WCC::Contentful::Services.instance.sync_engine.next(up_to: event.dig('sys', 'id'))
|
26
|
+
# end, with: :call)
|
27
|
+
class SyncEngine
|
28
|
+
include ::Wisper::Publisher
|
29
|
+
|
30
|
+
def state
|
31
|
+
(@state&.dup || token_wrapper_factory(nil)).freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :store
|
35
|
+
attr_reader :client
|
36
|
+
|
37
|
+
def should_sync?
|
38
|
+
store&.index?
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(state: nil, store: nil, client: nil, key: nil)
|
42
|
+
@state_key = key || "sync:#{object_id}"
|
43
|
+
@client = client || WCC::Contentful::Services.instance.client
|
44
|
+
@mutex = Mutex.new
|
45
|
+
|
46
|
+
if store
|
47
|
+
unless %i[index index? find].all? { |m| store.respond_to?(m) }
|
48
|
+
raise ArgumentError, ':store param must implement the Store interface'
|
49
|
+
end
|
50
|
+
|
51
|
+
@store = store
|
52
|
+
@state = read_state if should_sync?
|
53
|
+
end
|
54
|
+
if state
|
55
|
+
@state = token_wrapper_factory(state)
|
56
|
+
raise ArgumentError, ':state param must be a String or Hash' unless @state.is_a? Hash
|
57
|
+
unless @state.dig('sys', 'type') == 'token'
|
58
|
+
raise ArgumentError, ':state param must be of sys.type = "token"'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
raise ArgumentError, 'either :state or :store must be provided' unless @state || @store
|
62
|
+
end
|
63
|
+
|
64
|
+
# Gets the next increment of data from the Sync API.
|
65
|
+
# If the configured store responds to `:index`, that will be called with each
|
66
|
+
# item in the Sync response to update the store.
|
67
|
+
# If a block is passed, that block will be evaluated with each item in the
|
68
|
+
# response.
|
69
|
+
# @param [String] up_to_id An ID to look for in the response. The method returns
|
70
|
+
# true if the ID was found or no up_to_id was given, false if the ID did not come back.
|
71
|
+
# @return [Array] A `[Boolean, Integer]` tuple where the first value is whether the ID was found,
|
72
|
+
# and the second value is the number of items returned.
|
73
|
+
def next(up_to_id: nil)
|
74
|
+
id_found = up_to_id.nil?
|
75
|
+
all_events = []
|
76
|
+
|
77
|
+
@mutex.synchronize do
|
78
|
+
@state ||= read_state || token_wrapper_factory(nil)
|
79
|
+
next_sync_token = @state['token']
|
80
|
+
|
81
|
+
sync_resp = client.sync(sync_token: next_sync_token)
|
82
|
+
sync_resp.items.each do |item|
|
83
|
+
id = item.dig('sys', 'id')
|
84
|
+
id_found ||= id == up_to_id
|
85
|
+
|
86
|
+
store.index(item) if store&.index?
|
87
|
+
event = WCC::Contentful::Event.from_raw(item, source: self)
|
88
|
+
yield(event) if block_given?
|
89
|
+
emit_event(event)
|
90
|
+
all_events << event
|
91
|
+
end
|
92
|
+
|
93
|
+
@state = @state.merge('token' => sync_resp.next_sync_token)
|
94
|
+
write_state
|
95
|
+
end
|
96
|
+
|
97
|
+
emit_sync_complete(all_events)
|
98
|
+
|
99
|
+
[id_found, all_events.length]
|
100
|
+
end
|
101
|
+
|
102
|
+
def emit_event(event)
|
103
|
+
type = event.dig('sys', 'type')
|
104
|
+
raise ArgumentError, "Unknown event type #{event}" unless type.present?
|
105
|
+
|
106
|
+
broadcast(type, event)
|
107
|
+
end
|
108
|
+
|
109
|
+
def emit_sync_complete(events)
|
110
|
+
event = WCC::Contentful::Event::SyncComplete.new(events, source: self)
|
111
|
+
broadcast('SyncComplete', event)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def read_state
|
117
|
+
return unless found = store&.find(@state_key)
|
118
|
+
|
119
|
+
# backwards compat - migrate existing state
|
120
|
+
token_wrapper_factory(found)
|
121
|
+
end
|
122
|
+
|
123
|
+
def write_state
|
124
|
+
store.index(@state) if store&.index?
|
125
|
+
end
|
126
|
+
|
127
|
+
def token_wrapper_factory(state)
|
128
|
+
state = { 'token' => state } unless state.is_a? Hash
|
129
|
+
|
130
|
+
state.merge!('sys' => { 'id' => @state_key, 'type' => 'token' }) unless state['sys']
|
131
|
+
state
|
132
|
+
end
|
133
|
+
|
134
|
+
# Define the job only if rails is loaded
|
135
|
+
if defined?(ActiveJob)
|
136
|
+
# This job uses the Contentful Sync API to update the configured store with
|
137
|
+
# the latest data from Contentful.
|
138
|
+
class Job < ActiveJob::Base
|
139
|
+
include WCC::Contentful::ServiceAccessors
|
140
|
+
|
141
|
+
self.queue_adapter = :async
|
142
|
+
queue_as :default
|
143
|
+
|
144
|
+
def perform(event = nil)
|
145
|
+
return unless sync_engine&.should_sync?
|
146
|
+
|
147
|
+
up_to_id = nil
|
148
|
+
up_to_id = event[:up_to_id] || event.dig('sys', 'id') if event
|
149
|
+
sync!(up_to_id: up_to_id)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Calls the Contentful Sync API and updates the configured store with the returned
|
153
|
+
# data.
|
154
|
+
#
|
155
|
+
# @param [String] up_to_id
|
156
|
+
# An ID that we know has changed and should come back from the sync.
|
157
|
+
# If we don't find this ID in the sync data, then drop a job to try
|
158
|
+
# the sync again after a few minutes.
|
159
|
+
#
|
160
|
+
def sync!(up_to_id: nil)
|
161
|
+
id_found, count = sync_engine.next(up_to_id: up_to_id)
|
162
|
+
|
163
|
+
next_sync_token = sync_engine.state['token']
|
164
|
+
|
165
|
+
logger.info "Synced #{count} entries. Next sync token:\n #{next_sync_token}"
|
166
|
+
logger.info "Should enqueue again? [#{!id_found}]"
|
167
|
+
# Passing nil to only enqueue the job 1 more time
|
168
|
+
sync_later!(up_to_id: nil) unless id_found
|
169
|
+
next_sync_token
|
170
|
+
end
|
171
|
+
|
172
|
+
# Drops an ActiveJob job to invoke WCC::Contentful.sync! after a given amount
|
173
|
+
# of time.
|
174
|
+
def sync_later!(up_to_id: nil, wait: 10.minutes)
|
175
|
+
self.class.set(wait: wait)
|
176
|
+
.perform_later(up_to_id: up_to_id)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -5,10 +5,22 @@ module WCC::Contentful::Test::Attributes
|
|
5
5
|
String: 'test',
|
6
6
|
Int: 0,
|
7
7
|
Float: 0.0,
|
8
|
-
DateTime: Time.at(0),
|
8
|
+
DateTime: Time.at(0).to_s,
|
9
9
|
Boolean: false,
|
10
|
-
Json: -> {
|
11
|
-
Coordinates: -> {
|
10
|
+
Json: ->(_f) { {} },
|
11
|
+
Coordinates: ->(_f) { {} },
|
12
|
+
Asset: ->(f) {
|
13
|
+
WCC::Contentful::Link.new(
|
14
|
+
"fake-#{f.name}-#{SecureRandom.urlsafe_base64[1..6]}",
|
15
|
+
:Asset
|
16
|
+
).raw
|
17
|
+
},
|
18
|
+
Link: ->(f) {
|
19
|
+
WCC::Contentful::Link.new(
|
20
|
+
"fake-#{f.name}-#{SecureRandom.urlsafe_base64[1..6]}",
|
21
|
+
:Link
|
22
|
+
).raw
|
23
|
+
}
|
12
24
|
}.freeze
|
13
25
|
|
14
26
|
class << self
|
@@ -35,8 +47,8 @@ module WCC::Contentful::Test::Attributes
|
|
35
47
|
return [] if field.array
|
36
48
|
return unless field.required
|
37
49
|
|
38
|
-
val = DEFAULTS[field]
|
39
|
-
return val.call if val.respond_to?(:call)
|
50
|
+
val = DEFAULTS[field.type]
|
51
|
+
return val.call(field) if val.respond_to?(:call)
|
40
52
|
|
41
53
|
val
|
42
54
|
end
|
@@ -7,62 +7,38 @@ module WCC::Contentful::Test::Factory
|
|
7
7
|
# Builds a in-memory instance of the Contentful model for the given content_type.
|
8
8
|
# All attributes that are known to be required fields on the content type
|
9
9
|
# will return a default value based on the field type.
|
10
|
-
def contentful_create(content_type, **attrs)
|
10
|
+
def contentful_create(content_type, context = nil, **attrs)
|
11
11
|
const = WCC::Contentful::Model.resolve_constant(content_type.to_s)
|
12
12
|
attrs = attrs.transform_keys { |a| a.to_s.camelize(:lower) }
|
13
13
|
|
14
14
|
id = attrs.delete('id')
|
15
|
+
sys = attrs.delete('sys')
|
16
|
+
raw = attrs.delete('raw') || default_raw(const, id)
|
15
17
|
bad_attrs = attrs.reject { |a| const.content_type_definition.fields.key?(a) }
|
16
18
|
raise ArgumentError, "Attribute(s) do not exist on #{const}: #{bad_attrs.keys}" if bad_attrs.any?
|
17
19
|
|
18
|
-
|
19
|
-
attrs.each do |k, v|
|
20
|
-
field = const.content_type_definition.fields[k]
|
20
|
+
raw['sys'].merge!(sys) if sys
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
raw = to_raw(v, field.type)
|
22
|
+
attrs.each do |k, v|
|
23
|
+
field = const.content_type_definition.fields[k]
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
raw_value = v
|
26
|
+
raw_value = to_raw(v, field.type) if %i[Asset Link].include?(field.type)
|
27
|
+
raw['fields'][field.name][raw.dig('sys', 'locale')] = raw_value
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
instance.instance_variable_set("@#{field.name}", raw)
|
33
|
-
end
|
30
|
+
instance = const.new(raw, context)
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
32
|
+
attrs.each do |k, v|
|
33
|
+
field = const.content_type_definition.fields[k]
|
34
|
+
next unless %i[Asset Link].include?(field.type)
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
attr_reader :raw
|
45
|
-
|
46
|
-
LINK_TYPES = {
|
47
|
-
Asset: 'Asset',
|
48
|
-
Link: 'Entry'
|
49
|
-
}.freeze
|
50
|
-
|
51
|
-
def initialize(model, link_type = nil)
|
52
|
-
@id = model.try(:id) || model
|
53
|
-
@link_type = link_type
|
54
|
-
@link_type ||= model.is_a?(WCC::Contentful::Model::Asset) ? :Asset : :Link
|
55
|
-
@raw =
|
56
|
-
{
|
57
|
-
'sys' => {
|
58
|
-
'type' => 'Link',
|
59
|
-
'linkType' => LINK_TYPES[@link_type],
|
60
|
-
'id' => @id
|
61
|
-
}
|
62
|
-
}
|
36
|
+
unless field.array ? v.any? { |i| i.is_a?(String) } : v.is_a?(String)
|
37
|
+
instance.instance_variable_set("@#{field.name}_resolved", v)
|
38
|
+
end
|
63
39
|
end
|
64
40
|
|
65
|
-
|
41
|
+
instance
|
66
42
|
end
|
67
43
|
|
68
44
|
private
|
@@ -72,10 +48,10 @@ module WCC::Contentful::Test::Factory
|
|
72
48
|
end
|
73
49
|
|
74
50
|
def default_raw(model, id = nil)
|
75
|
-
{ sys:
|
51
|
+
{ sys: contentful_sys(model, id), fields: contentful_fields(model) }.as_json
|
76
52
|
end
|
77
53
|
|
78
|
-
def
|
54
|
+
def contentful_sys(model, id = nil)
|
79
55
|
{
|
80
56
|
space: {
|
81
57
|
sys: {
|
@@ -107,7 +83,7 @@ module WCC::Contentful::Test::Factory
|
|
107
83
|
}
|
108
84
|
end
|
109
85
|
|
110
|
-
def
|
86
|
+
def contentful_fields(model)
|
111
87
|
WCC::Contentful::Test::Attributes.defaults(model).each_with_object({}) do |(k, v), h|
|
112
88
|
h[k] = { 'en-US' => v }
|
113
89
|
end
|
@@ -117,7 +93,7 @@ module WCC::Contentful::Test::Factory
|
|
117
93
|
if val.is_a? Array
|
118
94
|
val.map { |i| to_raw(i, field_type) }
|
119
95
|
elsif val.is_a? String
|
120
|
-
Link.new(val, field_type).raw
|
96
|
+
WCC::Contentful::Link.new(val, field_type).raw
|
121
97
|
elsif val
|
122
98
|
val.raw
|
123
99
|
end
|
data/wcc-contentful.gemspec
CHANGED
@@ -4,6 +4,9 @@ lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'wcc/contentful/version'
|
6
6
|
|
7
|
+
doc_version = Gem::Version.new(WCC::Contentful::VERSION).release.to_s.sub(/\.\d+$/, '')
|
8
|
+
|
9
|
+
# rubocop:disable Metrics/LineLength
|
7
10
|
Gem::Specification.new do |spec|
|
8
11
|
spec.name = 'wcc-contentful'
|
9
12
|
spec.version = WCC::Contentful::VERSION
|
@@ -15,6 +18,10 @@ Gem::Specification.new do |spec|
|
|
15
18
|
spec.homepage = 'https://github.com/watermarkchurch/wcc-contentful/wcc-contentful'
|
16
19
|
spec.license = 'MIT'
|
17
20
|
|
21
|
+
spec.metadata = {
|
22
|
+
'documentation_uri' => "https://watermarkchurch.github.io/wcc-contentful/#{doc_version}/wcc-contentful"
|
23
|
+
}
|
24
|
+
|
18
25
|
spec.required_ruby_version = '>= 2.3'
|
19
26
|
|
20
27
|
spec.files =
|
@@ -24,39 +31,43 @@ Gem::Specification.new do |spec|
|
|
24
31
|
|
25
32
|
spec.require_paths = ['lib']
|
26
33
|
|
34
|
+
spec.add_development_dependency 'byebug', '~> 11.0.1'
|
27
35
|
spec.add_development_dependency 'coveralls'
|
28
36
|
spec.add_development_dependency 'dotenv', '~> 2.2'
|
37
|
+
spec.add_development_dependency 'erb_lint', '~> 0.0.26'
|
29
38
|
spec.add_development_dependency 'httplog', '~> 1.0'
|
30
|
-
spec.add_development_dependency 'rake', '~>
|
39
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
31
40
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
-
spec.add_development_dependency '
|
33
|
-
spec.add_development_dependency '
|
41
|
+
spec.add_development_dependency 'rspec-instrumentation-matcher'
|
42
|
+
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
|
43
|
+
spec.add_development_dependency 'rubocop', '0.68'
|
34
44
|
spec.add_development_dependency 'simplecov', '~> 0.16.1'
|
35
|
-
spec.add_development_dependency 'vcr', '~>
|
45
|
+
spec.add_development_dependency 'vcr', '~> 5.0'
|
36
46
|
spec.add_development_dependency 'webmock', '~> 3.0'
|
47
|
+
spec.add_development_dependency 'wisper-rspec'
|
37
48
|
|
38
49
|
# Makes testing easy via `bundle exec guard`
|
39
50
|
spec.add_development_dependency 'guard', '~> 2.14'
|
40
51
|
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
41
52
|
spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
|
53
|
+
spec.add_development_dependency 'guard-shell', '~> 0.7.1'
|
42
54
|
|
43
55
|
# for generators
|
44
56
|
spec.add_development_dependency 'generator_spec', '~> 0.9.4'
|
45
|
-
spec.add_development_dependency 'rails', '~> 5.
|
46
|
-
spec.add_development_dependency 'rspec-rails', '~> 3.7'
|
47
|
-
spec.add_development_dependency 'sqlite3'
|
57
|
+
# spec.add_development_dependency 'rails', '~> 5.0'
|
58
|
+
# spec.add_development_dependency 'rspec-rails', '~> 3.7'
|
59
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.6'
|
48
60
|
spec.add_development_dependency 'timecop', '~> 0.9.1'
|
49
61
|
|
50
62
|
# optional dependencies
|
51
63
|
spec.add_development_dependency 'connection_pool', '~> 2.2'
|
52
|
-
spec.add_development_dependency '
|
53
|
-
spec.add_development_dependency 'contentful-management', '2.0.2'
|
54
|
-
spec.add_development_dependency 'graphql', '~> 1.7'
|
64
|
+
spec.add_development_dependency 'faraday', '~> 0.9'
|
55
65
|
spec.add_development_dependency 'http', '> 1.0', '< 3.0'
|
56
66
|
spec.add_development_dependency 'pg', '~> 1.0'
|
57
67
|
spec.add_development_dependency 'typhoeus', '~> 1.3'
|
58
68
|
|
59
69
|
spec.add_dependency 'activesupport', '>= 5'
|
60
|
-
spec.add_dependency 'dry-validation', '~> 0.11.1'
|
61
70
|
spec.add_dependency 'wcc-base', '~> 0.3.1'
|
71
|
+
spec.add_dependency 'wisper', '~> 2.0.0'
|
62
72
|
end
|
73
|
+
# rubocop:enable Metrics/LineLength
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wcc-contentful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Watermark Dev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: byebug
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 11.0.1
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 11.0.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: coveralls
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '2.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: erb_lint
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.26
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.26
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: httplog
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +86,14 @@ dependencies:
|
|
58
86
|
requirements:
|
59
87
|
- - "~>"
|
60
88
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
89
|
+
version: '13.0'
|
62
90
|
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
94
|
- - "~>"
|
67
95
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
96
|
+
version: '13.0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rspec
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,34 +108,48 @@ dependencies:
|
|
80
108
|
- - "~>"
|
81
109
|
- !ruby/object:Gem::Version
|
82
110
|
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-instrumentation-matcher
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
83
125
|
- !ruby/object:Gem::Dependency
|
84
126
|
name: rspec_junit_formatter
|
85
127
|
requirement: !ruby/object:Gem::Requirement
|
86
128
|
requirements:
|
87
129
|
- - "~>"
|
88
130
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
131
|
+
version: 0.4.1
|
90
132
|
type: :development
|
91
133
|
prerelease: false
|
92
134
|
version_requirements: !ruby/object:Gem::Requirement
|
93
135
|
requirements:
|
94
136
|
- - "~>"
|
95
137
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
138
|
+
version: 0.4.1
|
97
139
|
- !ruby/object:Gem::Dependency
|
98
140
|
name: rubocop
|
99
141
|
requirement: !ruby/object:Gem::Requirement
|
100
142
|
requirements:
|
101
|
-
- -
|
143
|
+
- - '='
|
102
144
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0.
|
145
|
+
version: '0.68'
|
104
146
|
type: :development
|
105
147
|
prerelease: false
|
106
148
|
version_requirements: !ruby/object:Gem::Requirement
|
107
149
|
requirements:
|
108
|
-
- -
|
150
|
+
- - '='
|
109
151
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0.
|
152
|
+
version: '0.68'
|
111
153
|
- !ruby/object:Gem::Dependency
|
112
154
|
name: simplecov
|
113
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,14 +170,14 @@ dependencies:
|
|
128
170
|
requirements:
|
129
171
|
- - "~>"
|
130
172
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
173
|
+
version: '5.0'
|
132
174
|
type: :development
|
133
175
|
prerelease: false
|
134
176
|
version_requirements: !ruby/object:Gem::Requirement
|
135
177
|
requirements:
|
136
178
|
- - "~>"
|
137
179
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
180
|
+
version: '5.0'
|
139
181
|
- !ruby/object:Gem::Dependency
|
140
182
|
name: webmock
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +192,20 @@ dependencies:
|
|
150
192
|
- - "~>"
|
151
193
|
- !ruby/object:Gem::Version
|
152
194
|
version: '3.0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: wisper-rspec
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
153
209
|
- !ruby/object:Gem::Dependency
|
154
210
|
name: guard
|
155
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -193,61 +249,47 @@ dependencies:
|
|
193
249
|
- !ruby/object:Gem::Version
|
194
250
|
version: 1.3.0
|
195
251
|
- !ruby/object:Gem::Dependency
|
196
|
-
name:
|
252
|
+
name: guard-shell
|
197
253
|
requirement: !ruby/object:Gem::Requirement
|
198
254
|
requirements:
|
199
255
|
- - "~>"
|
200
256
|
- !ruby/object:Gem::Version
|
201
|
-
version: 0.
|
257
|
+
version: 0.7.1
|
202
258
|
type: :development
|
203
259
|
prerelease: false
|
204
260
|
version_requirements: !ruby/object:Gem::Requirement
|
205
261
|
requirements:
|
206
262
|
- - "~>"
|
207
263
|
- !ruby/object:Gem::Version
|
208
|
-
version: 0.
|
264
|
+
version: 0.7.1
|
209
265
|
- !ruby/object:Gem::Dependency
|
210
|
-
name:
|
266
|
+
name: generator_spec
|
211
267
|
requirement: !ruby/object:Gem::Requirement
|
212
268
|
requirements:
|
213
269
|
- - "~>"
|
214
270
|
- !ruby/object:Gem::Version
|
215
|
-
version:
|
271
|
+
version: 0.9.4
|
216
272
|
type: :development
|
217
273
|
prerelease: false
|
218
274
|
version_requirements: !ruby/object:Gem::Requirement
|
219
275
|
requirements:
|
220
276
|
- - "~>"
|
221
277
|
- !ruby/object:Gem::Version
|
222
|
-
version:
|
278
|
+
version: 0.9.4
|
223
279
|
- !ruby/object:Gem::Dependency
|
224
|
-
name:
|
280
|
+
name: sqlite3
|
225
281
|
requirement: !ruby/object:Gem::Requirement
|
226
282
|
requirements:
|
227
283
|
- - "~>"
|
228
284
|
- !ruby/object:Gem::Version
|
229
|
-
version:
|
285
|
+
version: 1.3.6
|
230
286
|
type: :development
|
231
287
|
prerelease: false
|
232
288
|
version_requirements: !ruby/object:Gem::Requirement
|
233
289
|
requirements:
|
234
290
|
- - "~>"
|
235
291
|
- !ruby/object:Gem::Version
|
236
|
-
version:
|
237
|
-
- !ruby/object:Gem::Dependency
|
238
|
-
name: sqlite3
|
239
|
-
requirement: !ruby/object:Gem::Requirement
|
240
|
-
requirements:
|
241
|
-
- - ">="
|
242
|
-
- !ruby/object:Gem::Version
|
243
|
-
version: '0'
|
244
|
-
type: :development
|
245
|
-
prerelease: false
|
246
|
-
version_requirements: !ruby/object:Gem::Requirement
|
247
|
-
requirements:
|
248
|
-
- - ">="
|
249
|
-
- !ruby/object:Gem::Version
|
250
|
-
version: '0'
|
292
|
+
version: 1.3.6
|
251
293
|
- !ruby/object:Gem::Dependency
|
252
294
|
name: timecop
|
253
295
|
requirement: !ruby/object:Gem::Requirement
|
@@ -277,47 +319,19 @@ dependencies:
|
|
277
319
|
- !ruby/object:Gem::Version
|
278
320
|
version: '2.2'
|
279
321
|
- !ruby/object:Gem::Dependency
|
280
|
-
name:
|
281
|
-
requirement: !ruby/object:Gem::Requirement
|
282
|
-
requirements:
|
283
|
-
- - '='
|
284
|
-
- !ruby/object:Gem::Version
|
285
|
-
version: 2.6.0
|
286
|
-
type: :development
|
287
|
-
prerelease: false
|
288
|
-
version_requirements: !ruby/object:Gem::Requirement
|
289
|
-
requirements:
|
290
|
-
- - '='
|
291
|
-
- !ruby/object:Gem::Version
|
292
|
-
version: 2.6.0
|
293
|
-
- !ruby/object:Gem::Dependency
|
294
|
-
name: contentful-management
|
295
|
-
requirement: !ruby/object:Gem::Requirement
|
296
|
-
requirements:
|
297
|
-
- - '='
|
298
|
-
- !ruby/object:Gem::Version
|
299
|
-
version: 2.0.2
|
300
|
-
type: :development
|
301
|
-
prerelease: false
|
302
|
-
version_requirements: !ruby/object:Gem::Requirement
|
303
|
-
requirements:
|
304
|
-
- - '='
|
305
|
-
- !ruby/object:Gem::Version
|
306
|
-
version: 2.0.2
|
307
|
-
- !ruby/object:Gem::Dependency
|
308
|
-
name: graphql
|
322
|
+
name: faraday
|
309
323
|
requirement: !ruby/object:Gem::Requirement
|
310
324
|
requirements:
|
311
325
|
- - "~>"
|
312
326
|
- !ruby/object:Gem::Version
|
313
|
-
version: '
|
327
|
+
version: '0.9'
|
314
328
|
type: :development
|
315
329
|
prerelease: false
|
316
330
|
version_requirements: !ruby/object:Gem::Requirement
|
317
331
|
requirements:
|
318
332
|
- - "~>"
|
319
333
|
- !ruby/object:Gem::Version
|
320
|
-
version: '
|
334
|
+
version: '0.9'
|
321
335
|
- !ruby/object:Gem::Dependency
|
322
336
|
name: http
|
323
337
|
requirement: !ruby/object:Gem::Requirement
|
@@ -381,33 +395,33 @@ dependencies:
|
|
381
395
|
- !ruby/object:Gem::Version
|
382
396
|
version: '5'
|
383
397
|
- !ruby/object:Gem::Dependency
|
384
|
-
name:
|
398
|
+
name: wcc-base
|
385
399
|
requirement: !ruby/object:Gem::Requirement
|
386
400
|
requirements:
|
387
401
|
- - "~>"
|
388
402
|
- !ruby/object:Gem::Version
|
389
|
-
version: 0.
|
403
|
+
version: 0.3.1
|
390
404
|
type: :runtime
|
391
405
|
prerelease: false
|
392
406
|
version_requirements: !ruby/object:Gem::Requirement
|
393
407
|
requirements:
|
394
408
|
- - "~>"
|
395
409
|
- !ruby/object:Gem::Version
|
396
|
-
version: 0.
|
410
|
+
version: 0.3.1
|
397
411
|
- !ruby/object:Gem::Dependency
|
398
|
-
name:
|
412
|
+
name: wisper
|
399
413
|
requirement: !ruby/object:Gem::Requirement
|
400
414
|
requirements:
|
401
415
|
- - "~>"
|
402
416
|
- !ruby/object:Gem::Version
|
403
|
-
version: 0.
|
417
|
+
version: 2.0.0
|
404
418
|
type: :runtime
|
405
419
|
prerelease: false
|
406
420
|
version_requirements: !ruby/object:Gem::Requirement
|
407
421
|
requirements:
|
408
422
|
- - "~>"
|
409
423
|
- !ruby/object:Gem::Version
|
410
|
-
version: 0.
|
424
|
+
version: 2.0.0
|
411
425
|
description: Contentful API wrapper library exposing an ActiveRecord-like interface
|
412
426
|
email:
|
413
427
|
- dev@watermark.org
|
@@ -416,12 +430,11 @@ extensions: []
|
|
416
430
|
extra_rdoc_files: []
|
417
431
|
files:
|
418
432
|
- ".rspec"
|
419
|
-
- Gemfile
|
420
433
|
- Guardfile
|
421
434
|
- README.md
|
435
|
+
- Rakefile
|
422
436
|
- app/controllers/wcc/contentful/application_controller.rb
|
423
437
|
- app/controllers/wcc/contentful/webhook_controller.rb
|
424
|
-
- app/jobs/wcc/contentful/delayed_sync_job.rb
|
425
438
|
- app/jobs/wcc/contentful/webhook_enable_job.rb
|
426
439
|
- bin/console
|
427
440
|
- bin/rails
|
@@ -429,35 +442,55 @@ files:
|
|
429
442
|
- bin/setup
|
430
443
|
- config/initializers/mime_types.rb
|
431
444
|
- config/routes.rb
|
445
|
+
- doc-static/wcc-contentful.png
|
446
|
+
- lib/tasks/download_schema.rake
|
432
447
|
- lib/wcc/contentful.rb
|
433
|
-
- lib/wcc/contentful/
|
448
|
+
- lib/wcc/contentful/active_record_shim.rb
|
434
449
|
- lib/wcc/contentful/configuration.rb
|
435
450
|
- lib/wcc/contentful/content_type_indexer.rb
|
451
|
+
- lib/wcc/contentful/downloads_schema.rb
|
436
452
|
- lib/wcc/contentful/engine.rb
|
453
|
+
- lib/wcc/contentful/event.rb
|
454
|
+
- lib/wcc/contentful/events.rb
|
437
455
|
- lib/wcc/contentful/exceptions.rb
|
438
|
-
- lib/wcc/contentful/graphql.rb
|
439
|
-
- lib/wcc/contentful/graphql/builder.rb
|
440
|
-
- lib/wcc/contentful/graphql/types.rb
|
441
456
|
- lib/wcc/contentful/helpers.rb
|
442
457
|
- lib/wcc/contentful/indexed_representation.rb
|
458
|
+
- lib/wcc/contentful/instrumentation.rb
|
459
|
+
- lib/wcc/contentful/link.rb
|
460
|
+
- lib/wcc/contentful/link_visitor.rb
|
461
|
+
- lib/wcc/contentful/middleware.rb
|
462
|
+
- lib/wcc/contentful/middleware/store.rb
|
463
|
+
- lib/wcc/contentful/middleware/store/caching_middleware.rb
|
443
464
|
- lib/wcc/contentful/model.rb
|
444
465
|
- lib/wcc/contentful/model_builder.rb
|
445
466
|
- lib/wcc/contentful/model_methods.rb
|
446
467
|
- lib/wcc/contentful/model_singleton_methods.rb
|
447
468
|
- lib/wcc/contentful/rails.rb
|
469
|
+
- lib/wcc/contentful/rake.rb
|
448
470
|
- lib/wcc/contentful/rspec.rb
|
449
471
|
- lib/wcc/contentful/services.rb
|
450
472
|
- lib/wcc/contentful/simple_client.rb
|
451
|
-
- lib/wcc/contentful/simple_client/http_adapter.rb
|
452
473
|
- lib/wcc/contentful/simple_client/management.rb
|
453
474
|
- lib/wcc/contentful/simple_client/response.rb
|
454
475
|
- lib/wcc/contentful/simple_client/typhoeus_adapter.rb
|
455
476
|
- lib/wcc/contentful/store.rb
|
477
|
+
- lib/wcc/contentful/store/README.md
|
456
478
|
- lib/wcc/contentful/store/base.rb
|
457
479
|
- lib/wcc/contentful/store/cdn_adapter.rb
|
458
|
-
- lib/wcc/contentful/store/
|
480
|
+
- lib/wcc/contentful/store/factory.rb
|
481
|
+
- lib/wcc/contentful/store/instrumentation.rb
|
482
|
+
- lib/wcc/contentful/store/interface.rb
|
459
483
|
- lib/wcc/contentful/store/memory_store.rb
|
460
484
|
- lib/wcc/contentful/store/postgres_store.rb
|
485
|
+
- lib/wcc/contentful/store/postgres_store/schema_1.sql
|
486
|
+
- lib/wcc/contentful/store/postgres_store/schema_2.sql
|
487
|
+
- lib/wcc/contentful/store/query.rb
|
488
|
+
- lib/wcc/contentful/store/query/interface.rb
|
489
|
+
- lib/wcc/contentful/store/rspec_examples.rb
|
490
|
+
- lib/wcc/contentful/store/rspec_examples/basic_store.rb
|
491
|
+
- lib/wcc/contentful/store/rspec_examples/include_param.rb
|
492
|
+
- lib/wcc/contentful/store/rspec_examples/nested_queries.rb
|
493
|
+
- lib/wcc/contentful/sync_engine.rb
|
461
494
|
- lib/wcc/contentful/sys.rb
|
462
495
|
- lib/wcc/contentful/test.rb
|
463
496
|
- lib/wcc/contentful/test/attributes.rb
|
@@ -468,8 +501,9 @@ files:
|
|
468
501
|
homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
|
469
502
|
licenses:
|
470
503
|
- MIT
|
471
|
-
metadata:
|
472
|
-
|
504
|
+
metadata:
|
505
|
+
documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.0/wcc-contentful
|
506
|
+
post_install_message:
|
473
507
|
rdoc_options: []
|
474
508
|
require_paths:
|
475
509
|
- lib
|
@@ -480,28 +514,123 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
480
514
|
version: '2.3'
|
481
515
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
482
516
|
requirements:
|
483
|
-
- - "
|
517
|
+
- - ">="
|
484
518
|
- !ruby/object:Gem::Version
|
485
|
-
version:
|
519
|
+
version: '0'
|
486
520
|
requirements: []
|
487
|
-
rubyforge_project:
|
488
|
-
rubygems_version: 2.6.
|
489
|
-
signing_key:
|
521
|
+
rubyforge_project:
|
522
|
+
rubygems_version: 2.7.6.2
|
523
|
+
signing_key:
|
490
524
|
specification_version: 4
|
491
|
-
summary: '[](https://
|
492
|
-
[](https://rubygems.org/gems/wcc-contentful)
|
526
|
+
[](https://circleci.com/gh/watermarkchurch/wcc-contentful)
|
493
527
|
[](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master) Full
|
494
|
-
documentation: https://
|
495
|
-
WCC::Contentful
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
528
|
+
documentation: https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/ #
|
529
|
+
WCC::Contentful An alternative to Contentful''s [contentful.rb ruby client](https://github.com/contentful/contentful.rb/),
|
530
|
+
[contentful_model](https://github.com/contentful/contentful_model), and [contentful_rails](https://github.com/contentful/contentful_rails)
|
531
|
+
gems all in one. Table of Contents: 1. [Why?](#why-did-you-rewrite-the-contentful-ruby-stack)
|
532
|
+
2. [Installation](#installation) 3. [Configuration](#configure) 4. [Usage](#usage)
|
533
|
+
1. [Model API](#wcccontentfulmodel-api) 2. [Store API](#store-api) 3. [Direct CDN
|
534
|
+
client](#direct-cdn-api-simpleclient) 4. [Accessing the APIs](#accessing-the-apis-within-application-code)
|
535
|
+
5. [Architecture](#architecture) 6. [Test Helpers](#test-helpers) 7. [Advanced Configuration
|
536
|
+
Example](#advanced-configuration-example) 8. [Development](#development) 9. [Contributing](#contributing)
|
537
|
+
10. [License](#license) ## Why did you rewrite the Contentful ruby stack? We
|
538
|
+
started working with Contentful almost 3 years ago. Since that time, Contentful''s
|
539
|
+
ruby stack has improved, but there are still a number of pain points that we feel
|
540
|
+
we have addressed better with our gem. These are: * [Low-level caching](#low-level-caching)
|
541
|
+
* [Better integration with Rails & Rails models](#better-rails-integration) * [Automatic
|
542
|
+
pagination and Automatic link resolution](#automatic-pagination-and-link-resolution)
|
543
|
+
* [Automatic webhook management](#automatic-webhook-management) Our gem no longer
|
544
|
+
depends on any of the Contentful gems and interacts directly with the [Contentful
|
545
|
+
CDA](https://www.contentful.com/developers/docs/references/content-delivery-api/)
|
546
|
+
and [Content Management API](https://www.contentful.com/developers/docs/references/content-management-api/)
|
547
|
+
over HTTPS. ### Low-level caching The wcc-contentful gem enables caching at two
|
548
|
+
levels: the HTTP response using [Faraday HTTP cache middleware](https://github.com/sourcelevel/faraday-http-cache),
|
549
|
+
and at the Entry level using the Rails cache and the [Sync API](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/synchronization)
|
550
|
+
to keep it up to date. We''ve found these two cache layers to be very effective
|
551
|
+
at reducing both round trip latency to the Content Delivery API, as well as reducing
|
552
|
+
our monthly API request usage. (which reduces our overage charges. Hooray!) ####
|
553
|
+
At the request/response level By default, the contentful.rb gem requires the [HTTP
|
554
|
+
library](https://rubygems.org/gems/http). While simple and straightforward to use,
|
555
|
+
it is not as powerful for caching. We decided to make our client conform to the
|
556
|
+
[Faraday gem''s API](https://github.com/lostisland/faraday). If you prefer not
|
557
|
+
to use Faraday, you can choose to supply your own HTTP adapter that "quacks like"
|
558
|
+
Faraday (see the [TyphoeusAdapter](https://github.com/watermarkchurch/wcc-contentful/blob/master/wcc-contentful/lib/wcc/contentful/simple_client/typhoeus_adapter.rb)
|
559
|
+
for one implementation). Using Faraday makes it easy to add Middleware. As an
|
560
|
+
example, our flagship Rails app that powers watermark.org uses the following configuration
|
561
|
+
in Production, which provides us with instrumentation through statsd, logging, and
|
562
|
+
caching: ```rb config.connection = Faraday.new do |builder| builder.use :http_cache,
|
563
|
+
shared_cache: false, store: ActiveSupport::Cache::MemoryStore.new(size: 512.megabytes),
|
564
|
+
logger: Rails.logger, serializer: Marshal, instrumenter: ActiveSupport::Notifications builder.use
|
565
|
+
:gzip builder.response :logger, Rails.logger, headers: false, bodies: false if Rails.env.development?
|
566
|
+
builder.request :instrumentation builder.adapter :typhoeus end ``` #### At the
|
567
|
+
Entry level Our stack has three layers, the middle layer being essentially a cache
|
568
|
+
for individual Entry hashes parsed out of responses from the Delivery API. We were
|
569
|
+
able to add a caching layer here which stores entries retrieved over the Sync API,
|
570
|
+
and responds to queries with cached versions of local content when possible. We
|
571
|
+
consider this to be our best innovation on the Contentful ruby stack. We have successfully
|
572
|
+
created caching layers using Memcached, Postgres, and an in-memory hash. The architecture
|
573
|
+
allows other caching implementations to be created fairly easily, and we have a
|
574
|
+
set of rspec specs that can verify that a cache store behaves appropriately. For
|
575
|
+
more information, [see the documentation on the caching modes here](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Store.html). ###
|
576
|
+
Better Rails Integration When we initially got started with the Contentful ruby
|
577
|
+
models, we encountered one problem that was more frustrating than all others: If
|
578
|
+
a field exists in the content model, but the particular entry we''re working with
|
579
|
+
does not have that field populated, then accessing that field raised a `NoMethodError`. This
|
580
|
+
caused us to litter our code with `if defined?(entry.my_field)` which is bad practice. (Note:
|
581
|
+
this has since been fixed in contentful.rb v2). We decided it was better to not
|
582
|
+
rely on `method_missing?` (what contentful.rb does), and instead to use `define_method`
|
583
|
+
in an initializer to generate the methods for our models. This has the advantage
|
584
|
+
that calling `.instance_methods` on a model class includes all the fields present
|
585
|
+
in the content model. We also took advantage of Rails'' naming conventions to automatically
|
586
|
+
infer the content type name based on the class name. Thus in our code, we have
|
587
|
+
`app/models/page.rb` which defines `class Page << WCC::Contentful::Model::Page`,
|
588
|
+
and is automatically linked to the `page` content type ID. (Note: this is overridable
|
589
|
+
on a per-model basis) All our models are automatically generated at startup which
|
590
|
+
improves response times at the expense of initialization time. In addition, our
|
591
|
+
content model registry allows easy definition of custom models in your `app/models`
|
592
|
+
directory to override fields. This plays nice with other gems like algoliasearch-rails,
|
593
|
+
which allows you to declaratively manage your Algolia indexes. Another example
|
594
|
+
from our flagship watermark.org: ```rb class Page < WCC::Contentful::Model::Page
|
595
|
+
include AlgoliaSearch algoliasearch(index_name: ''pages'') do attribute(:title,
|
596
|
+
:slug) ... end ``` ### Automatic Pagination and Link Resolution Using the `contentful_model`
|
597
|
+
gem, calling `Page.all.load` does not give you all Page entries if there are more
|
598
|
+
than 100. To get the next page you must call `.paginate` on the response. By contrast,
|
599
|
+
`Page.find_all` in the `wcc-contentful` gem gives you a [Lazy Enumerator](https://ruby-doc.org/core-2.5.0/Enumerator/Lazy.html). As
|
600
|
+
you iterate past the 100th entry, the enumerator will automatically fetch the next
|
601
|
+
page. If you only enumerate 99 entries (say with `.take(99)`), then the second
|
602
|
+
page will never be fetched. Similarly, if your Page references an asset, say `hero_image`,
|
603
|
+
that field returns a `Link` object rather than the actual `Asset`. You must either
|
604
|
+
predefine how many links you need using `Page.load_children(3).all.load`, or detect
|
605
|
+
that `hero_image` is a `Link` like `if @page.hero_image.is_a? Contentful::Link`
|
606
|
+
and then call `.resolve` on the link. We found all of that to be too cumbersome
|
607
|
+
when we are down in a nested partial view template that may be invoked from multiple
|
608
|
+
places. The `wcc-contentful` gem, by contrast, automatically resolves a link when
|
609
|
+
accessing the associated attribute. So in our example above, `wcc-contentful` will
|
610
|
+
**always** return a `WCC::Contentful::Asset` when calling `@page.hero_image`, even
|
611
|
+
if it has to execute a query to cdn.contentful.com in order to fetch it. Warning:
|
612
|
+
This can easily lead to you exhausting your Contentful API quota if you do not carefully
|
613
|
+
tune your cache, which you should be doing anyways! The default settings will use
|
614
|
+
the Rails cache to try to cache these resolutions, but *you are ultimately responsible
|
615
|
+
for how many queries you execute!* ### Automatic webhook management The `wcc-contentful`
|
616
|
+
gem, just like `contentful_rails`, provides an Engine to be mounted in your Rails
|
617
|
+
routes file. Unlike `contentful_rails`, if you also configure `wcc-contentful`
|
618
|
+
with a Contentful Management Token and a public `app_url`, then on startup the `wcc-contentful`
|
619
|
+
engine will reach out to the Contentful Management API and ensure that a webhook
|
620
|
+
is configured to point to your app. This is one less devops burden on you, and
|
621
|
+
plays very nicely in with Heroku review apps. ## Installation Add this line to
|
622
|
+
your application''s Gemfile: ```ruby gem ''wcc-contentful'', require: ''wcc/contentful/rails''
|
623
|
+
``` If you''re not using rails, exclude the `require:` parameter. ```ruby gem
|
624
|
+
''wcc-contentful'' ``` And then execute: ``` $ bundle ``` Or install it yourself: ```
|
625
|
+
$ gem install wcc-contentful ``` ## Configure Put this in an initializer: ```ruby
|
626
|
+
# config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
|
627
|
+
= <CONTENTFUL_ACCESS_TOKEN> config.space = <CONTENTFUL_SPACE_ID> end WCC::Contentful.init!
|
628
|
+
``` All configuration options can be found [in the rubydoc under WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration) ##
|
629
|
+
Usage ### WCC::Contentful::Model API The WCC::Contentful::Model API exposes Contentful
|
630
|
+
data as a set of dynamically generated Ruby objects. These objects are based on
|
631
|
+
the content types in your Contentful space. All these objects are generated by
|
632
|
+
`WCC::Contentful.init!` The following examples show how to use this API to find
|
633
|
+
entries of the `page` content type: ```ruby # Find objects by id WCC::Contentful::Model::Page.find(''1E2ucWSdacxxf233sfa3'')
|
505
634
|
# => #<WCC::Contentful::Model::Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17
|
506
635
|
UTC...> # Find objects by field WCC::Contentful::Model::Page.find_by(slug: ''/some-slug'')
|
507
636
|
# => #<WCC::Contentful::Model::Page:0x0000000005c71a78 @created_at=2018-04-16 18:41:17
|
@@ -541,51 +670,73 @@ summary: '[](https://
|
|
541
670
|
# {"sys"=> ...} # "6Fwukxxkxa6qQCC04WCaqg"=> # {"sys"=> ...} # ...} ``` The
|
542
671
|
client handles Paging automatically within the lazy iterator returned by #items.
|
543
672
|
This lazy iterator does not respect the `limit` param - that param is only passed
|
544
|
-
through to the API to set the page size.
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
#
|
565
|
-
|
673
|
+
through to the API to set the page size. If you truly want a limited subset of
|
674
|
+
response items, use [`response.items.take(n)`](https://ruby-doc.org/core-2.5.3/Enumerable.html#method-i-take) Entries
|
675
|
+
included via the `include` parameter are made available on the #includes field. This
|
676
|
+
is a hash of `<entry ID> => <raw entry>` and makes it easy to grab links. This
|
677
|
+
hash is added to lazily as you enumerate the pages. See the {WCC::Contentful::SimpleClient}
|
678
|
+
documentation for more details. ### Accessing the APIs within application code The
|
679
|
+
Model API is best exposed by defining your own model classes in the `app/models`
|
680
|
+
directory which inherit from the WCC::Contentful models. ```ruby # app/models/page.rb
|
681
|
+
class Page < WCC::Contentful::Model::Page # You can add additional methods here
|
682
|
+
end # app/controllers/pages_controller.rb class PagesController < ApplicationController
|
683
|
+
def show @page = Page.find_by(slug: params[:slug]) raise Exceptions::PageNotFoundError,
|
684
|
+
params[:slug] unless @page end end ``` The {WCC::Contentful::Services} singleton
|
685
|
+
gives access to the other configured services. You can also include the {WCC::Contentful::ServiceAccessors}
|
686
|
+
concern to define these services as attributes in a class. ```ruby class MyJob
|
687
|
+
< ApplicationJob include WCC::Contentful::ServiceAccessors def perform Page.find(...) store.find(...) client.entries(...)
|
688
|
+
end end ``` ## Architecture  ##
|
689
|
+
Test Helpers To use the test helpers, include the following in your rails_helper.rb: ```ruby
|
690
|
+
require ''wcc/contentful/rspec'' ``` This adds the following helpers to all your
|
691
|
+
specs: ```ruby ## # Builds a in-memory instance of the Contentful model for the
|
692
|
+
given content_type. # All attributes that are known to be required fields on the
|
693
|
+
content type # will return a default value based on the field type. instance = contentful_create(''my-content-type'',
|
694
|
+
my_field: ''some-value'') # => #<WCC::Contentful::Model::MyContentType:0x0000000005c71a78
|
695
|
+
@created_at=2018-04-16 18:41:17 UTC...> instance.my_field # => "some-value" instance.other_required_field
|
566
696
|
# => "default-value" instance.other_optional_field # => nil instance.not_a_field
|
567
|
-
# NoMethodError: undefined method `not_a_field'' for #<
|
697
|
+
# NoMethodError: undefined method `not_a_field'' for #<MyContentType:0x00007fbac81ee490> ##
|
568
698
|
# Builds a rspec double of the Contentful model for the given content_type. # All
|
569
699
|
attributes that are known to be required fields on the content type # will return
|
570
700
|
a default value based on the field type. dbl = contentful_double(''my-content-type'',
|
571
|
-
my_field: ''other-value'') # => #<Double (anonymous)> dbl.my_field # => "other-value" dbl.
|
572
|
-
# => #<Double (anonymous)> received unexpected message
|
573
|
-
# Builds out a fake Contentful entry for the given
|
574
|
-
the Model API to return that content type for `.find`
|
575
|
-
stubbed = contentful_stub(''my-content-type'', id:
|
576
|
-
|
577
|
-
''
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
701
|
+
my_field: ''other-value'') # => #<Double (anonymous)> dbl.my_field # => "other-value" dbl.other_optional_field
|
702
|
+
# => nil dbl.not_a_field # => #<Double (anonymous)> received unexpected message
|
703
|
+
:not_a_field with (no args) ## # Builds out a fake Contentful entry for the given
|
704
|
+
content type, and then # stubs the Model API to return that content type for `.find`
|
705
|
+
and `.find_by` # query methods. stubbed = contentful_stub(''my-content-type'', id:
|
706
|
+
''1234'', my_field: ''test'') WCC::Contentful::Model.find(''1234'') == stubbed
|
707
|
+
# => true MyContentType.find(''1234'') == stubbed # => true MyContentType.find_by(my_field:
|
708
|
+
''test'') == stubbed # => true ``` ## Advanced Configuration Example Here''s an
|
709
|
+
example containing all the configuration options, and a sample setup for automatic
|
710
|
+
deployment to Heroku. This is intended to make you aware of what is possible, and
|
711
|
+
not as a general recommendation of what your setup should look like. ```ruby #
|
712
|
+
config/initializers/wcc_contentful.rb WCC::Contentful.configure do |config| config.access_token
|
713
|
+
= ENV[''CONTENTFUL_ACCESS_TOKEN''] config.space = ENV[''CONTENTFUL_SPACE_ID''] config.environment
|
714
|
+
= ENV[''CONTENTFUL_ENVIRONMENT''] config.preview_token = ENV[''CONTENTFUL_PREVIEW_ACCESS_TOKEN''] #
|
715
|
+
You may or may not want to provide this to your production server... config.management_token
|
716
|
+
= ENV[''CONTENTFUL_MANAGEMENT_TOKEN''] unless Rails.env.production? config.app_url
|
717
|
+
= "https://#{ENV[''HOSTNAME'']}" config.webhook_username = ''my-app-webhook'' config.webhook_password
|
718
|
+
= Rails.application.secrets.webhook_password config.webhook_jobs << MyOnWebhookJob config.store
|
719
|
+
= :lazy_sync, Rails.cache if Rails.env.production? # config.store = MyCustomStore.new #
|
720
|
+
Use a custom Faraday connection config.connection = Faraday.new do |builder| f.request
|
721
|
+
:retry f.request MyFaradayRequestAdapter.new ... end # OR implement some adapter
|
722
|
+
like this to use another HTTP client config.connection = MyNetHttpAdapter.new config.update_schema_file
|
723
|
+
= :never end WCC::Contentful.init! ``` For Heroku: ```yaml # Procfile web: bundle
|
724
|
+
exec rails s worker: bundle exec sidekiq release: bin/release ``` ```sh # bin/release
|
725
|
+
#!/bin/sh set -e echo "Migrating database..." bin/rake db:migrate echo "Migrating
|
726
|
+
contentful..." migrations_to_be_run=$( ... ) # somehow figure this out node_modules/.bin/contentful-migration
|
727
|
+
\ -s $CONTENTFUL_SPACE_ID -a $CONTENTFUL_MANAGEMENT_TOKEN \ -y -p "$migrations_to_be_run" echo
|
728
|
+
"Updating schema file..." rake wcc_contentful:download_schema ``` All configuration
|
729
|
+
options can be found [in the rubydoc](https://www.rubydoc.info/gems/wcc-contentful/WCC/Contentful/Configuration)
|
730
|
+
under {WCC::Contentful::Configuration} ## Development After checking out the
|
731
|
+
repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to
|
732
|
+
run the tests. You can also run `bin/console` for an interactive prompt that will
|
733
|
+
allow you to experiment. ## Contributing Bug reports and pull requests are welcome
|
734
|
+
on GitHub at https://github.com/watermarkchurch/wcc-contentful. The developers
|
735
|
+
at Watermark Community Church have pledged to govern their interactions with each
|
736
|
+
other, with their clients, and with the larger wcc-contentful user community in
|
737
|
+
accordance with the "instruments of good works" from chapter 4 of The Rule of St.
|
738
|
+
Benedict (hereafter: "The Rule"). This code of ethics has proven its mettle in thousands
|
739
|
+
of diverse communities for over 1,500 years, and has served as a baseline for many
|
740
|
+
civil law codes since the time of Charlemagne. [See the full Code of Ethics](https://github.com/watermarkchurch/wcc-contentful/blob/master/CODE_OF_ETHICS.md) ##
|
741
|
+
License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).'
|
591
742
|
test_files: []
|