contentful_lite 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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