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.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/Guardfile +43 -0
  3. data/README.md +205 -17
  4. data/Rakefile +5 -0
  5. data/app/controllers/wcc/contentful/webhook_controller.rb +25 -24
  6. data/app/jobs/wcc/contentful/webhook_enable_job.rb +36 -2
  7. data/config/routes.rb +1 -1
  8. data/doc-static/wcc-contentful.png +0 -0
  9. data/lib/tasks/download_schema.rake +12 -0
  10. data/lib/wcc/contentful.rb +70 -16
  11. data/lib/wcc/contentful/active_record_shim.rb +72 -0
  12. data/lib/wcc/contentful/configuration.rb +177 -46
  13. data/lib/wcc/contentful/content_type_indexer.rb +14 -0
  14. data/lib/wcc/contentful/downloads_schema.rb +112 -0
  15. data/lib/wcc/contentful/engine.rb +33 -14
  16. data/lib/wcc/contentful/event.rb +171 -0
  17. data/lib/wcc/contentful/events.rb +41 -0
  18. data/lib/wcc/contentful/exceptions.rb +3 -0
  19. data/lib/wcc/contentful/indexed_representation.rb +2 -2
  20. data/lib/wcc/contentful/instrumentation.rb +31 -0
  21. data/lib/wcc/contentful/link.rb +28 -0
  22. data/lib/wcc/contentful/link_visitor.rb +122 -0
  23. data/lib/wcc/contentful/middleware.rb +7 -0
  24. data/lib/wcc/contentful/middleware/store.rb +158 -0
  25. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +114 -0
  26. data/lib/wcc/contentful/model.rb +37 -3
  27. data/lib/wcc/contentful/model_builder.rb +1 -0
  28. data/lib/wcc/contentful/model_methods.rb +40 -15
  29. data/lib/wcc/contentful/model_singleton_methods.rb +47 -30
  30. data/lib/wcc/contentful/rake.rb +4 -0
  31. data/lib/wcc/contentful/rspec.rb +13 -8
  32. data/lib/wcc/contentful/services.rb +61 -27
  33. data/lib/wcc/contentful/simple_client.rb +81 -25
  34. data/lib/wcc/contentful/simple_client/management.rb +43 -10
  35. data/lib/wcc/contentful/simple_client/response.rb +61 -22
  36. data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +17 -17
  37. data/lib/wcc/contentful/store.rb +7 -66
  38. data/lib/wcc/contentful/store/README.md +85 -0
  39. data/lib/wcc/contentful/store/base.rb +34 -119
  40. data/lib/wcc/contentful/store/cdn_adapter.rb +71 -12
  41. data/lib/wcc/contentful/store/factory.rb +186 -0
  42. data/lib/wcc/contentful/store/instrumentation.rb +55 -0
  43. data/lib/wcc/contentful/store/interface.rb +82 -0
  44. data/lib/wcc/contentful/store/memory_store.rb +27 -24
  45. data/lib/wcc/contentful/store/postgres_store.rb +253 -107
  46. data/lib/wcc/contentful/store/postgres_store/schema_1.sql +73 -0
  47. data/lib/wcc/contentful/store/postgres_store/schema_2.sql +21 -0
  48. data/lib/wcc/contentful/store/query.rb +246 -0
  49. data/lib/wcc/contentful/store/query/interface.rb +63 -0
  50. data/lib/wcc/contentful/store/rspec_examples.rb +48 -0
  51. data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +629 -0
  52. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +283 -0
  53. data/lib/wcc/contentful/store/rspec_examples/nested_queries.rb +342 -0
  54. data/lib/wcc/contentful/sync_engine.rb +181 -0
  55. data/lib/wcc/contentful/test/attributes.rb +17 -5
  56. data/lib/wcc/contentful/test/factory.rb +22 -46
  57. data/lib/wcc/contentful/version.rb +1 -1
  58. data/wcc-contentful.gemspec +22 -11
  59. metadata +295 -144
  60. data/Gemfile +0 -6
  61. data/app/jobs/wcc/contentful/delayed_sync_job.rb +0 -63
  62. data/lib/wcc/contentful/client_ext.rb +0 -28
  63. data/lib/wcc/contentful/graphql.rb +0 -14
  64. data/lib/wcc/contentful/graphql/builder.rb +0 -177
  65. data/lib/wcc/contentful/graphql/types.rb +0 -54
  66. data/lib/wcc/contentful/simple_client/http_adapter.rb +0 -24
  67. 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: -> { OpenStruct.new },
11
- Coordinates: -> { OpenStruct.new }
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
- default_instance(const, id).tap do |instance|
19
- attrs.each do |k, v|
20
- field = const.content_type_definition.fields[k]
20
+ raw['sys'].merge!(sys) if sys
21
21
 
22
- raw = v
23
- if %i[Asset Link].include?(field.type)
24
- raw = to_raw(v, field.type)
22
+ attrs.each do |k, v|
23
+ field = const.content_type_definition.fields[k]
25
24
 
26
- unless field.array ? v.any? { |i| i.is_a?(String) } : v.is_a?(String)
27
- instance.instance_variable_set("@#{field.name}_resolved", v)
28
- end
29
- end
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
- instance.raw['fields'][field.name][instance.sys.locale] = raw
32
- instance.instance_variable_set("@#{field.name}", raw)
33
- end
30
+ instance = const.new(raw, context)
34
31
 
35
- def instance.to_s
36
- "#<#{self.class.name} id=\"#{id}\">"
37
- end
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
- class Link
42
- attr_reader :id
43
- attr_reader :link_type
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
- alias_method :to_h, :raw
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: sys(model, id), fields: fields(model) }.as_json
51
+ { sys: contentful_sys(model, id), fields: contentful_fields(model) }.as_json
76
52
  end
77
53
 
78
- def sys(model, id = nil)
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 fields(model)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module WCC
4
4
  module Contentful
5
- VERSION = '0.4.0-beta'
5
+ VERSION = '1.0.0'
6
6
  end
7
7
  end
@@ -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', '~> 10.0'
39
+ spec.add_development_dependency 'rake', '~> 13.0'
31
40
  spec.add_development_dependency 'rspec', '~> 3.0'
32
- spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
33
- spec.add_development_dependency 'rubocop', '~> 0.52'
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', '~> 4.0'
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.1'
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 'contentful', '2.6.0'
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.0.pre.beta
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: 2018-10-19 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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.3.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.3.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.52'
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.52'
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: '4.0'
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: '4.0'
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: generator_spec
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.9.4
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.9.4
264
+ version: 0.7.1
209
265
  - !ruby/object:Gem::Dependency
210
- name: rails
266
+ name: generator_spec
211
267
  requirement: !ruby/object:Gem::Requirement
212
268
  requirements:
213
269
  - - "~>"
214
270
  - !ruby/object:Gem::Version
215
- version: '5.1'
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: '5.1'
278
+ version: 0.9.4
223
279
  - !ruby/object:Gem::Dependency
224
- name: rspec-rails
280
+ name: sqlite3
225
281
  requirement: !ruby/object:Gem::Requirement
226
282
  requirements:
227
283
  - - "~>"
228
284
  - !ruby/object:Gem::Version
229
- version: '3.7'
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: '3.7'
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: contentful
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: '1.7'
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: '1.7'
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: dry-validation
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.11.1
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.11.1
410
+ version: 0.3.1
397
411
  - !ruby/object:Gem::Dependency
398
- name: wcc-base
412
+ name: wisper
399
413
  requirement: !ruby/object:Gem::Requirement
400
414
  requirements:
401
415
  - - "~>"
402
416
  - !ruby/object:Gem::Version
403
- version: 0.3.1
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.3.1
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/client_ext.rb
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/lazy_cache_store.rb
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
- post_install_message:
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: 1.3.1
519
+ version: '0'
486
520
  requirements: []
487
- rubyforge_project:
488
- rubygems_version: 2.6.11
489
- signing_key:
521
+ rubyforge_project:
522
+ rubygems_version: 2.7.6.2
523
+ signing_key:
490
524
  specification_version: 4
491
- summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://badge.fury.io/rb/wcc-contentful)
492
- [![CircleCI](https://circleci.com/gh/watermarkchurch/wcc-contentful.svg?style=svg)](https://circleci.com/gh/watermarkchurch/wcc-contentful)
525
+ summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://rubygems.org/gems/wcc-contentful)
526
+ [![Build Status](https://circleci.com/gh/watermarkchurch/wcc-contentful.svg?style=svg)](https://circleci.com/gh/watermarkchurch/wcc-contentful)
493
527
  [![Coverage Status](https://coveralls.io/repos/github/watermarkchurch/wcc-contentful/badge.svg?branch=master)](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master) Full
494
- documentation: https://www.rubydoc.info/github/watermarkchurch/wcc-contentful #
495
- WCC::Contentful ## Installation Add this line to your application''s Gemfile: ```ruby
496
- gem ''wcc-contentful'', require: ''wcc/contentful/rails'' ``` And then execute: $
497
- bundle Or install it yourself as: $ gem install wcc-contentful ## Configure ```ruby
498
- WCC::Contentful.configure do |config| config.access_token = <CONTENTFUL_ACCESS_TOKEN>
499
- config.space = <CONTENTFUL_SPACE_ID> end WCC::Contentful.init! ``` ## Usage ###
500
- WCC::Contentful::Model API The WCC::Contentful::Model API exposes Contentful data
501
- as a set of dynamically generated Ruby objects. These objects are based on the
502
- content types in your Contentful space. All these objects are generated by WCC::Contentful.init! The
503
- following examples show how to use this API to find entries of the `page` content
504
- type: ```ruby # Find objects by id WCC::Contentful::Model::Page.find(''1E2ucWSdacxxf233sfa3'')
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: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](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. Entries included via the `include` parameter
545
- are made available on the #includes field. This is a hash of `<entry ID> => <raw
546
- entry>` and makes it easy to grab links. This hash is added to lazily as you enumerate
547
- the pages. See the {WCC::Contentful::SimpleClient} documentation for more details. ###
548
- Accessing the APIs within application code The Model API is best exposed by defining
549
- your own model classes in the `app/models` directory which inherit from the WCC::Contentful
550
- models. ```ruby # app/models/page.rb class Page < WCC::Contentful::Model::Page #
551
- You can add additional methods here end # app/controllers/pages_controller.rb class
552
- PagesController < ApplicationController def show @page = Page.find_by(slug: params[:slug])
553
- raise Exceptions::PageNotFoundError, params[:slug] unless @page end end ``` The
554
- {WCC::Contentful::Services} singleton gives access to the other configured services.
555
- You can also include the {WCC::Contentful::ServiceAccessors} concern to define these
556
- services as attributes in a class. ```ruby class MyJob < ApplicationJob include
557
- WCC::Contentful::ServiceAccessors def perform Page.find(...) store.find(...) client.entries(...)
558
- end end ``` ## Test Helpers To use the test helpers, include the following in
559
- your rails_helper.rb: ```ruby require ''wcc/contentful/rspec'' ``` This adds the
560
- following helpers to all your specs: ```ruby ## # Builds a in-memory instance of
561
- the Contentful model for the given content_type. # All attributes that are known
562
- to be required fields on the content type # will return a default value based on
563
- the field type. instance = contentful_create(''my-content-type'', my_field: ''some-value'')
564
- # => #<WCC::Contentful::Model::MyContentType:0x0000000005c71a78 @created_at=2018-04-16
565
- 18:41:17 UTC...> instance.my_field # => "some-value" instance.other_required_field
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 ![wcc-contentful diagram](./doc-static/wcc-contentful.png) ##
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 #<Menu:0x00007fbac81ee490> ##
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.not_a_field
572
- # => #<Double (anonymous)> received unexpected message :not_a_field with (no args) ##
573
- # Builds out a fake Contentful entry for the given content type, and then # stubs
574
- the Model API to return that content type for `.find` and `.find_by` # query methods.
575
- stubbed = contentful_stub(''my-content-type'', id: ''1234'', my_field: ''test'') WCC::Contentful::Model.find(''1234'')
576
- == stubbed # => true MyContentType.find(''1234'') == stubbed # => true MyContentType.find_by(my_field:
577
- ''test'') == stubbed # => true ``` ## Development After checking out the repo,
578
- run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
579
- You can also run `bin/console` for an interactive prompt that will allow you to
580
- experiment. To install this gem onto your local machine, run `bundle exec rake
581
- install`. To release a new version, update the version number in `version.rb`, and
582
- then run `bundle exec rake release`, which will create a git tag for the version,
583
- push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ##
584
- Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/watermarkchurch/wcc-contentful.
585
- This project is intended to be a safe, welcoming space for collaboration, and contributors
586
- are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org)
587
- code of conduct. ## License The gem is available as open source under the terms
588
- of the [MIT License](http://opensource.org/licenses/MIT). ## Code of Conduct Everyone
589
- interacting in the WCC::Contentful project''s codebases, issue trackers, chat rooms
590
- and mailing lists is expected to follow the [code of conduct](https://github.com/watermarkchurch/wcc-contentful/blob/master/CODE_OF_CONDUCT.md).'
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: []