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 +7 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/lib/contentful_lite/asset.rb +30 -0
- data/lib/contentful_lite/assets_array.rb +12 -0
- data/lib/contentful_lite/base_array.rb +29 -0
- data/lib/contentful_lite/client.rb +110 -0
- data/lib/contentful_lite/common_data.rb +95 -0
- data/lib/contentful_lite/deleted_entry.rb +10 -0
- data/lib/contentful_lite/entries_array.rb +62 -0
- data/lib/contentful_lite/entry.rb +52 -0
- data/lib/contentful_lite/entry_mapping.rb +23 -0
- data/lib/contentful_lite/link.rb +40 -0
- data/lib/contentful_lite/validations/entry.rb +68 -0
- data/lib/contentful_lite/validations/included_asset_validator.rb +14 -0
- data/lib/contentful_lite/validations/included_child_validator.rb +40 -0
- data/lib/contentful_lite/validations/included_entry_validator.rb +24 -0
- data/lib/contentful_lite/version.rb +3 -0
- data/lib/contentful_lite.rb +12 -0
- data/spec/asset_spec.rb +61 -0
- data/spec/assets_array_spec.rb +32 -0
- data/spec/base_array_spec.rb +25 -0
- data/spec/client_spec.rb +129 -0
- data/spec/common_data_spec.rb +111 -0
- data/spec/deleted_entry_spec.rb +30 -0
- data/spec/entries_array_spec.rb +102 -0
- data/spec/entry_mapping_spec.rb +20 -0
- data/spec/entry_spec.rb +84 -0
- data/spec/link_spec.rb +66 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/validations_spec.rb +366 -0
- metadata +186 -0
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
|
+

|
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
|