wcc-contentful 0.3.0 → 1.0.0.pre.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +1 -1
  3. data/Guardfile +43 -0
  4. data/README.md +161 -11
  5. data/Rakefile +3 -6
  6. data/app/controllers/wcc/contentful/webhook_controller.rb +25 -24
  7. data/app/jobs/wcc/contentful/webhook_enable_job.rb +36 -2
  8. data/bin/console +4 -3
  9. data/bin/rails +2 -0
  10. data/config/routes.rb +1 -1
  11. data/doc +1 -0
  12. data/lib/tasks/download_schema.rake +12 -0
  13. data/lib/wcc/contentful.rb +69 -45
  14. data/lib/wcc/contentful/active_record_shim.rb +72 -0
  15. data/lib/wcc/contentful/configuration.rb +177 -46
  16. data/lib/wcc/contentful/content_type_indexer.rb +14 -0
  17. data/lib/wcc/contentful/downloads_schema.rb +112 -0
  18. data/lib/wcc/contentful/engine.rb +33 -14
  19. data/lib/wcc/contentful/event.rb +171 -0
  20. data/lib/wcc/contentful/events.rb +41 -0
  21. data/lib/wcc/contentful/exceptions.rb +3 -33
  22. data/lib/wcc/contentful/indexed_representation.rb +2 -2
  23. data/lib/wcc/contentful/instrumentation.rb +31 -0
  24. data/lib/wcc/contentful/link.rb +28 -0
  25. data/lib/wcc/contentful/link_visitor.rb +122 -0
  26. data/lib/wcc/contentful/middleware.rb +7 -0
  27. data/lib/wcc/contentful/middleware/store.rb +158 -0
  28. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +114 -0
  29. data/lib/wcc/contentful/model.rb +37 -4
  30. data/lib/wcc/contentful/model_builder.rb +1 -0
  31. data/lib/wcc/contentful/model_methods.rb +40 -15
  32. data/lib/wcc/contentful/model_singleton_methods.rb +47 -30
  33. data/lib/wcc/contentful/rake.rb +4 -0
  34. data/lib/wcc/contentful/rspec.rb +46 -0
  35. data/lib/wcc/contentful/services.rb +61 -27
  36. data/lib/wcc/contentful/simple_client.rb +81 -25
  37. data/lib/wcc/contentful/simple_client/management.rb +43 -10
  38. data/lib/wcc/contentful/simple_client/response.rb +61 -22
  39. data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +17 -17
  40. data/lib/wcc/contentful/store.rb +7 -66
  41. data/lib/wcc/contentful/store/README.md +85 -0
  42. data/lib/wcc/contentful/store/base.rb +34 -119
  43. data/lib/wcc/contentful/store/cdn_adapter.rb +71 -12
  44. data/lib/wcc/contentful/store/factory.rb +186 -0
  45. data/lib/wcc/contentful/store/instrumentation.rb +55 -0
  46. data/lib/wcc/contentful/store/interface.rb +82 -0
  47. data/lib/wcc/contentful/store/memory_store.rb +27 -24
  48. data/lib/wcc/contentful/store/postgres_store.rb +268 -101
  49. data/lib/wcc/contentful/store/postgres_store/schema_1.sql +73 -0
  50. data/lib/wcc/contentful/store/postgres_store/schema_2.sql +21 -0
  51. data/lib/wcc/contentful/store/query.rb +246 -0
  52. data/lib/wcc/contentful/store/query/interface.rb +63 -0
  53. data/lib/wcc/contentful/store/rspec_examples.rb +48 -0
  54. data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +629 -0
  55. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +283 -0
  56. data/lib/wcc/contentful/store/rspec_examples/nested_queries.rb +342 -0
  57. data/lib/wcc/contentful/sync_engine.rb +181 -0
  58. data/lib/wcc/contentful/test.rb +7 -0
  59. data/lib/wcc/contentful/test/attributes.rb +56 -0
  60. data/lib/wcc/contentful/test/double.rb +76 -0
  61. data/lib/wcc/contentful/test/factory.rb +101 -0
  62. data/lib/wcc/contentful/version.rb +1 -1
  63. data/wcc-contentful.gemspec +28 -14
  64. metadata +248 -152
  65. data/.circleci/config.yml +0 -51
  66. data/.gitignore +0 -26
  67. data/.rubocop.yml +0 -242
  68. data/.rubocop_todo.yml +0 -19
  69. data/.travis.yml +0 -5
  70. data/CHANGELOG.md +0 -180
  71. data/CODE_OF_CONDUCT.md +0 -74
  72. data/Gemfile +0 -8
  73. data/LICENSE.txt +0 -21
  74. data/app/jobs/wcc/contentful/delayed_sync_job.rb +0 -63
  75. data/lib/generators/wcc/USAGE +0 -24
  76. data/lib/generators/wcc/model_generator.rb +0 -90
  77. data/lib/generators/wcc/templates/.keep +0 -0
  78. data/lib/generators/wcc/templates/Procfile +0 -3
  79. data/lib/generators/wcc/templates/contentful_shell_wrapper +0 -385
  80. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +0 -192
  81. data/lib/generators/wcc/templates/menu/models/menu.rb +0 -23
  82. data/lib/generators/wcc/templates/menu/models/menu_button.rb +0 -23
  83. data/lib/generators/wcc/templates/page/generated_add_pages.ts +0 -50
  84. data/lib/generators/wcc/templates/page/models/page.rb +0 -23
  85. data/lib/generators/wcc/templates/release +0 -9
  86. data/lib/generators/wcc/templates/wcc_contentful.rb +0 -17
  87. data/lib/wcc/contentful/client_ext.rb +0 -28
  88. data/lib/wcc/contentful/graphql.rb +0 -14
  89. data/lib/wcc/contentful/graphql/builder.rb +0 -177
  90. data/lib/wcc/contentful/graphql/types.rb +0 -54
  91. data/lib/wcc/contentful/model/dropdown_menu.rb +0 -7
  92. data/lib/wcc/contentful/model/menu.rb +0 -6
  93. data/lib/wcc/contentful/model/menu_button.rb +0 -16
  94. data/lib/wcc/contentful/model/page.rb +0 -8
  95. data/lib/wcc/contentful/model/redirect.rb +0 -19
  96. data/lib/wcc/contentful/model_validators.rb +0 -121
  97. data/lib/wcc/contentful/model_validators/dsl.rb +0 -166
  98. data/lib/wcc/contentful/simple_client/http_adapter.rb +0 -24
  99. data/lib/wcc/contentful/store/lazy_cache_store.rb +0 -161
@@ -38,7 +38,6 @@
38
38
  # @api Model
39
39
  class WCC::Contentful::Model
40
40
  extend WCC::Contentful::Helpers
41
- extend WCC::Contentful::ModelValidators
42
41
 
43
42
  # The Model base class maintains a registry which is best expressed as a
44
43
  # class var.
@@ -55,15 +54,29 @@ class WCC::Contentful::Model
55
54
 
56
55
  @@registry = {}
57
56
 
57
+ def self.store(preview = false)
58
+ if preview
59
+ if preview_store.nil?
60
+ raise ArgumentError,
61
+ 'You must include a contentful preview token in your WCC::Contentful.configure block'
62
+ end
63
+ preview_store
64
+ else
65
+ super()
66
+ end
67
+ end
68
+
58
69
  # Finds an Entry or Asset by ID in the configured contentful space
59
70
  # and returns an initialized instance of the appropriate model type.
60
71
  #
61
72
  # Makes use of the {WCC::Contentful::Services#store configured store}
62
73
  # to access the Contentful CDN.
63
- def self.find(id, context = nil)
64
- return unless raw = store.find(id)
74
+ def self.find(id, options: nil)
75
+ options ||= {}
76
+ raw = store(options[:preview])
77
+ .find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
65
78
 
66
- new_from_raw(raw, context)
79
+ new_from_raw(raw, options) if raw.present?
67
80
  end
68
81
 
69
82
  # Creates a new initialized instance of the appropriate model type for the
@@ -78,6 +91,8 @@ class WCC::Contentful::Model
78
91
  # Accepts a content type ID as a string and returns the Ruby constant
79
92
  # stored in the registry that represents this content type.
80
93
  def self.resolve_constant(content_type)
94
+ raise ArgumentError, 'content_type cannot be nil' unless content_type
95
+
81
96
  const = @@registry[content_type]
82
97
  return const if const
83
98
 
@@ -130,6 +145,24 @@ class WCC::Contentful::Model
130
145
  @@registry.dup.freeze
131
146
  end
132
147
 
148
+ def self.reload!
149
+ registry = self.registry
150
+ registry.each do |(content_type, klass)|
151
+ const_name = klass.name
152
+ begin
153
+ const = Object.const_missing(const_name)
154
+ register_for_content_type(content_type, klass: const) if const
155
+ rescue NameError => e
156
+ msg = "Error when reloading constant #{const_name} - #{e}"
157
+ if defined?(Rails) && Rails.logger
158
+ Rails.logger.error msg
159
+ else
160
+ puts msg
161
+ end
162
+ end
163
+ end
164
+ end
165
+
133
166
  # Checks if a content type has already been registered to a class and returns
134
167
  # that class. If nil, the generated WCC::Contentful::Model::{content_type} class
135
168
  # will be resolved for this content type.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './link'
3
4
  require_relative './sys'
4
5
 
5
6
  module WCC::Contentful
@@ -5,6 +5,15 @@
5
5
  #
6
6
  # @api Model
7
7
  module WCC::Contentful::ModelMethods
8
+ include WCC::Contentful::Instrumentation
9
+
10
+ # The set of options keys that are specific to the Model layer and shouldn't
11
+ # be passed down to the Store layer.
12
+ MODEL_LAYER_CONTEXT_KEYS = %i[
13
+ preview
14
+ backlinks
15
+ ].freeze
16
+
8
17
  # Resolves all links in an entry to the specified depth.
9
18
  #
10
19
  # Each link in the entry is recursively retrieved from the store until the given
@@ -22,7 +31,7 @@ module WCC::Contentful::ModelMethods
22
31
  # handled. `:raise` causes a {WCC::Contentful::CircularReferenceError} to be raised,
23
32
  # `:ignore` will cause the field to remain unresolved, and any other value (or nil)
24
33
  # will cause the field to point to the previously resolved ruby object for that ID.
25
- def resolve(depth: 1, fields: nil, context: {}, **options)
34
+ def resolve(depth: 1, fields: nil, context: sys.context.to_h, **options)
26
35
  raise ArgumentError, "Depth must be > 0 (was #{depth})" unless depth && depth > 0
27
36
  return self if resolved?(depth: depth, fields: fields)
28
37
 
@@ -32,20 +41,26 @@ module WCC::Contentful::ModelMethods
32
41
  typedef = self.class.content_type_definition
33
42
  links = fields.select { |f| %i[Asset Link].include?(typedef.fields[f].type) }
34
43
 
35
- raw_links =
36
- links.any? do |field_name|
37
- raw_value = raw.dig('fields', field_name, sys.locale)
38
- if raw_value&.is_a? Array
39
- raw_value.any? { |v| v&.dig('sys', 'type') == 'Link' }
40
- elsif raw_value
41
- raw_value.dig('sys', 'type') == 'Link'
44
+ raw_link_ids =
45
+ links.map { |field_name| raw.dig('fields', field_name, sys.locale) }
46
+ .flat_map do |raw_value|
47
+ _try_map(raw_value) { |v| v.dig('sys', 'id') if v.dig('sys', 'type') == 'Link' }
48
+ end
49
+ raw_link_ids = raw_link_ids.compact
50
+ backlinked_ids = (context[:backlinks]&.map { |m| m.id } || [])
51
+
52
+ has_unresolved_raw_links = (raw_link_ids - backlinked_ids).any?
53
+ if has_unresolved_raw_links
54
+ raw =
55
+ _instrument 'resolve', id: id, depth: depth, backlinks: backlinked_ids do
56
+ # use include param to do resolution
57
+ self.class.store(context[:preview])
58
+ .find_by(content_type: self.class.content_type,
59
+ filter: { 'sys.id' => id },
60
+ options: context.except(*MODEL_LAYER_CONTEXT_KEYS).merge!({
61
+ include: [depth, 10].min
62
+ }))
42
63
  end
43
- end
44
- if raw_links
45
- # use include param to do resolution
46
- raw = self.class.store.find_by(content_type: self.class.content_type,
47
- filter: { 'sys.id' => id },
48
- options: { include: [depth, 10].min })
49
64
  unless raw
50
65
  raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}"
51
66
  end
@@ -118,6 +133,12 @@ module WCC::Contentful::ModelMethods
118
133
 
119
134
  delegate :to_json, to: :to_h
120
135
 
136
+ protected
137
+
138
+ def _instrumentation_event_prefix
139
+ '.model.contentful.wcc'
140
+ end
141
+
121
142
  private
122
143
 
123
144
  def _resolve_field(field_name, depth = 1, context = {}, options = {})
@@ -147,7 +168,10 @@ module WCC::Contentful::ModelMethods
147
168
  # instantiate from already resolved raw entry data.
148
169
  m = already_resolved ||
149
170
  if raw.dig('sys', 'type') == 'Link'
150
- WCC::Contentful::Model.find(id, new_context)
171
+ _instrument 'resolve',
172
+ id: self.id, depth: depth, backlinks: context[:backlinks]&.map(&:id) do
173
+ WCC::Contentful::Model.find(id, options: new_context)
174
+ end
151
175
  else
152
176
  WCC::Contentful::Model.new_from_raw(raw, new_context)
153
177
  end
@@ -158,6 +182,7 @@ module WCC::Contentful::ModelMethods
158
182
 
159
183
  begin
160
184
  val = _try_map(val) { |v| load.call(v) }
185
+ val = val.compact if val.is_a? Array
161
186
 
162
187
  instance_variable_set(var_name + '_resolved', val)
163
188
  rescue WCC::Contentful::CircularReferenceError
@@ -4,18 +4,6 @@
4
4
  # methods that are not dynamically generated.
5
5
  # @api Model
6
6
  module WCC::Contentful::ModelSingletonMethods
7
- def store(preview = false)
8
- if preview
9
- if WCC::Contentful::Model.preview_store.nil?
10
- raise ArgumentError,
11
- 'You must include a contentful preview token in your WCC::Contentful.configure block'
12
- end
13
- WCC::Contentful::Model.preview_store
14
- else
15
- WCC::Contentful::Model.store
16
- end
17
- end
18
-
19
7
  # Finds an instance of this content type.
20
8
  #
21
9
  # @return [nil, WCC::Contentful::Model] An instance of the appropriate model class
@@ -24,8 +12,12 @@ module WCC::Contentful::ModelSingletonMethods
24
12
  # WCC::Contentful::Model::Page.find(id)
25
13
  def find(id, options: nil)
26
14
  options ||= {}
27
- raw = store(options[:preview])
28
- .find(id, { hint: type }.merge!(options.except(:preview)))
15
+ store = store(options[:preview])
16
+ raw =
17
+ WCC::Contentful::Instrumentation.instrument 'find.model.contentful.wcc',
18
+ content_type: content_type, id: id, options: options do
19
+ store.find(id, { hint: type }.merge!(options.except(:preview)))
20
+ end
29
21
  new(raw, options) if raw.present?
30
22
  end
31
23
 
@@ -40,16 +32,16 @@ module WCC::Contentful::ModelSingletonMethods
40
32
  filter = filter&.dup
41
33
  options = filter&.delete(:options) || {}
42
34
 
43
- if filter.present?
44
- filter.transform_keys! { |k| k.to_s.camelize(:lower) }
45
- bad_fields = filter.keys.reject { |k| self::FIELDS.include?(k) }
46
- raise ArgumentError, "These fields do not exist: #{bad_fields}" unless bad_fields.empty?
47
- end
35
+ filter.transform_keys! { |k| k.to_s.camelize(:lower) } if filter.present?
48
36
 
49
- query = store(options[:preview])
50
- .find_all(content_type: content_type, options: options.except(:preview))
37
+ store = store(options[:preview])
38
+ query =
39
+ WCC::Contentful::Instrumentation.instrument 'find_all.model.contentful.wcc',
40
+ content_type: content_type, filter: filter, options: options do
41
+ store.find_all(content_type: content_type, options: options.except(:preview))
42
+ end
51
43
  query = query.apply(filter) if filter.present?
52
- query.map { |r| new(r, options) }
44
+ ModelQuery.new(query, options, self)
53
45
  end
54
46
 
55
47
  # Finds the first instance of this content type matching the given query.
@@ -62,22 +54,47 @@ module WCC::Contentful::ModelSingletonMethods
62
54
  filter = filter&.dup
63
55
  options = filter&.delete(:options) || {}
64
56
 
65
- if filter.present?
66
- filter.transform_keys! { |k| k.to_s.camelize(:lower) }
67
- bad_fields = filter.keys.reject { |k| self::FIELDS.include?(k) }
68
- raise ArgumentError, "These fields do not exist: #{bad_fields}" unless bad_fields.empty?
69
- end
57
+ filter.transform_keys! { |k| k.to_s.camelize(:lower) } if filter.present?
70
58
 
71
- result = store(options[:preview])
72
- .find_by(content_type: content_type, filter: filter, options: options.except(:preview))
59
+ store = store(options[:preview])
60
+ result =
61
+ WCC::Contentful::Instrumentation.instrument 'find_by.model.contentful.wcc',
62
+ content_type: content_type, filter: filter, options: options do
63
+ store.find_by(content_type: content_type, filter: filter, options: options.except(:preview))
64
+ end
73
65
 
74
66
  new(result, options) if result
75
67
  end
76
68
 
77
69
  def inherited(subclass)
78
- # only register if it's not already registered
70
+ # If another different class is already registered for this content type,
71
+ # don't auto-register this one.
79
72
  return if WCC::Contentful::Model.registered?(content_type)
80
73
 
81
74
  WCC::Contentful::Model.register_for_content_type(content_type, klass: subclass)
82
75
  end
76
+
77
+ class ModelQuery
78
+ include Enumerable
79
+
80
+ # by default all enumerable methods delegated to the to_enum method
81
+ delegate(*(Enumerable.instance_methods - Module.instance_methods), to: :to_enum)
82
+ delegate :each, to: :to_enum
83
+
84
+ # except count - because that needs to pull data off the final query obj
85
+ delegate :count, to: :wrapped_query
86
+
87
+ attr_reader :wrapped_query, :options, :klass
88
+
89
+ def initialize(wrapped_query, options, klass)
90
+ @wrapped_query = wrapped_query
91
+ @options = options
92
+ @klass = klass
93
+ end
94
+
95
+ def to_enum
96
+ wrapped_query.to_enum
97
+ .map { |r| klass.new(r, options) }
98
+ end
99
+ end
83
100
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(File.dirname(__FILE__), '../../tasks/**/*.rake')]
4
+ .each { |f| load f }
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wcc/contentful'
4
+
5
+ require_relative './test'
6
+
7
+ module WCC::Contentful::RSpec
8
+ include WCC::Contentful::Test::Double
9
+ include WCC::Contentful::Test::Factory
10
+
11
+ ##
12
+ # Builds out a fake Contentful entry for the given content type, and then
13
+ # stubs the Model API to return that content type for `.find` and `.find_by`
14
+ # query methods.
15
+ def contentful_stub(content_type, **attrs)
16
+ const = WCC::Contentful::Model.resolve_constant(content_type.to_s)
17
+ instance = contentful_create(content_type, **attrs)
18
+
19
+ # mimic what's going on inside model_singleton_methods.rb
20
+ # find, find_by, etc always return a new instance from the same raw
21
+ allow(WCC::Contentful::Model).to receive(:find)
22
+ .with(instance.id, any_args) do |_id, keyword_params|
23
+ options = keyword_params && keyword_params[:options]
24
+ contentful_create(content_type, options, raw: instance.raw, **attrs)
25
+ end
26
+ allow(const).to receive(:find) { |id, options| WCC::Contentful::Model.find(id, **(options || {})) }
27
+
28
+ attrs.each do |k, v|
29
+ allow(const).to receive(:find_by)
30
+ .with(hash_including(k => v)) do |filter|
31
+ filter = filter&.dup
32
+ options = filter&.delete(:options) || {}
33
+
34
+ contentful_create(content_type, options, raw: instance.raw, **attrs)
35
+ end
36
+ end
37
+
38
+ instance
39
+ end
40
+ end
41
+
42
+ if defined?(RSpec)
43
+ RSpec.configure do |config|
44
+ config.include WCC::Contentful::RSpec
45
+ end
46
+ end
@@ -1,21 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
-
5
3
  module WCC::Contentful
6
4
  class Services
7
- include Singleton
5
+ class << self
6
+ def instance
7
+ @singleton__instance__ ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
8
+ end
9
+ end
10
+
11
+ def configuration
12
+ @configuration ||= WCC::Contentful.configuration
13
+ end
14
+
15
+ def initialize(configuration = nil)
16
+ @configuration = configuration
17
+ end
8
18
 
9
19
  # Gets the data-store which executes the queries run against the dynamic
10
20
  # models in the WCC::Contentful::Model namespace.
11
- # This is one of the following based on the configured content_delivery method:
21
+ # This is one of the following based on the configured store method:
12
22
  #
13
23
  # [:direct] an instance of {WCC::Contentful::Store::CDNAdapter} with a
14
24
  # {WCC::Contentful::SimpleClient::Cdn CDN Client} to access the CDN.
15
25
  #
16
- # [:lazy_sync] an instance of {WCC::Contentful::Store::LazyCacheStore}
17
- # with the configured ActiveSupport::Cache implementation and a
18
- # {WCC::Contentful::SimpleClient::Cdn CDN Client} for when data
26
+ # [:lazy_sync] an instance of {WCC::Contentful::Middleware::Store::CachingMiddleware}
27
+ # with the configured ActiveSupport::Cache implementation around a
28
+ # {WCC::Contentful::Store::CDNAdapter} for when data
19
29
  # cannot be found in the cache.
20
30
  #
21
31
  # [:eager_sync] an instance of the configured Store type, defined by
@@ -25,12 +35,7 @@ module WCC::Contentful
25
35
  def store
26
36
  @store ||=
27
37
  ensure_configured do |config|
28
- WCC::Contentful::Store::Factory.new(
29
- config,
30
- self,
31
- config.content_delivery,
32
- config.content_delivery_params
33
- ).build_sync_store
38
+ config.store.build(self)
34
39
  end
35
40
  end
36
41
 
@@ -43,10 +48,9 @@ module WCC::Contentful
43
48
  ensure_configured do |config|
44
49
  WCC::Contentful::Store::Factory.new(
45
50
  config,
46
- self,
47
51
  :direct,
48
- [{ preview: true }]
49
- ).build_sync_store
52
+ :preview
53
+ ).build(self)
50
54
  end
51
55
  end
52
56
 
@@ -58,10 +62,11 @@ module WCC::Contentful
58
62
  @client ||=
59
63
  ensure_configured do |config|
60
64
  WCC::Contentful::SimpleClient::Cdn.new(
65
+ **config.connection_options,
61
66
  access_token: config.access_token,
62
67
  space: config.space,
63
68
  default_locale: config.default_locale,
64
- adapter: config.http_adapter,
69
+ connection: config.connection,
65
70
  environment: config.environment
66
71
  )
67
72
  end
@@ -76,10 +81,11 @@ module WCC::Contentful
76
81
  ensure_configured do |config|
77
82
  if config.preview_token.present?
78
83
  WCC::Contentful::SimpleClient::Preview.new(
84
+ **config.connection_options,
79
85
  preview_token: config.preview_token,
80
86
  space: config.space,
81
87
  default_locale: config.default_locale,
82
- adapter: config.http_adapter,
88
+ connection: config.connection,
83
89
  environment: config.environment
84
90
  )
85
91
  end
@@ -95,27 +101,57 @@ module WCC::Contentful
95
101
  ensure_configured do |config|
96
102
  if config.management_token.present?
97
103
  WCC::Contentful::SimpleClient::Management.new(
104
+ **config.connection_options,
98
105
  management_token: config.management_token,
99
106
  space: config.space,
100
107
  default_locale: config.default_locale,
101
- adapter: config.http_adapter,
108
+ connection: config.connection,
102
109
  environment: config.environment
103
110
  )
104
111
  end
105
112
  end
106
113
  end
107
114
 
115
+ # Gets the configured WCC::Contentful::SyncEngine which is responsible for
116
+ # updating the currently configured store. The application must periodically
117
+ # call #next on this instance. Alternately, the application can mount the
118
+ # WCC::Contentful::Engine, which will call #next anytime a webhook is received.
119
+ #
120
+ # This returns `nil` if the currently configured store does not respond to sync
121
+ # events.
122
+ def sync_engine
123
+ @sync_engine ||=
124
+ if store.index?
125
+ SyncEngine.new(
126
+ store: store,
127
+ client: client,
128
+ key: 'sync:token'
129
+ )
130
+ end
131
+ end
132
+
133
+ # Gets the configured instrumentation adapter, defaulting to ActiveSupport::Notifications
134
+ def instrumentation
135
+ return @instrumentation if @instrumentation
136
+ return ActiveSupport::Notifications if WCC::Contentful.configuration.nil?
137
+
138
+ @instrumentation ||=
139
+ WCC::Contentful.configuration.instrumentation_adapter ||
140
+ ActiveSupport::Notifications
141
+ end
142
+
108
143
  private
109
144
 
110
145
  def ensure_configured
111
- if WCC::Contentful.configuration.nil?
112
- raise StandardError, 'WCC::Contentful has not yet been configured!'
113
- end
146
+ raise StandardError, 'WCC::Contentful has not yet been configured!' if configuration.nil?
114
147
 
115
- yield WCC::Contentful.configuration
148
+ yield configuration
116
149
  end
117
150
  end
118
151
 
152
+ SERVICES = (WCC::Contentful::Services.instance_methods -
153
+ Object.instance_methods)
154
+
119
155
  # Include this module to define accessors for every method defined on the
120
156
  # {Services} singleton.
121
157
  #
@@ -129,14 +165,12 @@ module WCC::Contentful
129
165
  # store.find(...)
130
166
  #
131
167
  # client.entries(...)
168
+ #
169
+ # sync_engine.next
132
170
  # end
133
171
  # end
134
172
  # @see Services
135
173
  module ServiceAccessors
136
- SERVICES = (WCC::Contentful::Services.instance_methods -
137
- Object.instance_methods -
138
- Singleton.instance_methods)
139
-
140
174
  SERVICES.each do |m|
141
175
  define_method m do
142
176
  Services.instance.public_send(m)