contentful 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e169a58eaeb582e61a250dc95e7bf980c632d906
4
- data.tar.gz: 615244247ac26f6f3be2afa7e430f12aa225687d
3
+ metadata.gz: 033f5efe6223ef9a1e09d693c298977d21235209
4
+ data.tar.gz: bbe60ca4295d8c60bebfeed1e9362d5f60e937dd
5
5
  SHA512:
6
- metadata.gz: f074a6327c040944e729763b8003899c452ff7b31fe2a52998ab2364d55cb426b3e00b10fa4eaeb925293656afb2f85c3c9385c343c22a5563cbbe97d004bf36
7
- data.tar.gz: 721607153d1085113cd632081be420a17f2c835521116f15f3cc450f4c710f88a580d44b05541c5dc2df777afadde5faa6ed63467dfe331306cb4ecc59802616
6
+ metadata.gz: 59e84824fe37fd0410211e2a8ad6eaa52316d2ecefa53c8d75c2b0e2f4aec93cbb1222e0ebcda1e3ed9bba0b6af1311fbc3cb6eddf2c44a0d9c27e75532c3041
7
+ data.tar.gz: 562af572939b7615e4de51483eac5718b820815ac757e0dd9821320f846a73be056eaae7eccc9807f869f9cc69dca052321db263e2d34a67b508eca879a9ee62
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.4.0
6
+ ### Added
7
+ * Added `reuse_entries` option to client configuration. This is a performance improvement, which is disabled by default due to backwards compatibility. All users are highly encouraged to enable it and test it in their applications. [#164](https://github.com/contentful/contentful.rb/pull/164)
8
+
5
9
  ## 2.3.0
6
10
  ### Added
7
11
  * Support for the new query parameters to find incoming links [to a specific entry](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/links-to-entry) or [to a specific asset](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/links-to-asset) in a space.
data/README.md CHANGED
@@ -242,12 +242,18 @@ Maximum time to wait for next available request (in seconds). Default value is 6
242
242
  can have up to 60 minutes of blocked requests. It is set to a default of 60 seconds in order to avoid blocking processes for too long, as rate limit retry behaviour
243
243
  is blocking per execution thread.
244
244
 
245
+ ### :reuse_entries
246
+
247
+ When true, reuse hydrated Entry and Asset objects within the same request when possible. Can result in a large speed increase and better handles cyclical object graphs.
248
+ This can be a good alternative to `max_include_resolution_depth` if your content model contains (or can contain) circular references.
249
+
245
250
  ### :max_include_resolution_depth
246
251
 
247
252
  Maximum amount of levels to resolve includes for SDK entities (this is independent of API-level includes - it represents the maximum depth the include resolution
248
253
  tree is allowed to resolved before falling back to `Link` objects). This include resolution strategy is in place in order to avoid having infinite circular recursion
249
254
  on resources with circular dependencies. Defaults to 20. _Note_: If you're using something like `Rails::cache` it's advisable to considerably lower this value
250
255
  (around 5 has proven to be a good compromise - but keep it higher or equal than your maximum API-level include parameter if you need the entire tree resolution).
256
+ Note that when `reuse_entries` is enabled, the max include resolution depth only affects deep chains of unique objects (ie, not simple circular references).
251
257
 
252
258
  ### :use_camel_case
253
259
 
@@ -5,7 +5,9 @@ module Contentful
5
5
  class BaseResource
6
6
  attr_reader :raw, :default_locale, :sys
7
7
 
8
- def initialize(item, configuration = {}, _localized = false, _includes = [], depth = 0)
8
+ # rubocop:disable Metrics/ParameterLists
9
+ def initialize(item, configuration = {}, _localized = false, _includes = [], entries = {}, depth = 0)
10
+ entries["#{item['sys']['type']}:#{item['sys']['id']}"] = self if entries && item.key?('sys')
9
11
  @raw = item
10
12
  @default_locale = configuration[:default_locale]
11
13
  @depth = depth
@@ -15,9 +15,9 @@ module Contentful
15
15
 
16
16
  private
17
17
 
18
- def coerce(field_id, value, includes)
19
- return build_nested_resource(value, includes) if Support.link?(value)
20
- return coerce_link_array(value, includes) if Support.link_array?(value)
18
+ def coerce(field_id, value, includes, entries = {})
19
+ return build_nested_resource(value, includes, entries) if Support.link?(value)
20
+ return coerce_link_array(value, includes, entries) if Support.link_array?(value)
21
21
 
22
22
  content_type_key = Support.snakify('contentType', @configuration[:use_camel_case])
23
23
  content_type = ContentTypeCache.cache_get(sys[:space].id, sys[content_type_key.to_sym].id)
@@ -27,13 +27,13 @@ module Contentful
27
27
  return content_type_field.coerce(value) unless content_type_field.nil?
28
28
  end
29
29
 
30
- super(field_id, value, includes)
30
+ super(field_id, value, includes, entries)
31
31
  end
32
32
 
33
- def coerce_link_array(value, includes)
33
+ def coerce_link_array(value, includes, entries)
34
34
  items = []
35
35
  value.each do |link|
36
- items << build_nested_resource(link, includes)
36
+ items << build_nested_resource(link, includes, entries)
37
37
  end
38
38
 
39
39
  items
@@ -43,23 +43,24 @@ module Contentful
43
43
  # in case one of the included items has a reference in an upper level,
44
44
  # so we can keep the include chain for that object as well
45
45
  # Any included object after the maximum include resolution depth will be just a Link
46
- def build_nested_resource(value, includes)
46
+ def build_nested_resource(value, includes, entries)
47
47
  if @depth < @configuration.fetch(:max_include_resolution_depth, 20)
48
48
  resource = Support.resource_for_link(value, includes)
49
- return resolve_include(resource, includes) unless resource.nil?
49
+ return resolve_include(resource, includes, entries) unless resource.nil?
50
50
  end
51
51
 
52
52
  build_link(value)
53
53
  end
54
54
 
55
- def resolve_include(resource, includes)
55
+ def resolve_include(resource, includes, entries)
56
56
  require_relative 'resource_builder'
57
57
 
58
58
  ResourceBuilder.new(
59
59
  resource,
60
60
  @configuration.merge(
61
61
  includes_for_single:
62
- @configuration.fetch(:includes_for_single, []) + includes
62
+ @configuration.fetch(:includes_for_single, []) + includes,
63
+ _entries_cache: entries
63
64
  ),
64
65
  localized,
65
66
  @depth + 1,
@@ -6,12 +6,12 @@ module Contentful
6
6
  class FieldsResource < BaseResource
7
7
  attr_reader :localized
8
8
 
9
- def initialize(item, _configuration, localized = false, includes = [], *)
9
+ # rubocop:disable Metrics/ParameterLists
10
+ def initialize(item, _configuration, localized = false, includes = [], entries = {}, *)
10
11
  super
11
12
 
12
13
  @localized = localized
13
- @fields = hydrate_fields(includes)
14
-
14
+ @fields = hydrate_fields(includes, entries)
15
15
  define_fields_methods!
16
16
  end
17
17
 
@@ -56,7 +56,7 @@ module Contentful
56
56
  def marshal_load(raw_object)
57
57
  super(raw_object)
58
58
  @localized = raw_object[:localized]
59
- @fields = hydrate_fields(raw_object[:configuration].fetch(:includes_for_single, []))
59
+ @fields = hydrate_fields(raw_object[:configuration].fetch(:includes_for_single, []), {})
60
60
  define_fields_methods!
61
61
  end
62
62
 
@@ -82,31 +82,18 @@ module Contentful
82
82
  end
83
83
  end
84
84
 
85
- def hydrate_fields(includes)
86
- return {} unless raw.key?('fields')
87
-
85
+ def hydrate_localized_fields(includes, entries)
88
86
  locale = internal_resource_locale
89
87
  result = { locale => {} }
90
-
91
- if localized
92
- raw['fields'].each do |name, locales|
93
- locales.each do |loc, value|
94
- result[loc] ||= {}
95
- name = Support.snakify(name, @configuration[:use_camel_case])
96
- result[loc][name.to_sym] = coerce(
97
- name,
98
- value,
99
- includes
100
- )
101
- end
102
- end
103
- else
104
- raw['fields'].each do |name, value|
88
+ raw['fields'].each do |name, locales|
89
+ locales.each do |loc, value|
90
+ result[loc] ||= {}
105
91
  name = Support.snakify(name, @configuration[:use_camel_case])
106
- result[locale][name.to_sym] = coerce(
92
+ result[loc][name.to_sym] = coerce(
107
93
  name,
108
94
  value,
109
- includes
95
+ includes,
96
+ entries
110
97
  )
111
98
  end
112
99
  end
@@ -114,9 +101,35 @@ module Contentful
114
101
  result
115
102
  end
116
103
 
104
+ def hydrate_nonlocalized_fields(includes, entries)
105
+ result = { locale => {} }
106
+ locale = internal_resource_locale
107
+ raw['fields'].each do |name, value|
108
+ name = Support.snakify(name, @configuration[:use_camel_case])
109
+ result[locale][name.to_sym] = coerce(
110
+ name,
111
+ value,
112
+ includes,
113
+ entries
114
+ )
115
+ end
116
+
117
+ result
118
+ end
119
+
120
+ def hydrate_fields(includes, entries)
121
+ return {} unless raw.key?('fields')
122
+
123
+ if localized
124
+ hydrate_localized_fields(includes, entries)
125
+ else
126
+ hydrate_nonlocalized_fields(includes, entries)
127
+ end
128
+ end
129
+
117
130
  protected
118
131
 
119
- def coerce(_field_id, value, _includes)
132
+ def coerce(_field_id, value, _includes, _entries)
120
133
  value
121
134
  end
122
135
  end
@@ -40,6 +40,7 @@ module Contentful
40
40
  @depth = depth
41
41
  @endpoint = endpoint
42
42
  @configuration = configuration
43
+ @entries = configuration[:_entries_cache] || {}
43
44
  end
44
45
 
45
46
  # Starts the parsing process.
@@ -74,7 +75,17 @@ module Contentful
74
75
  fail UnparsableResource, 'Item type is not known, could not parse' if item_type.nil?
75
76
  item_class = resource_class(item)
76
77
 
77
- item_class.new(item, @configuration, localized?, includes, depth)
78
+ reuse_entries = @configuration.fetch(:reuse_entries, false)
79
+ entries = @entries ? @entries : {}
80
+
81
+ id = "#{item['sys']['type']}:#{item['sys']['id']}"
82
+ entry = if reuse_entries && entries.key?(id)
83
+ entries[id]
84
+ else
85
+ item_class.new(item, @configuration, localized?, includes, entries, depth)
86
+ end
87
+
88
+ entry
78
89
  end
79
90
 
80
91
  def fetch_includes
@@ -1,5 +1,5 @@
1
1
  # Contentful Namespace
2
2
  module Contentful
3
3
  # Gem Version
4
- VERSION = '2.3.0'
4
+ VERSION = '2.4.0'
5
5
  end
data/spec/entry_spec.rb CHANGED
@@ -283,7 +283,73 @@ describe Contentful::Entry do
283
283
  end
284
284
  end
285
285
 
286
+ describe 'reuse objects' do
287
+ it 'should handle recursion as well as not reusing' do
288
+ vcr('entry/include_resolution') {
289
+ entry = create_client(reuse_objects: true).entry('nyancat', include: 2)
290
+
291
+ expect(entry.best_friend.name).to eq 'Happy Cat'
292
+ expect(entry
293
+ .best_friend.best_friend
294
+ .best_friend.best_friend
295
+ .best_friend.best_friend
296
+ .best_friend.best_friend
297
+ .best_friend.best_friend
298
+ .best_friend.best_friend
299
+ .best_friend.best_friend
300
+ .best_friend.best_friend
301
+ .best_friend.best_friend
302
+ .best_friend.best_friend.name).to eq 'Nyan Cat'
303
+ }
304
+ end
305
+ it 'should use the same object for the same entry' do
306
+ vcr('entry/include_resolution') {
307
+ entry = create_client(reuse_entries: true).entry('nyancat', include: 2)
308
+
309
+ expect(entry.best_friend.name).to eq 'Happy Cat'
310
+ expect(entry.best_friend.best_friend).to be(entry)
311
+ }
312
+ end
313
+ it 'works on nested structures with unique objects' do
314
+ vcr('entry/include_resolution_uniques') {
315
+ entry = create_client(
316
+ space: 'v7cxgyxt0w5x',
317
+ access_token: '96e5d256e9a5349ce30e84356597e409f8f1bb485cb4719285b555e0f78aa27e',
318
+ reuse_entries: true
319
+ ).entry('1nLXjjWvk4MEeWeQCWmymc', include: 10)
320
+
321
+ expect(entry.title).to eq '1'
322
+ expect(entry
323
+ .child.child
324
+ .child.child
325
+ .child.child
326
+ .child.child
327
+ .child.title).to eq '10'
328
+ expect(entry
329
+ .child.child
330
+ .child.child
331
+ .child.child
332
+ .child.child
333
+ .child.child.title).to eq '1'
334
+ expect(entry
335
+ .child.child.child.child
336
+ .child.child.child.child
337
+ .child.child.child.child
338
+ .child.child.child.child
339
+ .child.child.child.child.title).to eq '1'
340
+ }
341
+ end
342
+ end
343
+
286
344
  describe 'include resolution' do
345
+ it 'should not reuse objects by default' do
346
+ vcr('entry/include_resolution') {
347
+ entry = create_client.entry('nyancat', include: 2)
348
+
349
+ expect(entry.best_friend.name).to eq 'Happy Cat'
350
+ expect(entry.best_friend.best_friend).not_to be(entry)
351
+ }
352
+ end
287
353
  it 'defaults to 20 depth' do
288
354
  vcr('entry/include_resolution') {
289
355
  entry = create_client.entry('nyancat', include: 2)
@@ -329,6 +395,34 @@ describe Contentful::Entry do
329
395
  .best_friend.best_friend).to be_a ::Contentful::Link
330
396
  }
331
397
  end
398
+ it 'works on nested structures with unique objects' do
399
+ vcr('entry/include_resolution_uniques') {
400
+ entry = create_client(
401
+ space: 'v7cxgyxt0w5x',
402
+ access_token: '96e5d256e9a5349ce30e84356597e409f8f1bb485cb4719285b555e0f78aa27e',
403
+ ).entry('1nLXjjWvk4MEeWeQCWmymc', include: 10)
404
+
405
+ expect(entry.title).to eq '1'
406
+ expect(entry
407
+ .child.child
408
+ .child.child
409
+ .child.child
410
+ .child.child
411
+ .child.title).to eq '10'
412
+ expect(entry
413
+ .child.child
414
+ .child.child
415
+ .child.child
416
+ .child.child
417
+ .child.child.title).to eq '1'
418
+ expect(entry
419
+ .child.child.child.child
420
+ .child.child.child.child
421
+ .child.child.child.child
422
+ .child.child.child.child
423
+ .child.child.child.child.title).to eq '1'
424
+ }
425
+ end
332
426
  end
333
427
 
334
428
  describe 'issues' do
@@ -0,0 +1,81 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://cdn.contentful.com/spaces/v7cxgyxt0w5x/entries?include=10&sys.id=1nLXjjWvk4MEeWeQCWmymc
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ X-Contentful-User-Agent:
11
+ - sdk contentful.rb/2.3.0; platform ruby/2.4.1; os macOS/16;
12
+ Authorization:
13
+ - Bearer 96e5d256e9a5349ce30e84356597e409f8f1bb485cb4719285b555e0f78aa27e
14
+ Content-Type:
15
+ - application/vnd.contentful.delivery.v1+json
16
+ Accept-Encoding:
17
+ - gzip
18
+ Connection:
19
+ - close
20
+ Host:
21
+ - cdn.contentful.com
22
+ User-Agent:
23
+ - http.rb/2.2.2
24
+ response:
25
+ status:
26
+ code: 200
27
+ message: OK
28
+ headers:
29
+ Access-Control-Allow-Headers:
30
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature
31
+ Access-Control-Allow-Methods:
32
+ - GET,HEAD,OPTIONS
33
+ Access-Control-Allow-Origin:
34
+ - "*"
35
+ Access-Control-Expose-Headers:
36
+ - Etag
37
+ Access-Control-Max-Age:
38
+ - '86400'
39
+ Cache-Control:
40
+ - max-age=0
41
+ Content-Encoding:
42
+ - gzip
43
+ Content-Type:
44
+ - application/vnd.contentful.delivery.v1+json
45
+ Etag:
46
+ - W/"61dce3f065024b11b5dce76f1e0b6c4d"
47
+ Server:
48
+ - Contentful
49
+ X-Content-Type-Options:
50
+ - nosniff
51
+ X-Contentful-Request-Id:
52
+ - d4bd8c6410d7abd46c1d4669c38f6357
53
+ Content-Length:
54
+ - '878'
55
+ Accept-Ranges:
56
+ - bytes
57
+ Date:
58
+ - Wed, 14 Feb 2018 10:17:53 GMT
59
+ Via:
60
+ - 1.1 varnish
61
+ Age:
62
+ - '0'
63
+ Connection:
64
+ - close
65
+ X-Served-By:
66
+ - cache-hhn1548-HHN
67
+ X-Cache:
68
+ - MISS
69
+ X-Cache-Hits:
70
+ - '0'
71
+ X-Timer:
72
+ - S1518603473.158289,VS0,VE398
73
+ Vary:
74
+ - Accept-Encoding
75
+ body:
76
+ encoding: ASCII-8BIT
77
+ string: !binary |-
78
+ H4sIAAAAAAAAA+2ZS4+bMBCA7/srVpy7yLwhN5SlyWo3jSI2omy1B0po6hBICORBV/nvNQ8TkpqsqDggBQ4oNh6PZ+xvxnY+7u7vqTAOqd79B/qJClG8dlCJUjcbK6ZQ3fFL0iZaRdYS1TNpKXThGhVAWlhCD0bJJ5CVYeR4SYc/0g6zbi+0pJrCtWUnqnCLrLI0lqQircRjeoG+SyU6Tw/S7ruv+Zj1tMeLBnCWmLOT7MM8PkRgLxwSq/BzLH6ndmYPlckw/sv3xcLYufxIcwxn0je82LNL/RcD0/xoE5c/2BvHipyZmviFYgEjPwD2geFfGdBjhB6QaEaR3soC2/WsnsDG2cEQrnw8J/nI7ZUfOX6Ue6Q51/ZL/RIdHDlh9LljlyvbWqbry/EfpjoWKHxP/YLOcnZajolVVASjTIY5c/FvuEymtjkbL2cxVZ4tBS4+DIP9ePHmae5ozsoAgq2rVdibr6NsaSXv95QT6NvL7cwpsZYpxKScWCHQUsELsWXmtJwJAjNI6DNqCsOruEFxoUxReUlgelbKgJMfuT99bS+6q+0YPO9Z9YzOSn6Q+msE8T1BoRlBKBOERK4xVCFCpihRX8lREy6vpqlw/CVP1xxeQVUeu/PIQCALKSvY4s9nxibQ1YTlJMYKm324CL2nTTxUtbk3Z0dePx6JZcgunHCK3Rg5vA5PQeEsu+WuIGWeJowj5Z/CuP8niR0eBo7R/8oZtjsyNTZkbSDrjZCEchFDS4CtQVKFSEcSzlJcC0hiyMG3FLJvE6WKPH6WvvBu899QdTUpIS54WuSUeiiRRDqUMEpsC1CqiL43j5IgqlAJdGfwZNpxIKsGUCc8bCQr8T1epnlBroFShUiHEkZJbgFKQfAszR7h7pvKW9vpNLQCGXb7O0pQTOHnUDCkLeuG7ACo4/lorDVEksDRMsfUIoko0pGESRJbQJL+bMbuU8zMBxaiaDJyJroudyclwexvZ/owXE9FMQByoJnQXJkNkcQpNKi1veN7RJGOpOI+D7QApYpb3pvf35FDTBMnJXQXx9BArLe9I4p0KGGUpBaQVHEiuHmSyNeaDZEk0rwk1tvekUQ6kjBJQhtIIp8Ibp4k8gGyGZJ4npZBvb+UiCIdSZgkpQ0kkU8EdUhK276j9/HuePcXnaKW8jEhAAA=
79
+ http_version:
80
+ recorded_at: Wed, 14 Feb 2018 10:17:52 GMT
81
+ recorded_with: VCR 3.0.3
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contentful
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Contentful GmbH (Jan Lelis)
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-02-06 00:00:00.000000000 Z
13
+ date: 2018-02-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: http
@@ -455,6 +455,7 @@ files:
455
455
  - spec/fixtures/vcr_cassettes/entry.yml
456
456
  - spec/fixtures/vcr_cassettes/entry/custom_resource.yml
457
457
  - spec/fixtures/vcr_cassettes/entry/include_resolution.yml
458
+ - spec/fixtures/vcr_cassettes/entry/include_resolution_uniques.yml
458
459
  - spec/fixtures/vcr_cassettes/entry/json_objects.yml
459
460
  - spec/fixtures/vcr_cassettes/entry/json_objects_client.yml
460
461
  - spec/fixtures/vcr_cassettes/entry/marshal_138.yml
@@ -591,6 +592,7 @@ test_files:
591
592
  - spec/fixtures/vcr_cassettes/entry.yml
592
593
  - spec/fixtures/vcr_cassettes/entry/custom_resource.yml
593
594
  - spec/fixtures/vcr_cassettes/entry/include_resolution.yml
595
+ - spec/fixtures/vcr_cassettes/entry/include_resolution_uniques.yml
594
596
  - spec/fixtures/vcr_cassettes/entry/json_objects.yml
595
597
  - spec/fixtures/vcr_cassettes/entry/json_objects_client.yml
596
598
  - spec/fixtures/vcr_cassettes/entry/marshal_138.yml