wcc-contentful 1.2.0 → 1.3.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/README.md +25 -1
  3. data/app/controllers/wcc/contentful/webhook_controller.rb +2 -0
  4. data/lib/tasks/download_schema.rake +1 -1
  5. data/lib/wcc/contentful/configuration.rb +37 -18
  6. data/lib/wcc/contentful/downloads_schema.rb +5 -4
  7. data/lib/wcc/contentful/engine.rb +2 -4
  8. data/lib/wcc/contentful/event.rb +2 -3
  9. data/lib/wcc/contentful/exceptions.rb +2 -3
  10. data/lib/wcc/contentful/indexed_representation.rb +2 -6
  11. data/lib/wcc/contentful/instrumentation.rb +2 -1
  12. data/lib/wcc/contentful/link.rb +1 -3
  13. data/lib/wcc/contentful/link_visitor.rb +2 -4
  14. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +4 -8
  15. data/lib/wcc/contentful/middleware/store.rb +4 -6
  16. data/lib/wcc/contentful/model_api.rb +3 -5
  17. data/lib/wcc/contentful/model_builder.rb +5 -4
  18. data/lib/wcc/contentful/model_methods.rb +10 -12
  19. data/lib/wcc/contentful/model_singleton_methods.rb +1 -1
  20. data/lib/wcc/contentful/rich_text/node.rb +1 -3
  21. data/lib/wcc/contentful/rich_text.rb +1 -1
  22. data/lib/wcc/contentful/rspec.rb +1 -3
  23. data/lib/wcc/contentful/services.rb +9 -0
  24. data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
  25. data/lib/wcc/contentful/simple_client/preview.rb +17 -0
  26. data/lib/wcc/contentful/simple_client/response.rb +24 -19
  27. data/lib/wcc/contentful/simple_client.rb +13 -118
  28. data/lib/wcc/contentful/store/base.rb +19 -27
  29. data/lib/wcc/contentful/store/cdn_adapter.rb +1 -1
  30. data/lib/wcc/contentful/store/factory.rb +1 -1
  31. data/lib/wcc/contentful/store/memory_store.rb +10 -7
  32. data/lib/wcc/contentful/store/postgres_store.rb +52 -42
  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 +58 -34
  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 +9 -12
  42. data/wcc-contentful.gemspec +5 -5
  43. metadata +37 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f05ea585d54f7ca83c96e018021f88776ebb148cc40b7d719d6199b3a3cd9d0c
4
- data.tar.gz: 0da490d12beb9938ec51c0ac40e140420966667b72afebc5c9895cf197308ea8
3
+ metadata.gz: 58a77a1fbe7e5a7f99d8f13142c29ccac39d7101dfbe2769f542d18e88634a40
4
+ data.tar.gz: 8a4748e01236daaf0f6bce2c32b2999e02205de77977b6fee01280ea84b83241
5
5
  SHA512:
6
- metadata.gz: 979360a35356b041dbe0f8bbd4f0e5bfcaf91909ccc567322ceef8493de25300ea09a081e755142fe23bf9cfd5ea86ca7f919d3218d5dec44a98bcab5921af7d
7
- data.tar.gz: 9e5f29e70fdcbccffd72aeb8b896e3518d2236cd699704289e8a3fdf1392201dc49671bab0eb83948e79ee079366de90b46add4ce2027e065db2da28b2be4e85
6
+ metadata.gz: 992a019411bac0fb5c14819db375ed2c8a318c94ccc78b46237598a0ece9aa3646c3b34315738e5c20b7583f43fd0fdec946594cc6e96d92fdaa1b084b425aae
7
+ data.tar.gz: 4ba8042ea71939b58d51239e4d8b325d6a714f051ff273c2cbcc4116f707eac3b0ca2b695cc73de2904bdcb9cd2f2651927c4987e4967cee514e4d5acd1e9511
data/README.md CHANGED
@@ -565,7 +565,10 @@ to multiple spaces within the same ruby process! You just have to create and in
565
565
  The {WCC::Contentful::ModelAPI} concern makes this straightforward. Start by creating your Namespace
566
566
  and including the concern:
567
567
  ```ruby
568
- # app/models/my_second_space.rb
568
+ # lib/my_second_space.rb
569
+
570
+ # Note: This class must be in the "lib" folder in :zeitwerk mode, otherwise Rails 6+ will unload all your constants
571
+ # that were created in the initializer. Your models which subclass this namespace may reside in the app/models directory.
569
572
  class MySecondSpace
570
573
  include WCC::Contentful::ModelAPI
571
574
  end
@@ -587,6 +590,11 @@ MySecondSpace.configure do |config|
587
590
  config.space = ENV['SECOND_CONTENTFUL_SPACE_ID']
588
591
  config.environment = ENV['CONTENTFUL_ENVIRONMENT']
589
592
  end
593
+
594
+ # Ensure that models are reloaded in Rails development mode
595
+ Rails.application.config.to_prepare do
596
+ MySecondSpace.reload!
597
+ end
590
598
  ```
591
599
 
592
600
  Finally, use it:
@@ -614,6 +622,22 @@ sync_engine = MySecondSpace.services.sync_engine
614
622
  Note that the above services are not accessible on {WCC::Contentful::Services.instance}
615
623
  or via the {WCC::Contentful::ServiceAccessors}.
616
624
 
625
+ #### Important Note when using Zeitwerk with Rails 6+
626
+ When using Rails >= 6 with `config.autoloader = :zeitwerk`, Rails will remove any models defined in `app/models` after
627
+ initialization and then load them again when they are referenced. If you `include WCC::Contentful::ModelAPI` in a class
628
+ defined inside the `app` directory, this will have the effect of deleting all configuration that was set in the initializer
629
+ as well as the constants generated from your schema.
630
+ This will result in one of two errors:
631
+
632
+ * `NameError (uninitialized constant MySecondSpace::MyContentType)`
633
+ if you try to reference a subclass such as `MyContentType < MySecondSpace::MyContentType`
634
+ * `ArgumentError (Not yet configured!)`
635
+ if you try to `MySecondSpace.find('xxxx')` to load an Entry or Asset
636
+
637
+ The solution is to have your secondary namespace in a folder which is not in the `autoload_paths`.
638
+ We suggest using `lib`, which will work so long as you have not added the `lib` folder to the `autoload_paths` as some
639
+ uninformed StackOverflow answers suggest you do.
640
+
617
641
  ### Using a sync store with a second space
618
642
 
619
643
  If you use something other than the CDNAdapter with your second space, you will
@@ -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'
@@ -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
@@ -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
@@ -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}"
@@ -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
@@ -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
@@ -109,9 +109,7 @@ module WCC::Contentful
109
109
  ].freeze
110
110
 
111
111
  def type=(raw_type)
112
- unless TYPES.include?(raw_type)
113
- raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}"
114
- end
112
+ raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}" unless TYPES.include?(raw_type)
115
113
 
116
114
  @type = raw_type
117
115
  end
@@ -131,9 +129,7 @@ module WCC::Contentful
131
129
 
132
130
  if raw_type = hash_or_id.delete('type')
133
131
  raw_type = raw_type.to_sym
134
- unless TYPES.include?(raw_type)
135
- raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}"
136
- end
132
+ raise ArgumentError, "Unknown type #{raw_type}, expected one of: #{TYPES}" unless TYPES.include?(raw_type)
137
133
 
138
134
  @type = raw_type
139
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
@@ -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
@@ -98,7 +96,7 @@ module WCC::Contentful::ModelAPI
98
96
  loop do
99
97
  begin
100
98
  # The app may have defined a model and we haven't loaded it yet
101
- const = parent.const_missing(const_name)
99
+ const = parent.const_get(const_name)
102
100
  return const if const && const < self
103
101
  rescue NameError => e
104
102
  raise e unless e.message =~ /uninitialized constant (.+::)*#{const_name}$/
@@ -158,7 +156,7 @@ module WCC::Contentful::ModelAPI
158
156
  const_name = klass.name
159
157
  begin
160
158
  # the const_name is fully qualified so search from root
161
- const = Object.const_missing(const_name)
159
+ const = Object.const_get(const_name)
162
160
  register_for_content_type(content_type, klass: const) if const
163
161
  rescue NameError => e
164
162
  msg = "Error when reloading constant #{const_name} - #{e}"
@@ -61,7 +61,7 @@ module WCC::Contentful
61
61
  ct = content_type_from_raw(raw)
62
62
  if ct != typedef.content_type
63
63
  raise ArgumentError, 'Wrong Content Type - ' \
64
- "'#{raw.dig('sys', 'id')}' is a #{ct}, expected #{typedef.content_type}"
64
+ "'#{raw.dig('sys', 'id')}' is a #{ct}, expected #{typedef.content_type}"
65
65
  end
66
66
  @raw = raw.freeze
67
67
 
@@ -107,12 +107,13 @@ module WCC::Contentful
107
107
  # array fields need to resolve to an empty array when nothing is there
108
108
  raw_value = []
109
109
  end
110
- instance_variable_set('@' + f.name, raw_value)
110
+ instance_variable_set("@#{f.name}", raw_value)
111
111
  end
112
112
  end
113
113
 
114
114
  attr_reader :sys
115
115
  attr_reader :raw
116
+
116
117
  delegate :id, to: :sys
117
118
  delegate :created_at, to: :sys
118
119
  delegate :updated_at, to: :sys
@@ -122,11 +123,11 @@ module WCC::Contentful
122
123
  # Make a field for each column:
123
124
  typedef.fields.each_value do |f|
124
125
  name = f.name
125
- var_name = '@' + name
126
+ var_name = "@#{name}"
126
127
  case f.type
127
128
  when :Asset, :Link
128
129
  define_method(name) do
129
- val = instance_variable_get(var_name + '_resolved')
130
+ val = instance_variable_get("#{var_name}_resolved")
130
131
  return val if val.present?
131
132
 
132
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
@@ -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)
@@ -37,9 +37,7 @@ module WCC::Contentful::RichText
37
37
  end
38
38
 
39
39
  def tokenize(raw, context = nil)
40
- unless raw['nodeType'] == node_type
41
- raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'"
42
- end
40
+ raise ArgumentError, "Expected '#{node_type}', got '#{raw['nodeType']}'" unless raw['nodeType'] == node_type
43
41
 
44
42
  values =
45
43
  members.map do |symbol|
@@ -43,7 +43,7 @@ module WCC::Contentful::RichText
43
43
  EmbeddedEntryBlock
44
44
  when 'embedded-asset-block'
45
45
  EmbeddedAssetBlock
46
- when /heading\-(\d+)/
46
+ when /heading-(\d+)/
47
47
  size = Regexp.last_match(1)
48
48
  const_get("Heading#{size}")
49
49
  else
@@ -13,9 +13,7 @@ module WCC::Contentful::RSpec
13
13
  # stubs the Model API to return that content type for `.find` and `.find_by`
14
14
  # query methods.
15
15
  def contentful_stub(const, **attrs)
16
- unless const.respond_to?(:content_type_definition)
17
- const = WCC::Contentful::Model.resolve_constant(const.to_s)
18
- end
16
+ const = WCC::Contentful::Model.resolve_constant(const.to_s) unless const.respond_to?(:content_type_definition)
19
17
  instance = contentful_create(const, **attrs)
20
18
 
21
19
  # mimic what's going on inside model_singleton_methods.rb
@@ -132,6 +132,15 @@ module WCC::Contentful
132
132
  # Allow it to be injected into a store
133
133
  alias_method :_instrumentation, :instrumentation
134
134
 
135
+ # Gets the configured logger, defaulting to Rails.logger in a rails context,
136
+ # or logging to STDERR in a non-rails context.
137
+ def logger
138
+ @logger ||=
139
+ configuration.logger ||
140
+ (Rails.logger if defined?(Rails)) ||
141
+ Logger.new($stderr)
142
+ end
143
+
135
144
  ##
136
145
  # This method enables simple dependency injection -
137
146
  # If the target has a setter matching the name of one of the services,