wcc-contentful 1.1.2 → 1.3.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/wcc/contentful/webhook_controller.rb +2 -0
  3. data/app/jobs/wcc/contentful/webhook_enable_job.rb +1 -1
  4. data/lib/tasks/download_schema.rake +1 -1
  5. data/lib/wcc/contentful/active_record_shim.rb +2 -2
  6. data/lib/wcc/contentful/configuration.rb +37 -18
  7. data/lib/wcc/contentful/content_type_indexer.rb +2 -0
  8. data/lib/wcc/contentful/downloads_schema.rb +5 -4
  9. data/lib/wcc/contentful/engine.rb +2 -4
  10. data/lib/wcc/contentful/event.rb +4 -11
  11. data/lib/wcc/contentful/exceptions.rb +2 -3
  12. data/lib/wcc/contentful/indexed_representation.rb +3 -6
  13. data/lib/wcc/contentful/instrumentation.rb +2 -1
  14. data/lib/wcc/contentful/link.rb +1 -3
  15. data/lib/wcc/contentful/link_visitor.rb +2 -4
  16. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +5 -9
  17. data/lib/wcc/contentful/middleware/store.rb +4 -6
  18. data/lib/wcc/contentful/model_api.rb +2 -4
  19. data/lib/wcc/contentful/model_builder.rb +8 -4
  20. data/lib/wcc/contentful/model_methods.rb +10 -12
  21. data/lib/wcc/contentful/model_singleton_methods.rb +2 -2
  22. data/lib/wcc/contentful/rich_text/node.rb +60 -0
  23. data/lib/wcc/contentful/rich_text.rb +105 -0
  24. data/lib/wcc/contentful/rspec.rb +1 -3
  25. data/lib/wcc/contentful/services.rb +9 -0
  26. data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
  27. data/lib/wcc/contentful/simple_client/preview.rb +17 -0
  28. data/lib/wcc/contentful/simple_client/response.rb +47 -46
  29. data/lib/wcc/contentful/simple_client.rb +13 -118
  30. data/lib/wcc/contentful/store/base.rb +19 -27
  31. data/lib/wcc/contentful/store/cdn_adapter.rb +11 -7
  32. data/lib/wcc/contentful/store/factory.rb +1 -1
  33. data/lib/wcc/contentful/store/memory_store.rb +10 -7
  34. data/lib/wcc/contentful/store/postgres_store.rb +52 -42
  35. data/lib/wcc/contentful/store/query.rb +2 -2
  36. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +6 -4
  37. data/lib/wcc/contentful/store/rspec_examples/operators.rb +1 -1
  38. data/lib/wcc/contentful/sync_engine.rb +58 -34
  39. data/lib/wcc/contentful/sys.rb +2 -1
  40. data/lib/wcc/contentful/test/double.rb +3 -5
  41. data/lib/wcc/contentful/test/factory.rb +4 -6
  42. data/lib/wcc/contentful/version.rb +1 -1
  43. data/lib/wcc/contentful.rb +10 -13
  44. data/wcc-contentful.gemspec +6 -6
  45. metadata +15 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3facd27f1d4401d01c3e42dc9ab11d75652cc6859a74c5c74ffb58647b13c28d
4
- data.tar.gz: 48072021ae935b5c7d9a684f488dafb3ffa53b94ea153bfeeba7f1b5f5a46806
3
+ metadata.gz: 9026bfcc1e7745f7d8ecf3fb38928672f02d663d382fd1a3ca953ab33a78cb25
4
+ data.tar.gz: 36ec7c7f47fb6e49d565d7ca82c5021ac8f88b46eabfe41754ac5c123ac0004d
5
5
  SHA512:
6
- metadata.gz: b884394e95a3c722c8a3a1327780700f153f9e724c920d9d57e81409d836e5111777f3493b504bfe7b6321a1d83ed95f6fe2613b9f77677393d2caab631caa4e
7
- data.tar.gz: 6891f2e8a291d4a72c725ce09a7270681ed8e2f7485d2f68d195f6fc0f55005e07408855f194d1ac130fe3c874c59c3bdc4e5aa87b512b776c37be792dd5e605
6
+ metadata.gz: 633116bd83752074876ccf76265eb64bc6a19a28b9a477bf2026925f0a84977cd651a234f5656b7a657a43e65b3320031e2385ca398bb2b846ab730693105799
7
+ data.tar.gz: 32f1f08bac98d42d89a136c0b49d2bb1ee1a72e057326edd50a17834b53b2da7fecbbe1db1aded355d2849bc1d222d4f16d3e721024a3c105f786349ab6548fc
@@ -36,6 +36,7 @@ module WCC::Contentful
36
36
  def authorize_contentful
37
37
  config = WCC::Contentful.configuration
38
38
 
39
+ # rubocop:disable Style/SoleNestedConditional
39
40
  if config.webhook_username.present? && config.webhook_password.present?
40
41
  unless authenticate_with_http_basic do |u, p|
41
42
  u == config.webhook_username &&
@@ -45,6 +46,7 @@ module WCC::Contentful
45
46
  return
46
47
  end
47
48
  end
49
+ # rubocop:enable Style/SoleNestedConditional
48
50
 
49
51
  # 'application/vnd.contentful.management.v1+json' is an alias for the 'application/json'
50
52
  # content-type, so 'request.content_type' will give 'application/json'
@@ -11,7 +11,7 @@ module WCC::Contentful
11
11
  args = default_configuration.merge!(args)
12
12
 
13
13
  client = WCC::Contentful::SimpleClient::Management.new(
14
- args
14
+ **args
15
15
  )
16
16
  enable_webhook(client, args.slice(:receive_url, :webhook_username, :webhook_password))
17
17
  end
@@ -5,7 +5,7 @@ require 'wcc/contentful/downloads_schema'
5
5
 
6
6
  namespace :wcc_contentful do
7
7
  desc 'Downloads the schema from the currently configured space and stores it in ' \
8
- 'db/contentful-schema.json'
8
+ 'db/contentful-schema.json'
9
9
  task download_schema: :environment do
10
10
  WCC::Contentful::DownloadsSchema.call
11
11
  end
@@ -64,13 +64,13 @@ module WCC::Contentful::ActiveRecordShim
64
64
  }
65
65
  }
66
66
 
67
- find_all(filter).each_slice(batch_size, &block)
67
+ find_all(**filter).each_slice(batch_size, &block)
68
68
  end
69
69
 
70
70
  def where(**conditions)
71
71
  # TODO: return a Query object that implements more of the ActiveRecord query interface
72
72
  # https://guides.rubyonrails.org/active_record_querying.html#conditions
73
- find_all(conditions)
73
+ find_all(**conditions)
74
74
  end
75
75
  end
76
76
  end
@@ -3,22 +3,25 @@
3
3
  # This object contains all the configuration options for the `wcc-contentful` gem.
4
4
  class WCC::Contentful::Configuration
5
5
  ATTRIBUTES = %i[
6
- space
7
6
  access_token
8
7
  app_url
9
- management_token
10
- environment
11
- default_locale
12
- preview_token
13
- webhook_username
14
- webhook_password
15
- webhook_jobs
16
8
  connection
17
9
  connection_options
18
- update_schema_file
10
+ default_locale
11
+ environment
12
+ instrumentation_adapter
13
+ logger
14
+ management_token
15
+ preview_token
19
16
  schema_file
17
+ space
20
18
  store
21
- instrumentation_adapter
19
+ sync_retry_limit
20
+ sync_retry_wait
21
+ update_schema_file
22
+ webhook_jobs
23
+ webhook_password
24
+ webhook_username
22
25
  ].freeze
23
26
 
24
27
  # (required) Sets the Contentful Space ID.
@@ -57,6 +60,17 @@ class WCC::Contentful::Configuration
57
60
  # to implement a webhook job.
58
61
  attr_accessor :webhook_jobs
59
62
 
63
+ # Sets the maximum number of times that the SyncEngine will retry synchronization
64
+ # when it detects that the Contentful CDN's cache has not been updated after a webhook.
65
+ # Default: 2
66
+ attr_accessor :sync_retry_limit
67
+
68
+ # Sets the base ActiveSupport::Duration that the SyncEngine will wait before retrying.
69
+ # Each subsequent retry uses an exponential backoff, so the second retry will be
70
+ # after (2 * sync_retry_wait), the third after (4 * sync_retry_wait), etc.
71
+ # Default: 2.seconds
72
+ attr_accessor :sync_retry_wait
73
+
60
74
  # Returns true if the currently configured environment is pointing at `master`.
61
75
  def master?
62
76
  !environment.present?
@@ -145,9 +159,7 @@ class WCC::Contentful::Configuration
145
159
  # WCC::Contentful::InitializationError if the API cannot be reached.
146
160
  def update_schema_file=(sym)
147
161
  valid_syms = %i[never if_possible if_missing always]
148
- unless valid_syms.include?(sym)
149
- raise ArgumentError, "update_schema_file must be one of #{valid_syms}"
150
- end
162
+ raise ArgumentError, "update_schema_file must be one of #{valid_syms}" unless valid_syms.include?(sym)
151
163
 
152
164
  @update_schema_file = sym
153
165
  end
@@ -171,23 +183,30 @@ class WCC::Contentful::Configuration
171
183
  # to :instrument like ActiveSupport::Notifications.instrument
172
184
  attr_accessor :instrumentation_adapter
173
185
 
186
+ # Sets the logger to be used by the wcc-contentful gem, including stores.
187
+ # Defaults to the rails logger if in a rails context, otherwise creates a new
188
+ # logger that writes to STDERR.
189
+ attr_accessor :logger
190
+
174
191
  def initialize
175
- @access_token = ENV['CONTENTFUL_ACCESS_TOKEN']
176
- @app_url = ENV['APP_URL']
192
+ @access_token = ENV.fetch('CONTENTFUL_ACCESS_TOKEN', nil)
193
+ @app_url = ENV.fetch('APP_URL', nil)
177
194
  @connection_options = {
178
195
  api_url: 'https://cdn.contentful.com/',
179
196
  preview_api_url: 'https://preview.contentful.com/',
180
197
  management_api_url: 'https://api.contentful.com'
181
198
  }
182
- @management_token = ENV['CONTENTFUL_MANAGEMENT_TOKEN']
183
- @preview_token = ENV['CONTENTFUL_PREVIEW_TOKEN']
184
- @space = ENV['CONTENTFUL_SPACE_ID']
199
+ @management_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', nil)
200
+ @preview_token = ENV.fetch('CONTENTFUL_PREVIEW_TOKEN', nil)
201
+ @space = ENV.fetch('CONTENTFUL_SPACE_ID', nil)
185
202
  @default_locale = nil
186
203
  @middleware = []
187
204
  @update_schema_file = :if_possible
188
205
  @schema_file = 'db/contentful-schema.json'
189
206
  @webhook_jobs = []
190
207
  @store_factory = WCC::Contentful::Store::Factory.new(self, :direct)
208
+ @sync_retry_limit = 3
209
+ @sync_retry_wait = 1.second
191
210
  end
192
211
 
193
212
  # Validates the configuration, raising ArgumentError if anything is wrong. This
@@ -137,6 +137,8 @@ module WCC::Contentful
137
137
  :Json
138
138
  when 'Location'
139
139
  :Coordinates
140
+ when 'RichText'
141
+ :RichText
140
142
  when 'Array'
141
143
  find_field_type(field.try(:items) || field['items'])
142
144
  when 'Link'
@@ -36,7 +36,7 @@ class WCC::Contentful::DownloadsSchema
36
36
  begin
37
37
  JSON.parse(File.read(@file))
38
38
  rescue JSON::ParserError
39
- return true
39
+ return true # rubocop:disable Lint/NoReturnInBeginEndBlocks
40
40
  end
41
41
 
42
42
  existing_cts = contents['contentTypes'].sort_by { |ct| ct.dig('sys', 'id') }
@@ -83,13 +83,14 @@ class WCC::Contentful::DownloadsSchema
83
83
  end
84
84
 
85
85
  def deep_contains_all(expected, actual)
86
- if expected.is_a? Array
86
+ case expected
87
+ when Array
87
88
  expected.each_with_index do |val, i|
88
89
  return false unless actual[i]
89
90
  return false unless deep_contains_all(val, actual[i])
90
91
  end
91
92
  true
92
- elsif expected.is_a? Hash
93
+ when Hash
93
94
  expected.each do |(key, val)|
94
95
  return false unless actual.key?(key)
95
96
  return false unless deep_contains_all(val, actual[key])
@@ -107,6 +108,6 @@ class WCC::Contentful::DownloadsSchema
107
108
  # only in its treatment of empty arrays in the "validations" field.
108
109
  json_string = json_string.gsub(/\[\n\n\s+\]/, '[]')
109
110
  # contentful-shell also adds a newline at the end.
110
- json_string + "\n"
111
+ "#{json_string}\n"
111
112
  end
112
113
  end
@@ -6,9 +6,7 @@ module WCC::Contentful
6
6
  config = WCC::Contentful.configuration
7
7
 
8
8
  jobs = []
9
- if WCC::Contentful::Services.instance.sync_engine&.should_sync?
10
- jobs << WCC::Contentful::SyncEngine::Job
11
- end
9
+ jobs << WCC::Contentful::SyncEngine::Job if WCC::Contentful::Services.instance.sync_engine&.should_sync?
12
10
  jobs.push(*WCC::Contentful.configuration.webhook_jobs)
13
11
 
14
12
  jobs.each do |job|
@@ -19,7 +17,7 @@ module WCC::Contentful
19
17
  job.perform_later(event.to_h)
20
18
  else
21
19
  Rails.logger.error "Misconfigured webhook job: #{job} does not respond to " \
22
- ':perform_later'
20
+ ':perform_later'
23
21
  end
24
22
  rescue StandardError => e
25
23
  warn "Error in job #{job}: #{e}"
@@ -7,10 +7,10 @@ module WCC::Contentful::Event
7
7
 
8
8
  # Creates an Event out of a raw value received by a webhook or given from
9
9
  # the Contentful Sync API.
10
- def self.from_raw(raw, context = nil)
10
+ def self.from_raw(raw, context = nil, source: nil)
11
11
  const = Registry.instance.get(raw.dig('sys', 'type'))
12
12
 
13
- const.new(raw, context)
13
+ const.new(raw, context, source: source)
14
14
  end
15
15
 
16
16
  class Registry
@@ -23,9 +23,7 @@ module WCC::Contentful::Event
23
23
 
24
24
  def register(constant)
25
25
  name = constant.try(:type) || constant.name.demodulize
26
- unless constant.respond_to?(:new)
27
- raise ArgumentError, "Constant #{constant} does not define 'new'"
28
- end
26
+ raise ArgumentError, "Constant #{constant} does not define 'new'" unless constant.respond_to?(:new)
29
27
 
30
28
  @event_types ||= {}
31
29
  @event_types[name] = constant
@@ -58,6 +56,7 @@ module WCC::Contentful::Event
58
56
  attr_reader :sys
59
57
  attr_reader :raw
60
58
  attr_reader :source
59
+
61
60
  delegate :id, to: :sys
62
61
  delegate :type, to: :sys
63
62
  delegate :created_at, to: :sys
@@ -134,12 +133,6 @@ class WCC::Contentful::Event::SyncComplete
134
133
  include WCC::Contentful::Event
135
134
 
136
135
  def initialize(items, context = nil, source: nil)
137
- items =
138
- items.map do |item|
139
- next item if item.is_a? WCC::Contentful::Event
140
-
141
- WCC::Contentful::Event.from_raw(item, context, source: source)
142
- end
143
136
  @items = items.freeze
144
137
  @source = source
145
138
  @sys = WCC::Contentful::Sys.new(
@@ -12,8 +12,7 @@ module WCC::Contentful
12
12
  # Raised when an entry contains a circular reference and cannot be represented
13
13
  # as a flat tree.
14
14
  class CircularReferenceError < StandardError
15
- attr_reader :stack
16
- attr_reader :id
15
+ attr_reader :stack, :id
17
16
 
18
17
  def initialize(stack, id)
19
18
  @id = id
@@ -25,7 +24,7 @@ module WCC::Contentful
25
24
  return super unless stack
26
25
 
27
26
  super + "\n " \
28
- "#{stack.last} points to #{id} which is also it's ancestor\n " +
27
+ "#{stack.last} points to #{id} which is also it's ancestor\n " +
29
28
  stack.join('->')
30
29
  end
31
30
  end
@@ -103,14 +103,13 @@ module WCC::Contentful
103
103
  Boolean
104
104
  Json
105
105
  Coordinates
106
+ RichText
106
107
  Link
107
108
  Asset
108
109
  ].freeze
109
110
 
110
111
  def type=(raw_type)
111
- unless TYPES.include?(raw_type)
112
- raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}"
113
- end
112
+ raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}" unless TYPES.include?(raw_type)
114
113
 
115
114
  @type = raw_type
116
115
  end
@@ -130,9 +129,7 @@ module WCC::Contentful
130
129
 
131
130
  if raw_type = hash_or_id.delete('type')
132
131
  raw_type = raw_type.to_sym
133
- unless TYPES.include?(raw_type)
134
- raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}"
135
- end
132
+ raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}" unless TYPES.include?(raw_type)
136
133
 
137
134
  @type = raw_type
138
135
  end
@@ -7,11 +7,12 @@ module WCC::Contentful
7
7
  def _instrumentation_event_prefix
8
8
  @_instrumentation_event_prefix ||=
9
9
  # WCC::Contentful => contentful.wcc
10
- '.' + (is_a?(Class) || is_a?(Module) ? self : self.class)
10
+ '.' + (is_a?(Class) || is_a?(Module) ? self : self.class) # rubocop:disable Style/StringConcatenation
11
11
  .name.parameterize.split('-').reverse.join('.')
12
12
  end
13
13
 
14
14
  attr_writer :_instrumentation
15
+
15
16
  def _instrumentation
16
17
  # look for per-instance instrumentation then try class level
17
18
  @_instrumentation || self.class._instrumentation
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class WCC::Contentful::Link
4
- attr_reader :id
5
- attr_reader :link_type
6
- attr_reader :raw
4
+ attr_reader :id, :link_type, :raw
7
5
 
8
6
  LINK_TYPES = {
9
7
  Asset: 'Asset',
@@ -4,9 +4,7 @@
4
4
  # It is used internally by the Store layer to compose the resulting resolved hashes.
5
5
  # But you can use it too!
6
6
  class WCC::Contentful::LinkVisitor
7
- attr_reader :entry
8
- attr_reader :fields
9
- attr_reader :depth
7
+ attr_reader :entry, :fields, :depth
10
8
 
11
9
  # @param [Hash] entry The entry hash (resolved or unresolved) to walk
12
10
  # @param [Array<Symbol>] The type of fields to select from the entry tree.
@@ -66,7 +64,7 @@ class WCC::Contentful::LinkVisitor
66
64
 
67
65
  def each_field(field)
68
66
  each_locale(field) do |val, locale|
69
- if val&.is_a?(Array)
67
+ if val.is_a?(Array)
70
68
  val.each_with_index do |v, index|
71
69
  yield(v, field, locale, index) unless v.nil?
72
70
  end
@@ -20,7 +20,7 @@ module WCC::Contentful::Middleware::Store
20
20
  event = 'miss'
21
21
  # if it's not a contentful ID don't hit the API.
22
22
  # Store a nil object if we can't find the object on the CDN.
23
- (store.find(key, options) || nil_obj(key)) if key =~ /^\w+$/
23
+ (store.find(key, **options) || nil_obj(key)) if key =~ /^\w+$/
24
24
  end
25
25
  _instrument(event, key: key, options: options)
26
26
 
@@ -36,12 +36,8 @@ module WCC::Contentful::Middleware::Store
36
36
  # figure out how to cache the results of a find_by query, ex:
37
37
  # `find_by('slug' => '/about')`
38
38
  def find_by(content_type:, filter: nil, options: nil)
39
- if filter&.keys == ['sys.id']
40
- # Direct ID lookup, like what we do in `WCC::Contentful::ModelMethods.resolve`
41
- # We can return just this item. Stores are not required to implement :include option.
42
- if found = @cache.read(filter['sys.id'])
43
- return found
44
- end
39
+ if filter&.keys == ['sys.id'] && found = @cache.read(filter['sys.id'])
40
+ return found
45
41
  end
46
42
 
47
43
  store.find_by(content_type: content_type, filter: filter, options: options)
@@ -79,8 +75,8 @@ module WCC::Contentful::Middleware::Store
79
75
  prev = @cache.read(id)
80
76
  return if prev.nil? && LAZILY_CACHEABLE_TYPES.include?(type)
81
77
 
82
- if (prev_rev = prev&.dig('sys', 'revision')) && (next_rev = json.dig('sys', 'revision'))
83
- return prev if next_rev < prev_rev
78
+ if (prev_rev = prev&.dig('sys', 'revision')) && (next_rev = json.dig('sys', 'revision')) && (next_rev < prev_rev)
79
+ return prev
84
80
  end
85
81
 
86
82
  # we also set DeletedEntry objects in the cache - no need to go hit the API when we know
@@ -96,9 +96,9 @@ module WCC::Contentful::Middleware::Store
96
96
 
97
97
  def count
98
98
  if middleware.has_select?
99
- raise NameError, "Count cannot be determined because the middleware '#{middleware}'" \
100
- " implements the #select? method. Please use '.to_a.count' to count entries that" \
101
- ' pass the #select? method.'
99
+ raise NameError, "Count cannot be determined because the middleware '#{middleware}' " \
100
+ "implements the #select? method. Please use '.to_a.count' to count entries that " \
101
+ 'pass the #select? method.'
102
102
  end
103
103
 
104
104
  # The wrapped query may get count from the "Total" field in the response,
@@ -112,9 +112,7 @@ module WCC::Contentful::Middleware::Store
112
112
  result = wrapped_query.to_enum
113
113
  result = result.select { |x| middleware.select?(x) } if middleware.has_select?
114
114
 
115
- if options && options[:include]
116
- result = result.map { |x| middleware.resolve_includes(x, options[:include]) }
117
- end
115
+ result = result.map { |x| middleware.resolve_includes(x, options[:include]) } if options && options[:include]
118
116
 
119
117
  result.map { |x| middleware.transform(x) }
120
118
  end
@@ -51,9 +51,7 @@ module WCC::Contentful::ModelAPI
51
51
 
52
52
  file = configuration.schema_file
53
53
  schema_json = JSON.parse(File.read(file))['contentTypes']
54
- unless schema_json
55
- raise ArgumentError, 'Please give either a JSON array of content types or file to load from'
56
- end
54
+ raise ArgumentError, 'Please give either a JSON array of content types or file to load from' unless schema_json
57
55
 
58
56
  @schema = WCC::Contentful::ContentTypeIndexer.from_json_schema(schema_json).types
59
57
  end
@@ -68,7 +66,7 @@ module WCC::Contentful::ModelAPI
68
66
  store = options[:preview] ? services.preview_store : services.store
69
67
  raw =
70
68
  _instrumentation.instrument 'find.model.contentful.wcc', id: id, options: options do
71
- store.find(id, options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
69
+ store.find(id, **options.except(*WCC::Contentful::ModelMethods::MODEL_LAYER_CONTEXT_KEYS))
72
70
  end
73
71
 
74
72
  new_from_raw(raw, options) if raw.present?
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative './link'
4
4
  require_relative './sys'
5
+ require_relative './rich_text'
5
6
 
6
7
  module WCC::Contentful
7
8
  class ModelBuilder
@@ -60,7 +61,7 @@ module WCC::Contentful
60
61
  ct = content_type_from_raw(raw)
61
62
  if ct != typedef.content_type
62
63
  raise ArgumentError, 'Wrong Content Type - ' \
63
- "'#{raw.dig('sys', 'id')}' is a #{ct}, expected #{typedef.content_type}"
64
+ "'#{raw.dig('sys', 'id')}' is a #{ct}, expected #{typedef.content_type}"
64
65
  end
65
66
  @raw = raw.freeze
66
67
 
@@ -95,6 +96,8 @@ module WCC::Contentful
95
96
  #
96
97
  # when :DateTime
97
98
  # raw_value = Time.parse(raw_value).localtime
99
+ when :RichText
100
+ raw_value = WCC::Contentful::RichText.tokenize(raw_value)
98
101
  when :Int
99
102
  raw_value = Integer(raw_value)
100
103
  when :Float
@@ -104,12 +107,13 @@ module WCC::Contentful
104
107
  # array fields need to resolve to an empty array when nothing is there
105
108
  raw_value = []
106
109
  end
107
- instance_variable_set('@' + f.name, raw_value)
110
+ instance_variable_set("@#{f.name}", raw_value)
108
111
  end
109
112
  end
110
113
 
111
114
  attr_reader :sys
112
115
  attr_reader :raw
116
+
113
117
  delegate :id, to: :sys
114
118
  delegate :created_at, to: :sys
115
119
  delegate :updated_at, to: :sys
@@ -119,11 +123,11 @@ module WCC::Contentful
119
123
  # Make a field for each column:
120
124
  typedef.fields.each_value do |f|
121
125
  name = f.name
122
- var_name = '@' + name
126
+ var_name = "@#{name}"
123
127
  case f.type
124
128
  when :Asset, :Link
125
129
  define_method(name) do
126
- val = instance_variable_get(var_name + '_resolved')
130
+ val = instance_variable_get("#{var_name}_resolved")
127
131
  return val if val.present?
128
132
 
129
133
  _resolve_field(name)
@@ -54,17 +54,15 @@ module WCC::Contentful::ModelMethods
54
54
  _instrument 'resolve', id: id, depth: depth, backlinks: backlinked_ids do
55
55
  # use include param to do resolution
56
56
  store.find_by(content_type: self.class.content_type,
57
- filter: { 'sys.id' => id },
58
- options: context.except(*MODEL_LAYER_CONTEXT_KEYS).merge!({
59
- include: [depth, 10].min
60
- }))
57
+ filter: { 'sys.id' => id },
58
+ options: context.except(*MODEL_LAYER_CONTEXT_KEYS).merge!({
59
+ include: [depth, 10].min
60
+ }))
61
61
  end
62
- unless raw
63
- raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}"
64
- end
62
+ raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}" unless raw
65
63
 
66
64
  @raw = raw.freeze
67
- links.each { |f| instance_variable_set('@' + f, raw.dig('fields', f, sys.locale)) }
65
+ links.each { |f| instance_variable_set("@#{f}", raw.dig('fields', f, sys.locale)) }
68
66
  end
69
67
 
70
68
  links.each { |f| _resolve_field(f, depth, context, options) }
@@ -142,7 +140,7 @@ module WCC::Contentful::ModelMethods
142
140
  def _resolve_field(field_name, depth = 1, context = {}, options = {})
143
141
  return if depth <= 0
144
142
 
145
- var_name = '@' + field_name
143
+ var_name = "@#{field_name}"
146
144
  return unless val = instance_variable_get(var_name)
147
145
 
148
146
  context = sys.context.to_h.merge(context)
@@ -182,17 +180,17 @@ module WCC::Contentful::ModelMethods
182
180
  val = _try_map(val) { |v| load.call(v) }
183
181
  val = val.compact if val.is_a? Array
184
182
 
185
- instance_variable_set(var_name + '_resolved', val)
183
+ instance_variable_set("#{var_name}_resolved", val)
186
184
  rescue WCC::Contentful::CircularReferenceError
187
185
  raise unless options[:circular_reference] == :ignore
188
186
  end
189
187
  end
190
188
 
191
189
  def _resolved_field?(field_name, depth = 1)
192
- var_name = '@' + field_name
190
+ var_name = "@#{field_name}"
193
191
  raw = instance_variable_get(var_name)
194
192
  return true if raw.nil? || (raw.is_a?(Array) && raw.all?(&:nil?))
195
- return false unless val = instance_variable_get(var_name + '_resolved')
193
+ return false unless val = instance_variable_get("#{var_name}_resolved")
196
194
  return true if depth <= 1
197
195
 
198
196
  return val.resolved?(depth: depth - 1) unless val.is_a? Array
@@ -16,7 +16,7 @@ module WCC::Contentful::ModelSingletonMethods
16
16
  raw =
17
17
  _instrumentation.instrument 'find.model.contentful.wcc',
18
18
  content_type: content_type, id: id, options: options do
19
- store.find(id, { hint: type }.merge!(options.except(:preview)))
19
+ store.find(id, **{ hint: type }.merge!(options.except(:preview)))
20
20
  end
21
21
  new(raw, options) if raw.present?
22
22
  end
@@ -66,7 +66,7 @@ module WCC::Contentful::ModelSingletonMethods
66
66
  new(result, options) if result
67
67
  end
68
68
 
69
- def inherited(subclass)
69
+ def inherited(subclass) # rubocop:disable Lint/MissingSuper
70
70
  # If another different class is already registered for this content type,
71
71
  # don't auto-register this one.
72
72
  return if model_namespace.registered?(content_type)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WCC::Contentful::RichText
4
+ module Node
5
+ extend ActiveSupport::Concern
6
+
7
+ def keys
8
+ members.map(&:to_s)
9
+ end
10
+
11
+ included do
12
+ include Enumerable
13
+
14
+ alias_method :node_type, :nodeType
15
+
16
+ # Make the nodes read-only
17
+ undef_method :[]=
18
+ members.each do |member|
19
+ undef_method("#{member}=")
20
+ end
21
+
22
+ # Override each so it has a Hash-like interface rather than Struct-like.
23
+ # The goal being to mimic a JSON-parsed hash representation of the raw
24
+ def each
25
+ members.map do |key|
26
+ tuple = [key.to_s, self.[](key)]
27
+ yield tuple if block_given?
28
+ tuple
29
+ end
30
+ end
31
+ end
32
+
33
+ class_methods do
34
+ # Default value for node_type covers most cases
35
+ def node_type
36
+ name.demodulize.underscore.dasherize
37
+ end
38
+
39
+ def tokenize(raw, context = nil)
40
+ raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'" unless raw['nodeType'] == node_type
41
+
42
+ values =
43
+ members.map do |symbol|
44
+ val = raw[symbol.to_s]
45
+
46
+ case symbol
47
+ when :content
48
+ WCC::Contentful::RichText.tokenize(val, context)
49
+ # when :data
50
+ # TODO: resolve links...
51
+ else
52
+ val
53
+ end
54
+ end
55
+
56
+ new(*values)
57
+ end
58
+ end
59
+ end
60
+ end