wcc-contentful 1.1.1 → 1.2.1

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 (43) 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 +31 -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.rb +0 -6
  19. data/lib/wcc/contentful/model_api.rb +17 -12
  20. data/lib/wcc/contentful/model_builder.rb +8 -4
  21. data/lib/wcc/contentful/model_methods.rb +10 -12
  22. data/lib/wcc/contentful/model_singleton_methods.rb +4 -4
  23. data/lib/wcc/contentful/rich_text/node.rb +60 -0
  24. data/lib/wcc/contentful/rich_text.rb +105 -0
  25. data/lib/wcc/contentful/rspec.rb +1 -3
  26. data/lib/wcc/contentful/simple_client/response.rb +28 -34
  27. data/lib/wcc/contentful/simple_client.rb +11 -14
  28. data/lib/wcc/contentful/store/base.rb +5 -5
  29. data/lib/wcc/contentful/store/cdn_adapter.rb +11 -7
  30. data/lib/wcc/contentful/store/factory.rb +1 -1
  31. data/lib/wcc/contentful/store/memory_store.rb +2 -2
  32. data/lib/wcc/contentful/store/postgres_store.rb +15 -22
  33. data/lib/wcc/contentful/store/query.rb +2 -2
  34. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +6 -4
  35. data/lib/wcc/contentful/store/rspec_examples/operators.rb +1 -1
  36. data/lib/wcc/contentful/sync_engine.rb +31 -15
  37. data/lib/wcc/contentful/sys.rb +2 -1
  38. data/lib/wcc/contentful/test/double.rb +3 -5
  39. data/lib/wcc/contentful/test/factory.rb +4 -6
  40. data/lib/wcc/contentful/version.rb +1 -1
  41. data/lib/wcc/contentful.rb +6 -8
  42. data/wcc-contentful.gemspec +6 -6
  43. metadata +13 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 964aa9bb9b781c8ca90803f8117130daf1aac1ea88324ab64237b0e2a6a6c328
4
- data.tar.gz: 834dd56d5e39fdad058efa794f1e5022b92d9122463c827d91d85ffcd9dbe291
3
+ metadata.gz: 476f7c65f705c14e012e8c2cbb36f68e29782607377b0a4aa9d0c10fb93458da
4
+ data.tar.gz: 01aeff71919acef51c8d498c63bc42e822ed3c598e7c636f5faf74baeb74c552
5
5
  SHA512:
6
- metadata.gz: ff532ebd5b3750554a63786877d846c724e5edb28796f964977b7bc03064eada4ea3c608d0b0809a4306306c5bb75dfedc0ed5fa670ef7fb2b224b39ebcaafcc
7
- data.tar.gz: '0886f5a8caad820a963af4d37a07fb319ae5cfc8735f400f41eb22131df9f90477b966d91f3e2f26e40bffc1579bcbc9bba2dd351cb677a28df90dea87368eef'
6
+ metadata.gz: '0383a5fe8ef8b042d0a9aca04c964051fc5201b89e702a80b616e5c3b36fd6fa7590d768d178e96f3353c998923fc87458697f5b1112bdd000e6e273fdb07f56'
7
+ data.tar.gz: 776a6ec463edbcd209d28dd2033d6378ac45e44dd9bca790aefe81ea529c4fa4d0cf6725bd3f67e5c4054f41bd930e9905165f8ccd42f2a9f01b3bc0ac1deaf6
@@ -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,24 @@
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
+ management_token
14
+ preview_token
19
15
  schema_file
16
+ space
20
17
  store
21
- instrumentation_adapter
18
+ sync_retry_limit
19
+ sync_retry_wait
20
+ update_schema_file
21
+ webhook_jobs
22
+ webhook_password
23
+ webhook_username
22
24
  ].freeze
23
25
 
24
26
  # (required) Sets the Contentful Space ID.
@@ -57,6 +59,17 @@ class WCC::Contentful::Configuration
57
59
  # to implement a webhook job.
58
60
  attr_accessor :webhook_jobs
59
61
 
62
+ # Sets the maximum number of times that the SyncEngine will retry synchronization
63
+ # when it detects that the Contentful CDN's cache has not been updated after a webhook.
64
+ # Default: 2
65
+ attr_accessor :sync_retry_limit
66
+
67
+ # Sets the base ActiveSupport::Duration that the SyncEngine will wait before retrying.
68
+ # Each subsequent retry uses an exponential backoff, so the second retry will be
69
+ # after (2 * sync_retry_wait), the third after (4 * sync_retry_wait), etc.
70
+ # Default: 2.seconds
71
+ attr_accessor :sync_retry_wait
72
+
60
73
  # Returns true if the currently configured environment is pointing at `master`.
61
74
  def master?
62
75
  !environment.present?
@@ -145,9 +158,7 @@ class WCC::Contentful::Configuration
145
158
  # WCC::Contentful::InitializationError if the API cannot be reached.
146
159
  def update_schema_file=(sym)
147
160
  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
161
+ raise ArgumentError, "update_schema_file must be one of #{valid_syms}" unless valid_syms.include?(sym)
151
162
 
152
163
  @update_schema_file = sym
153
164
  end
@@ -172,22 +183,24 @@ class WCC::Contentful::Configuration
172
183
  attr_accessor :instrumentation_adapter
173
184
 
174
185
  def initialize
175
- @access_token = ENV['CONTENTFUL_ACCESS_TOKEN']
176
- @app_url = ENV['APP_URL']
186
+ @access_token = ENV.fetch('CONTENTFUL_ACCESS_TOKEN', nil)
187
+ @app_url = ENV.fetch('APP_URL', nil)
177
188
  @connection_options = {
178
189
  api_url: 'https://cdn.contentful.com/',
179
190
  preview_api_url: 'https://preview.contentful.com/',
180
191
  management_api_url: 'https://api.contentful.com'
181
192
  }
182
- @management_token = ENV['CONTENTFUL_MANAGEMENT_TOKEN']
183
- @preview_token = ENV['CONTENTFUL_PREVIEW_TOKEN']
184
- @space = ENV['CONTENTFUL_SPACE_ID']
193
+ @management_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', nil)
194
+ @preview_token = ENV.fetch('CONTENTFUL_PREVIEW_TOKEN', nil)
195
+ @space = ENV.fetch('CONTENTFUL_SPACE_ID', nil)
185
196
  @default_locale = nil
186
197
  @middleware = []
187
198
  @update_schema_file = :if_possible
188
199
  @schema_file = 'db/contentful-schema.json'
189
200
  @webhook_jobs = []
190
201
  @store_factory = WCC::Contentful::Store::Factory.new(self, :direct)
202
+ @sync_retry_limit = 3
203
+ @sync_retry_wait = 1.second
191
204
  end
192
205
 
193
206
  # 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
@@ -41,10 +41,6 @@ require_relative './model_api'
41
41
  class WCC::Contentful::Model
42
42
  include WCC::Contentful::ModelAPI
43
43
 
44
- # The Model base class maintains a registry which is best expressed as a
45
- # class var.
46
- # rubocop:disable Style/ClassVars
47
-
48
44
  class << self
49
45
  def const_missing(name)
50
46
  type = WCC::Contentful::Helpers.content_type_from_constant(name)
@@ -53,5 +49,3 @@ class WCC::Contentful::Model
53
49
  end
54
50
  end
55
51
  end
56
-
57
- # rubocop:enable Style/ClassVars
@@ -13,9 +13,8 @@ module WCC::Contentful::ModelAPI
13
13
  services.instrumentation
14
14
  end
15
15
 
16
- # We use a class var here because this is a global registry for all subclasses
17
- # of this namespace
18
- @@registry = {} # rubocop:disable Style/ClassVars
16
+ # Set the registry at the top of the namespace
17
+ @registry = {}
19
18
  end
20
19
 
21
20
  class_methods do
@@ -52,9 +51,7 @@ module WCC::Contentful::ModelAPI
52
51
 
53
52
  file = configuration.schema_file
54
53
  schema_json = JSON.parse(File.read(file))['contentTypes']
55
- unless schema_json
56
- raise ArgumentError, 'Please give either a JSON array of content types or file to load from'
57
- end
54
+ raise ArgumentError, 'Please give either a JSON array of content types or file to load from' unless schema_json
58
55
 
59
56
  @schema = WCC::Contentful::ContentTypeIndexer.from_json_schema(schema_json).types
60
57
  end
@@ -69,7 +66,7 @@ module WCC::Contentful::ModelAPI
69
66
  store = options[:preview] ? services.preview_store : services.store
70
67
  raw =
71
68
  _instrumentation.instrument 'find.model.contentful.wcc', id: id, options: options do
72
- 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))
73
70
  end
74
71
 
75
72
  new_from_raw(raw, options) if raw.present?
@@ -89,7 +86,7 @@ module WCC::Contentful::ModelAPI
89
86
  def resolve_constant(content_type)
90
87
  raise ArgumentError, 'content_type cannot be nil' unless content_type
91
88
 
92
- const = @@registry[content_type]
89
+ const = _registry[content_type]
93
90
  return const if const
94
91
 
95
92
  const_name = WCC::Contentful::Helpers.constant_from_content_type(content_type).to_s
@@ -143,14 +140,14 @@ module WCC::Contentful::ModelAPI
143
140
 
144
141
  content_type ||= WCC::Contentful::Helpers.content_type_from_constant(klass)
145
142
 
146
- @@registry[content_type] = klass
143
+ _registry[content_type] = klass
147
144
  end
148
145
 
149
146
  # Returns the current registry of content type names to constants.
150
147
  def registry
151
- return {} unless @@registry
148
+ return {} unless _registry
152
149
 
153
- @@registry.dup.freeze
150
+ _registry.dup.freeze
154
151
  end
155
152
 
156
153
  def reload!
@@ -176,7 +173,15 @@ module WCC::Contentful::ModelAPI
176
173
  # that class. If nil, the generated WCC::Contentful::Model::{content_type} class
177
174
  # will be resolved for this content type.
178
175
  def registered?(content_type)
179
- @@registry[content_type]
176
+ _registry[content_type]
177
+ end
178
+
179
+ protected
180
+
181
+ def _registry
182
+ # If calling register_for_content_type in a subclass, look up the superclass
183
+ # chain until we get to the model namespace which defines the registry
184
+ @registry || superclass._registry
180
185
  end
181
186
  end
182
187
  end
@@ -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,12 +66,12 @@ 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
- return if WCC::Contentful::Model.registered?(content_type)
72
+ return if model_namespace.registered?(content_type)
73
73
 
74
- WCC::Contentful::Model.register_for_content_type(content_type, klass: subclass)
74
+ model_namespace.register_for_content_type(content_type, klass: subclass)
75
75
  end
76
76
 
77
77
  class ModelQuery