contentful_lite 1.0.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 02c47f18feb382f97913063f3447bd7c9e00f40d7dc684a571f2c4cf9f400a80
4
+ data.tar.gz: 82a09e5c38cc7acbe580e28f524f6697d383ac117dae9cc67a0b5baf4e586618
5
+ SHA512:
6
+ metadata.gz: 57ac0c6defaba6ef173434ecbc496179496038559eddd75332ab5900981ba923447aa79fb23fd69a8f873a026793a042a730da839ac4111c67197fcbdb33fa04
7
+ data.tar.gz: 8d2f8a50516e8bedb28e08c16e290c36c3111c67fc74b4ef45d4c30cea45469a0dd4a8f5385fd0b2e5ab4dc90d01f64270e72b12b35b693b69b14c020ea9d10d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 JUUL Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # contentful_lite
2
+
3
+ ![Rspec](https://github.com/JuulLabs-OSS/contentful_lite/workflows/Rspec/badge.svg?branch=master&event=push)
4
+
5
+ > Ruby SDK gem for retrieving content from Contentful Content Delivery API and Content Preview API. A lighter replacement for the official contentful gem.
6
+
7
+ ### But why?
8
+ We've been using the official contentful gem for a while and we discovered it was causing a big memory leak. Among other issues, the main problem was that on the official gem the serialization of entries for cache stores a lot of extra information that we didn't require, causing to spend more cache memory unnecessarily. So we decided to build a custom lite gem providing support only for the features we were using.
9
+
10
+ ## Features
11
+ - Retrieve entries and assets from CDA and CPA
12
+ - Localization support
13
+ - Modeling of Content Types into Ruby classes
14
+ - Cache serialization
15
+ - Content validations using ActiveModel::Validations
16
+ - Retrieve data from any environment
17
+ - Pagination data
18
+
19
+ ## Getting Started
20
+
21
+ #### Installation
22
+ Add to your Gemfile:
23
+
24
+ `gem 'contentful_lite', git: "https://github.com/JuulLabs-OSS/contentful_lite.git"`
25
+
26
+ #### Use it!
27
+ ```ruby
28
+ require 'contentful_lite'
29
+
30
+ # First create the client by providing your space id and access token.
31
+ # You can also add environment: 'any_env' or preview: true if you prefer to use CPA
32
+ client = ContentfulLite::Client.new(space_id: 'cfexampleapi', access_token: 'b4c0n73n7fu1')
33
+ # Then request the entries
34
+ client.entries
35
+ ```
36
+
37
+ ## Creating your model classes with macros
38
+
39
+ This gem was created for being able to subclass the `ContentfulLite::Entry` and provide custom methods for each of the content models that you create on your contentful space.
40
+
41
+ This customization is really easy to implement. An example of a complete model may look like this:
42
+
43
+ ```ruby
44
+ class ContentPage < ContentfulLite::Entry
45
+ content_type_id 'content_page'
46
+
47
+ field_reader :title, :open_graph_image
48
+ field_reader :layout, default: 'simple'
49
+ field_reader :sections, default: []
50
+
51
+ validates :title, presence: true
52
+ validates_included_entry :sections, array: true
53
+ validates_included_asset :open_graph_image, type: 'image'
54
+ end
55
+ ```
56
+
57
+ ### Simple step by step explanation
58
+
59
+ 1. Create a class and inherit from `ContentfulLite::Entry`
60
+ 2. Call `content_type_id` to set the mapping to the right contentful content model.
61
+ 3. Call `field_reader` macro to setup your field accessors.
62
+ 4. Define your validations
63
+
64
+ ### Reference for field accessors macros
65
+
66
+ - #### field_reader
67
+
68
+ This macro define accessors methods for one or many fields.
69
+
70
+ **Parameters:**
71
+
72
+ - `*attrs` A symbol or array of symbols with the fields name you want to define.
73
+ - `default: nil` The default value in case there is no value for that field.
74
+ - `localizable: false` A boolean to indicate if this field is marked as localizable on contentful. If true, calling `#field_name(locale: custom)` will try to retrieve the value for that locale. If false, the same call will ignore the provided locale and instead use the main locale for the entry.
75
+
76
+ ### Reference for validations macros
77
+
78
+ Validations are implemented using ActiveModel::Validations so all the active model validation methods can be used, but you can also call our custom validation macros for contentful entries.
79
+
80
+ - #### validates\_included\_entry
81
+
82
+ Adds a validation for attributes with referenced entries.
83
+
84
+ **Parameters:**
85
+
86
+ - `*attr_names` A symbol or array of symbols with the fields name you want to include in this validation.
87
+ - `allow_blank: false` Optional boolean to determine if the validation should pass when the field is blank.
88
+ - `array: false` Optional boolean to determine if the field should be a single entry or an array of entries.
89
+ - `allowed_models: nil` Optional array of ruby subclasses of `ContentfulLite::Entry` with the allowed models to be referenced by this field. If not provided, any class will be allowed.
90
+
91
+ - #### validates\_included\_asset
92
+
93
+ Adds a validation for attributes with referenced assets.
94
+
95
+ **Parameters:**
96
+
97
+ - `*attr_names` A symbol or array of symbols with the fields name you want to include in this validation.
98
+ - `allow_blank: false` Boolean to determine if the validation should pass when the field is blank.
99
+ - `array: false` Boolean to determine if the field should be a single asset or an array of assets.
100
+ - `type: nil` String for validate it's appearance inside the file's content type. If not provided, the content type check will be omitted.
101
+
102
+ ## Public Methods Reference
103
+
104
+ ### ContentfulLite::Client
105
+ - #### #initialize `client = ContentfulLite::Client.new`
106
+
107
+ Builds the client for being able to retrieve data.
108
+
109
+ **Parameters:**
110
+ - `space_id:` Mandatory, you need to include the space_id you want to retrieve content from
111
+ - `access_token:` Mandatory, provide an access token for that space. You can read more about access tokens on [Contentful Documentation for CDA Authentication](https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/authentication)
112
+ - `environment: nil` Optional. If you want to retrieve the content from an environment that is not the default on contentful, you can include the environment name here.
113
+ - `preview: false` Optional. True for using Content Preview API, otherwise it will use Content Delivery API.
114
+
115
+
116
+ - #### #entries `client.entries(query = {})`
117
+
118
+ Searches for all the entries that fulfills the query conditions and returns an array (`ContentfulLite::EntriesArray`) with all the retrieved entries, using the right subclass of `ContentfulLite::Entry` (according to the entry mapping) for each of the entries.
119
+
120
+ **Parameters:**
121
+ - `query: {}` Optional, a hash representing any parameter you want to include in your CDA/CPA query. Read about search parameters on the [Contentful Documentation for Search Parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters)
122
+
123
+
124
+ - #### #entry `client.entry(id, query = {})`
125
+ Retrieves a single entry by id and returns an object of the right subclass of `ContentfulLite::Entry` according to the entry mapping.
126
+
127
+ **Parameters:**
128
+ - `id` Mandatory, the id for the entry you want to retrieve.
129
+ - `query = {}` Optional, a hash representing any parameter you want to include in your CDA/CPA query. Read about search parameters on the [Contentful Documentation for Search Parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters). Note that this API call doesn't allow the `include` parameter.
130
+
131
+
132
+ - #### #assets `client.assets(query = {})`
133
+
134
+ Searches for all the assets that fulfills the query conditions and returns an array (`ContentfulLite::AssetsArray`) with all the requested assets
135
+
136
+ **Parameters:**
137
+ - `query: {}` Optional, a hash representing any parameter you want to include in your CDA/CPA query. Read about search parameters on the [Contentful Documentation for Search Parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters)
138
+
139
+ - #### #asset `client.asset(id, query = {})`
140
+
141
+ Retrieves a single asset by id and returns a `ContentfulLite::Asset`
142
+
143
+ **Parameters:**
144
+ - `id` Mandatory, the id for the asset you want to retrieve.
145
+ - `query = {}` Optional, a hash representing any parameter you want to include in your CDA/CPA query. Read about search parameters on the [Contentful Documentation for Search Parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters).
146
+
147
+
148
+ - #### #build\_resource `client.build_resource(raw)`
149
+
150
+ Used for webhooks, it receives a raw body from the webhook callback and returns the right object according to the `sys.type`.
151
+
152
+ **Parameters:**
153
+ - `raw` Mandatory, the raw JSON received from the webhook callback.
154
+
155
+ ### ContentfulLite::Entry
156
+ - #### #contentful\_link `entry.contentful_link`
157
+
158
+ Returns the link to edit the entry on the contentful webapp.
159
+
160
+ - #### #fields `entry.fields(locale: nil)`
161
+
162
+ Provided a hash with all the fields on the specified locale
163
+
164
+ **Parameters:**
165
+ - `locale: nil` Optional, the locale code for the fields you want to retrieve. Defaults to first received locale
166
+
167
+ - #### #valid? `entry.valid?(locale: nil)`
168
+
169
+ Executes the validations for the specified locale.
170
+
171
+ **Parameters:**
172
+ - `locale: nil` Optional, the locale code you want to validate. Defaults to first received locale
173
+
174
+ Reference to #valid? [Active Model Documentation](https://api.rubyonrails.org/v5.2.3/classes/ActiveModel/Validations.html) for more information about this method.
175
+
176
+ - #### #errors `entry.errors(locale: nil)`
177
+
178
+ Provides a ActiveModel::Errors with all the errors for the specified locale. You need to call it after `#valid?`
179
+
180
+ **Parameters:**
181
+ - `locale: nil` Optional, the locale code for the errors you want to retrieve. Defaults to first received locale
182
+
183
+ Reference to #errors [Active Model Documentation](https://api.rubyonrails.org/v5.2.3/classes/ActiveModel/Validations.html) for more information about this method.
184
+
185
+
186
+ - #### #valid\_for\_all\_locales? `entry.valid_for_all_locales?`
187
+
188
+ Runs `#valid?` for each locale in the entry.
189
+
190
+ - #### #errors\_for\_all\_locales `entry.errors_for_all_locales`
191
+
192
+ Returns an array of `locale => ActiveModel::Errors`
193
+
194
+ - #### fields accessors `entry.field_name(locale: nil)`
195
+
196
+ Field accessors are defined using the `field_reader` macro. They receive and optional locale as parameter and returns the value for that field in the provided locale. Using the macro as `field_reader :content_field` will provide a `entry.content_field(locale: nil)` method.
197
+
198
+ **Parameters:**
199
+ - `locale: nil` Optional, the locale code for the field you want to retrieve. Defaults to first received locale
200
+
201
+ - #### sys accessors
202
+
203
+ ```
204
+ #id, #created_at, #updated_at, #locale,
205
+ #revision, #space_id, #environment_id,
206
+ #retrieved_at, #locales
207
+ ```
208
+ Provides access to the system data for the entry
209
+
210
+
211
+ ### ContentfulLite::Asset
212
+
213
+ - #### #contentful\_link `asset.contentful_link`
214
+
215
+ Returns the link to edit the asset on the contentful webapp.
216
+
217
+ - #### sys accessors
218
+
219
+ ```
220
+ #id, #created_at, #updated_at, #locale,
221
+ #revision, #space_id, #environment_id,
222
+ #retrieved_at, #locales
223
+ ```
224
+ Provides access to the system data for the entry
225
+
226
+ - #### asset accessors
227
+ ```
228
+ #title(locale: nil), #description(locale: nil),
229
+ #file_name(locale: nil), #content_type(locale: nil)
230
+ #url(locale: nil), file_details(locale: nil)
231
+ ```
232
+ Provides access to the asset data in the requested locale.
233
+
234
+ **Parameters:**
235
+ - `locale: nil` Optional, the locale code for the fields you want to retrieve. Defaults to first received locale
236
+
237
+ ### ContentfulLite::AssetsArray and ContentfulLite::EntriesArray
238
+
239
+ These classes inherits all methods from ruby Array class, but also implements the support for pagination data.
240
+
241
+ - #### #total `array.total`
242
+
243
+ Returns the total number of resources (entries or assets) for the request that created the array.
244
+
245
+ - #### #skip `array.skip`
246
+
247
+ Returns the number of skipped resources (entries or assets) for the request that created the array.
248
+
249
+ - #### #limit `array.limit`
250
+
251
+ Returns the maximum number of resources returned (entries or assets) for the request that created the array.
252
+
253
+ ## Development
254
+
255
+ ### Git Hooks
256
+
257
+ To Alias git hooks locally run `git config core.hooksPath git_hooks` inside the project.
@@ -0,0 +1,30 @@
1
+ require 'http'
2
+
3
+ module ContentfulLite
4
+ class Asset
5
+ include CommonData
6
+
7
+ # Gets the URL to view/edit the entry on Contentful webapp
8
+ # @return [String]
9
+ def contentful_link
10
+ "https://app.contentful.com/spaces/#{space_id}/assets/#{id}"
11
+ end
12
+
13
+ # @api private
14
+ # @!macro [attach] asset_attribute
15
+ # Returns the $1 attribute of the Contentful Asset
16
+ def self.asset_attribute(key, path, default: nil)
17
+ define_method(key) do |locale: nil|
18
+ path.inject(fields(locale: locale)) { |hash, path_section| hash.nil? ? nil : hash[path_section] } || default
19
+ end
20
+ end
21
+ private_class_method :asset_attribute
22
+
23
+ asset_attribute :title, ['title']
24
+ asset_attribute :description, ['description'], default: ''
25
+ asset_attribute :file_name, ['file', 'fileName']
26
+ asset_attribute :content_type, ['file', 'contentType']
27
+ asset_attribute :url, ['file', 'url']
28
+ asset_attribute :file_details, ['file', 'details']
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module ContentfulLite
2
+ class AssetsArray < BaseArray
3
+ # @param raw [Hash] raw response from Contentful API
4
+ # @api private
5
+ def initialize(raw)
6
+ super(raw)
7
+
8
+ # Create the array of asset objects
9
+ @items.collect! { |item| ContentfulLite::Asset.new(item) }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ module ContentfulLite
2
+ class BaseArray < Delegator
3
+ # The total number of items in the result
4
+ attr_reader :total
5
+ # The skip parameter sent to Contentful API for getting this result
6
+ attr_reader :skip
7
+ # The maximum number of resources returned per request
8
+ attr_reader :limit
9
+
10
+ # @param raw [Hash] raw response from Contentful API
11
+ # @api private
12
+ def initialize(raw)
13
+ @total = raw['total']
14
+ @skip = raw['skip']
15
+ @limit = raw['limit']
16
+ @items = raw.fetch('items', [])
17
+ end
18
+
19
+ # @api private
20
+ def __getobj__
21
+ @items
22
+ end
23
+
24
+ # @api private
25
+ def __setobj__(value)
26
+ @items = value
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,110 @@
1
+ require 'http'
2
+
3
+ module ContentfulLite
4
+ class Client
5
+ class RequestError < StandardError
6
+ attr_reader :response, :body
7
+
8
+ def initialize(response, body)
9
+ @response = response
10
+ @body = body
11
+ super(body['sys'] && body['sys']['type'] == 'Error' ? "#{body['sys']['id']}: #{body['message']}" : "Invalid Contentful Response: #{body}")
12
+ end
13
+ end
14
+ class NotFoundError < RequestError; end
15
+
16
+ attr_reader :space_id, :environment, :preview
17
+
18
+ # Creates the Contentful Client
19
+ # @param space_id [String] the Contentful Space Id you want to connect to.
20
+ # @param access_token [String] The secret access token to access the api
21
+ # @param environment [String, nil] To allow querying to a non-master environment
22
+ # @param preview [Boolean] True if you want to get draft entries
23
+ def initialize(space_id:, access_token:, environment: nil, preview: false)
24
+ @space_id = space_id
25
+ @environment = environment
26
+ @preview = preview
27
+ @access_token = access_token
28
+ end
29
+
30
+ # Gets an array of entries from Contentful API
31
+ # @param query [Hash] any query params accepted by Contentful API
32
+ # @return [ContentfulLite::EntriesArray]
33
+ def entries(query = {})
34
+ ContentfulLite::EntriesArray.new(request(:entries, query))
35
+ end
36
+
37
+ # Gets a single entry from Contentful API
38
+ # @param id [String] Unique id of the Contentful entry
39
+ # @param query [Hash] any query params accepted by Contentful API
40
+ # @return [ContentfulLite::Entry]
41
+ def entry(id, query = {})
42
+ parse_entry request("entries/#{id}", query)
43
+ end
44
+
45
+ # Gets a single asset from Contentful API
46
+ # @param id [String] Unique id of the Contentful asset
47
+ # @param query [Hash] any query params accepted by Contentful API
48
+ # @return [ContentfulLite::Asset]
49
+ def asset(id, query = {})
50
+ ContentfulLite::Asset.new(request("assets/#{id}", query))
51
+ end
52
+
53
+ # Gets an array of assets from Contentful API
54
+ # @param query [Hash] any query params accepted by Contentful API
55
+ # @return [ContentfulLite::AssetsArray]
56
+ def assets(query = {})
57
+ ContentfulLite::AssetsArray.new(request(:assets, query))
58
+ end
59
+
60
+ # Build an entry resource from a raw Contentful API response
61
+ # @param raw [Hash] a JSON parsed response from Contentful API
62
+ # @return [ContentfulLite::Entry,ContentfulLite::Asset,ContentfulLite::DeletedEntry]
63
+ def build_resource(raw)
64
+ case raw['sys']['type']
65
+ when 'Entry'
66
+ parse_entry(raw)
67
+ when 'Asset'
68
+ ContentfulLite::Asset.new(raw)
69
+ when 'DeletedEntry'
70
+ ContentfulLite::DeletedEntry.new(raw)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def parse_entry(hash)
77
+ klass = ContentfulLite::Entry.get_class(hash['sys']['contentType']['sys']['id'])
78
+ klass.new(hash)
79
+ end
80
+
81
+ def create_url(endpoint)
82
+ "https://#{preview ? 'preview' : 'cdn'}.contentful.com/spaces/#{space_id}/" +
83
+ ( environment.nil? ? '' : "environments/#{environment}/" ) +
84
+ endpoint.to_s
85
+ end
86
+
87
+ def request(endpoint, parameters)
88
+ parameters.transform_keys!(&:to_s)
89
+ parameters.transform_values! { |value| value.is_a?(::Array) ? value.join(',') : value }
90
+ response = HTTP[request_headers].get(create_url(endpoint), params: parameters)
91
+ body = response.to_s
92
+ body = Zlib::GzipReader.new(StringIO.new(body)).read if response.headers['Content-Encoding'].eql?('gzip')
93
+ JSON.parse(body).tap do |parsed|
94
+ raise error_class(response.status).new(response, parsed) if response.status != 200
95
+ end
96
+ end
97
+
98
+ def request_headers
99
+ {
100
+ 'Authorization' => "Bearer #{@access_token}",
101
+ 'Content-Type' => 'application/vnd.contentful.delivery.v1+json',
102
+ 'Accept-Encoding' => 'gzip'
103
+ }
104
+ end
105
+
106
+ def error_class(status)
107
+ status == 404 ? NotFoundError : RequestError
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,95 @@
1
+ require "active_support/core_ext/object/json"
2
+ require "active_support/core_ext/hash/keys"
3
+
4
+ module ContentfulLite
5
+ # Parses data common to all Contentful resources.
6
+ module CommonData
7
+ attr_reader :id, :created_at, :updated_at, :default_locale, :revision, :space_id, :environment_id, :retrieved_at, :locales, :localized_fields, :sys
8
+
9
+ # @param raw [Hash] raw response from Contentful API
10
+ # @api private
11
+ def initialize(raw)
12
+ @sys = raw['sys']
13
+ @id = sys['id']
14
+ @created_at = DateTime.parse sys['createdAt']
15
+ @updated_at = DateTime.parse sys['updatedAt']
16
+ @locale = sys['locale']
17
+ @revision = sys['revision']
18
+ @space_id = sys['space']['sys']['id']
19
+ @environment_id = sys['environment']['sys']['id']
20
+ @retrieved_at = DateTime.now
21
+
22
+ if locale
23
+ @locales = [locale]
24
+ @localized_fields = { locale => raw['fields'] }
25
+ else
26
+ @locales = raw.fetch('fields', {}).values.collect_concat(&:keys).uniq
27
+ @localized_fields = @locales.each_with_object({}) do |locale, hash|
28
+ hash[locale] = raw['fields'].transform_values { |value| value[locale] }
29
+ end
30
+ end
31
+
32
+ @default_locale = @locales.first
33
+ end
34
+
35
+ # Provides access to the locale being used to read fields
36
+ def locale
37
+ @locale || @default_locale
38
+ end
39
+
40
+ # Sets the locale that will be used to read fields
41
+ # @param value [String] the locale code
42
+ # @raise [StandardError] 'Invalid Locale' for locales not included on the API response
43
+ def locale=(value)
44
+ raise 'Invalid Locale' unless value.in?(locales)
45
+
46
+ @locale = value
47
+ end
48
+
49
+ # Returns a hash with field => value format using specified locale
50
+ # @param locale [String, nil] the locale that will be used for reading the fields. Defaults to {#locale}
51
+ # @return [Hash]
52
+ def fields(locale: nil)
53
+ @localized_fields.fetch(locale || self.locale, {})
54
+ end
55
+
56
+ # Executes a block with {#locale} set to the received locale. Then sets it back
57
+ # to current locale.
58
+ # @param locale [String] the locale to run the block with
59
+ # @yield
60
+ def with_locale(locale)
61
+ old_locale = @locale
62
+ @locale = locale unless locale.nil?
63
+ begin
64
+ yield
65
+ ensure
66
+ @locale = old_locale
67
+ end
68
+ end
69
+
70
+ # Gets a ContentfulLite::Link to the entry
71
+ # @return [ContentfulLite::Link] a link to this entry
72
+ def to_link
73
+ ContentfulLite::Link.new(self)
74
+ end
75
+
76
+ # Provided for compatibility with Rails JSON serializer
77
+ # @param serialized_ids [Array<String>] Ids already serialized, required for possible mutual references
78
+ # @param options [Hash] Serialization options, only provided for compatibility
79
+ # @return [Hash] a Hash representation of the link, to be formated as JSON
80
+ def as_json(serialized_ids: [], **options)
81
+ return to_link.as_json if serialized_ids.include?(id)
82
+
83
+ {
84
+ "sys" => sys,
85
+ "fields" => fields.transform_values do |value|
86
+ if value.respond_to?(:as_json)
87
+ value.as_json(serialized_ids: serialized_ids + [id], **options)
88
+ else
89
+ value
90
+ end
91
+ end
92
+ }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ module ContentfulLite
2
+ class DeletedEntry < Entry
3
+ # Not implemented, provided for compatibility reasons
4
+ # @raise NotImplementedError
5
+ # @api private
6
+ def contentful_link
7
+ raise NotImplementedError.new('contentful_link is not available for deleted entries')
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,62 @@
1
+ module ContentfulLite
2
+ class EntriesArray < BaseArray
3
+ # @param raw [Hash] raw response from Contentful API
4
+ # @api private
5
+ def initialize(raw)
6
+ super(raw)
7
+
8
+ # Collect arrays of missing (unresolvable) links
9
+ @errors = raw.fetch('errors', []).collect! { |error| error.fetch('details', {}) }.each_with_object({}) do |error_detail, hash|
10
+ type = error_detail['linkType'].downcase.to_sym
11
+ hash[type] ||= []
12
+ hash[type] << error_detail['id']
13
+ end
14
+
15
+ # Create the array of asset objects
16
+ @assets = hash_by_id(
17
+ raw.fetch('includes', {}).fetch('Asset', [])
18
+ ).transform_values! { |asset| ContentfulLite::Asset.new(asset) }
19
+
20
+ # Now parse the entries, this is the tricky part
21
+ @entries = {}
22
+ @raw_entries = hash_by_id(
23
+ raw.fetch('items', []) + raw.fetch('includes', {}).fetch('Entry', [])
24
+ )
25
+ @items.collect! { |item| build_entry(item['sys']['id']) }
26
+ end
27
+
28
+ private
29
+
30
+ def solve_link(value)
31
+ if value.is_a?(Array)
32
+ value.collect! { |link| solve_link(link) }.tap(&:compact!)
33
+ elsif value.is_a?(ContentfulLite::Link)
34
+ return nil if @errors.fetch(value.type, []).include?(value.id)
35
+
36
+ (value.type == :asset ? @assets[value.id] : build_entry(value.id)) || value
37
+ else
38
+ value
39
+ end
40
+ end
41
+
42
+ def hash_by_id(arr)
43
+ arr.each_with_object({}) do |element, hash|
44
+ hash[element['sys']['id']] = element
45
+ end
46
+ end
47
+
48
+ def build_entry(id)
49
+ @entries[id] || begin
50
+ hash = @raw_entries.delete(id)
51
+ return nil if hash.nil?
52
+
53
+ klass = ContentfulLite::Entry.get_class(hash['sys']['contentType']['sys']['id'])
54
+ @entries[id] = klass.new(hash)
55
+ @entries[id].localized_fields.values.each do |fields|
56
+ fields.transform_values! { |field| solve_link(field) }
57
+ end
58
+ @entries[id]
59
+ end
60
+ end
61
+ end
62
+ end