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