contentful 0.8.0 → 0.9.0
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 +4 -4
- data/.gitignore +3 -1
- data/.rubocop.yml +25 -0
- data/.rubocop_todo.yml +35 -0
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +12 -0
- data/Guardfile +18 -36
- data/README.md +34 -0
- data/Rakefile +5 -0
- data/contentful.gemspec +3 -0
- data/lib/contentful.rb +1 -0
- data/lib/contentful/array.rb +5 -2
- data/lib/contentful/asset.rb +15 -12
- data/lib/contentful/client.rb +90 -48
- data/lib/contentful/constants.rb +499 -497
- data/lib/contentful/dynamic_entry.rb +3 -0
- data/lib/contentful/entry.rb +40 -1
- data/lib/contentful/request.rb +3 -6
- data/lib/contentful/resource.rb +92 -69
- data/lib/contentful/resource/array_like.rb +10 -2
- data/lib/contentful/resource/asset_fields.rb +7 -0
- data/lib/contentful/resource/custom_resource.rb +29 -0
- data/lib/contentful/resource/fields.rb +20 -12
- data/lib/contentful/resource/system_properties.rb +7 -0
- data/lib/contentful/resource_builder.rb +24 -17
- data/lib/contentful/support.rb +5 -1
- data/lib/contentful/sync.rb +26 -15
- data/lib/contentful/sync_page.rb +12 -0
- data/lib/contentful/version.rb +3 -1
- data/spec/entry_spec.rb +116 -0
- data/spec/fixtures/vcr_cassettes/entry/custom_resource.yml +168 -0
- data/spec/fixtures/vcr_cassettes/entry/marshall.yml +317 -0
- data/spec/fixtures/vcr_cassettes/entry/raw.yml +237 -0
- metadata +113 -3
@@ -3,7 +3,9 @@ require_relative 'resource/fields'
|
|
3
3
|
require_relative 'location'
|
4
4
|
|
5
5
|
module Contentful
|
6
|
+
# Wrapper for Entries with Cached Content Types
|
6
7
|
class DynamicEntry < Entry
|
8
|
+
# Coercions from Contentful Types to Ruby native types
|
7
9
|
KNOWN_TYPES = {
|
8
10
|
'String' => :string,
|
9
11
|
'Text' => :string,
|
@@ -15,6 +17,7 @@ module Contentful
|
|
15
17
|
'Location' => Location
|
16
18
|
}
|
17
19
|
|
20
|
+
# @private
|
18
21
|
def self.create(content_type)
|
19
22
|
unless content_type.is_a? ContentType
|
20
23
|
content_type = ContentType.new(content_type)
|
data/lib/contentful/entry.rb
CHANGED
@@ -3,10 +3,49 @@ require_relative 'resource/fields'
|
|
3
3
|
|
4
4
|
module Contentful
|
5
5
|
# Resource class for Entry.
|
6
|
-
# https://www.contentful.com/developers/documentation/content-delivery-api/#entries
|
6
|
+
# @see _ https://www.contentful.com/developers/documentation/content-delivery-api/#entries
|
7
7
|
class Entry
|
8
8
|
include Contentful::Resource
|
9
9
|
include Contentful::Resource::SystemProperties
|
10
10
|
include Contentful::Resource::Fields
|
11
|
+
|
12
|
+
# @private
|
13
|
+
def marshal_dump
|
14
|
+
raw_with_links
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
def marshal_load(raw_object)
|
19
|
+
@properties = extract_from_object(raw_object, :property, self.class.property_coercions.keys)
|
20
|
+
@sys = raw_object.key?('sys') ? extract_from_object(raw_object['sys'], :sys) : {}
|
21
|
+
extract_fields_from_object!(raw_object)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @private
|
25
|
+
def raw_with_links
|
26
|
+
links = properties.keys.select { |property| known_link?(property) }
|
27
|
+
processed_raw = Marshal.load(Marshal.dump(raw)) # Deep Copy
|
28
|
+
raw['fields'].each do |k, v|
|
29
|
+
processed_raw['fields'][k] = links.include?(k.to_sym) ? send(snakify(k)) : v
|
30
|
+
end
|
31
|
+
|
32
|
+
processed_raw
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def known_link?(name)
|
38
|
+
field_name = name.to_sym
|
39
|
+
return true if known_contentful_object?(fields[field_name])
|
40
|
+
fields[field_name].is_a?(Enumerable) && known_contentful_object?(fields[field_name].first)
|
41
|
+
end
|
42
|
+
|
43
|
+
def known_contentful_object?(object)
|
44
|
+
(object.is_a?(Contentful::Entry) || object.is_a?(Contentful::Asset))
|
45
|
+
end
|
46
|
+
|
47
|
+
def snakify(name)
|
48
|
+
Contentful::Support.snakify(name).to_sym
|
49
|
+
end
|
11
50
|
end
|
12
51
|
end
|
data/lib/contentful/request.rb
CHANGED
@@ -8,11 +8,8 @@ module Contentful
|
|
8
8
|
def initialize(client, endpoint, query = {}, id = nil)
|
9
9
|
@client = client
|
10
10
|
@endpoint = endpoint
|
11
|
-
@absolute = true if @endpoint.start_with?('http')
|
12
11
|
|
13
|
-
@query = if query && !query.empty?
|
14
|
-
normalize_query(query)
|
15
|
-
end
|
12
|
+
@query = (normalize_query(query) if query && !query.empty?)
|
16
13
|
|
17
14
|
if id
|
18
15
|
@type = :single
|
@@ -25,7 +22,7 @@ module Contentful
|
|
25
22
|
|
26
23
|
# Returns the final URL, relative to a contentful space
|
27
24
|
def url
|
28
|
-
"#{@endpoint}#{
|
25
|
+
"#{@endpoint}#{@type == :single ? "/#{id}" : ''}"
|
29
26
|
end
|
30
27
|
|
31
28
|
# Delegates the actual HTTP work to the client
|
@@ -35,7 +32,7 @@ module Contentful
|
|
35
32
|
|
36
33
|
# Returns true if endpoint is an absolute url
|
37
34
|
def absolute?
|
38
|
-
|
35
|
+
@endpoint.start_with?('http')
|
39
36
|
end
|
40
37
|
|
41
38
|
# Returns a new Request object with the same data
|
data/lib/contentful/resource.rb
CHANGED
@@ -9,9 +9,14 @@ module Contentful
|
|
9
9
|
# You can define your own classes that behave like contentful resources:
|
10
10
|
# See examples/custom_classes.rb to see how.
|
11
11
|
#
|
12
|
-
# Take a look at examples/resource_mapping.rb on how to register them
|
13
|
-
#
|
12
|
+
# Take a look at examples/resource_mapping.rb on how to register them to be returned
|
13
|
+
# by the client by default
|
14
|
+
#
|
15
|
+
# @see _ examples/custom_classes.rb Custom Class as Resource
|
16
|
+
# @see _ examples/resource_mapping.rb Mapping a Custom Class
|
14
17
|
module Resource
|
18
|
+
# @private
|
19
|
+
# rubocop:disable Style/DoubleNegation
|
15
20
|
COERCIONS = {
|
16
21
|
string: ->(v) { v.to_s },
|
17
22
|
integer: ->(v) { v.to_i },
|
@@ -19,20 +24,33 @@ module Contentful
|
|
19
24
|
boolean: ->(v) { !!v },
|
20
25
|
date: ->(v) { DateTime.parse(v) }
|
21
26
|
}
|
27
|
+
# rubocop:enable Style/DoubleNegation
|
22
28
|
|
23
|
-
attr_reader :properties, :request, :client, :default_locale
|
29
|
+
attr_reader :properties, :request, :client, :default_locale, :raw
|
24
30
|
|
25
|
-
|
31
|
+
# @private
|
32
|
+
def initialize(object = nil,
|
33
|
+
request = nil,
|
34
|
+
client = nil,
|
35
|
+
default_locale = Contentful::Client::DEFAULT_CONFIGURATION[:default_locale])
|
26
36
|
self.class.update_coercions!
|
27
37
|
@default_locale = default_locale
|
28
38
|
|
29
|
-
@properties =
|
30
|
-
|
39
|
+
@properties = {}
|
40
|
+
self.class.property_coercions.keys.each do |property_name|
|
41
|
+
@properties[property_name] = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
@properties = @properties.merge(
|
45
|
+
extract_from_object(object, :property,
|
46
|
+
self.class.property_coercions.keys)
|
47
|
+
)
|
31
48
|
@request = request
|
32
49
|
@client = client
|
33
|
-
@
|
50
|
+
@raw = object
|
34
51
|
end
|
35
52
|
|
53
|
+
# @private
|
36
54
|
def inspect(info = nil)
|
37
55
|
properties_info = properties.empty? ? '' : " @properties=#{properties.inspect}"
|
38
56
|
"#<#{self.class}:#{properties_info}#{info}>"
|
@@ -43,6 +61,9 @@ module Contentful
|
|
43
61
|
false
|
44
62
|
end
|
45
63
|
|
64
|
+
# Returns true if resource is localized
|
65
|
+
#
|
66
|
+
# @return [Boolean]
|
46
67
|
def localized?(value)
|
47
68
|
return false unless value.is_a? ::Hash
|
48
69
|
value.keys.any? { |possible_locale| Contentful::Constants::KNOWN_LOCALES.include?(possible_locale) }
|
@@ -68,6 +89,57 @@ module Contentful
|
|
68
89
|
end
|
69
90
|
end
|
70
91
|
|
92
|
+
# Register the resources properties on class level by using the #property method
|
93
|
+
# @private
|
94
|
+
module ClassMethods
|
95
|
+
# By default, fields come flattened in the current locale. This is different for sync
|
96
|
+
def property_coercions
|
97
|
+
@property_coercions ||= {}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Defines which properties of a resource your class expects
|
101
|
+
# Define them in :camelCase, they will be available as #snake_cased methods
|
102
|
+
#
|
103
|
+
# You can pass in a second "type" argument:
|
104
|
+
# - If it is a class, it will be initialized for the property
|
105
|
+
# - Symbols are looked up in the COERCION constant for a lambda that
|
106
|
+
# defines a type conversion to apply
|
107
|
+
#
|
108
|
+
# Note: This second argument is not meant for contentful sub-resources,
|
109
|
+
# but for structured objects (like locales in a space)
|
110
|
+
# Sub-resources are handled by the resource builder
|
111
|
+
def property(name, property_class = nil)
|
112
|
+
property_coercions[name.to_sym] = property_class
|
113
|
+
define_method Contentful::Support.snakify(name) do
|
114
|
+
properties[name.to_sym]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Ensure inherited classes pick up coercions
|
119
|
+
def update_coercions!
|
120
|
+
return if @coercions_updated
|
121
|
+
|
122
|
+
if superclass.respond_to? :property_coercions
|
123
|
+
@property_coercions = superclass.property_coercions.dup.merge(@property_coercions || {})
|
124
|
+
end
|
125
|
+
|
126
|
+
if superclass.respond_to? :sys_coercions
|
127
|
+
@sys_coercions = superclass.sys_coercions.dup.merge(@sys_coercions || {})
|
128
|
+
end
|
129
|
+
|
130
|
+
if superclass.respond_to? :fields_coercions
|
131
|
+
@fields_coercions = superclass.fields_coercions.dup.merge(@fields_coercions || {})
|
132
|
+
end
|
133
|
+
|
134
|
+
@coercions_updated = true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @private
|
139
|
+
def self.included(base)
|
140
|
+
base.extend(ClassMethods)
|
141
|
+
end
|
142
|
+
|
71
143
|
private
|
72
144
|
|
73
145
|
def initialize_fields_for_localized_resource(object)
|
@@ -107,11 +179,11 @@ module Contentful
|
|
107
179
|
elsif value.is_a? ::Array
|
108
180
|
value.map { |v| coerce_or_create_class(v, what) }
|
109
181
|
elsif should_coerce_hash?(value)
|
110
|
-
::Hash[value.map
|
182
|
+
::Hash[value.map do |k, v|
|
111
183
|
to_coerce = v.is_a?(Hash) ? v : v.to_s
|
112
184
|
coercion = v.is_a?(Numeric) ? v : coerce_or_create_class(to_coerce, what)
|
113
185
|
[k.to_sym, coercion]
|
114
|
-
|
186
|
+
end]
|
115
187
|
else
|
116
188
|
coerce_or_create_class(value, what)
|
117
189
|
end
|
@@ -119,23 +191,23 @@ module Contentful
|
|
119
191
|
|
120
192
|
def should_coerce_hash?(value)
|
121
193
|
value.is_a?(::Hash) &&
|
122
|
-
!
|
123
|
-
!
|
124
|
-
!
|
125
|
-
!
|
126
|
-
!
|
194
|
+
!is_a?(Asset) &&
|
195
|
+
!is_a?(Field) &&
|
196
|
+
!location?(value) &&
|
197
|
+
!link?(value) &&
|
198
|
+
!image?(value)
|
127
199
|
end
|
128
200
|
|
129
|
-
def
|
130
|
-
value.
|
201
|
+
def location?(value)
|
202
|
+
value.key?('lat') || value.key?('lon')
|
131
203
|
end
|
132
204
|
|
133
|
-
def
|
134
|
-
value.
|
205
|
+
def link?(value)
|
206
|
+
value.key?('sys') && value['sys']['type'] == 'Link'
|
135
207
|
end
|
136
208
|
|
137
|
-
def
|
138
|
-
value.
|
209
|
+
def image?(value)
|
210
|
+
value.key?('image')
|
139
211
|
end
|
140
212
|
|
141
213
|
def coerce_or_create_class(value, what)
|
@@ -148,54 +220,5 @@ module Contentful
|
|
148
220
|
value
|
149
221
|
end
|
150
222
|
end
|
151
|
-
|
152
|
-
# Register the resources properties on class level by using the #property method
|
153
|
-
module ClassMethods
|
154
|
-
# By default, fields come flattened in the current locale. This is different for sync
|
155
|
-
def property_coercions
|
156
|
-
@property_coercions ||= {}
|
157
|
-
end
|
158
|
-
|
159
|
-
# Defines which properties of a resource your class expects
|
160
|
-
# Define them in :camelCase, they will be available as #snake_cased methods
|
161
|
-
#
|
162
|
-
# You can pass in a second "type" argument:
|
163
|
-
# - If it is a class, it will be initialized for the property
|
164
|
-
# - Symbols are looked up in the COERCION constant for a lambda that
|
165
|
-
# defines a type conversion to apply
|
166
|
-
#
|
167
|
-
# Note: This second argument is not meant for contentful sub-resources,
|
168
|
-
# but for structured objects (like locales in a space)
|
169
|
-
# Sub-resources are handled by the resource builder
|
170
|
-
def property(name, property_class = nil)
|
171
|
-
property_coercions[name.to_sym] = property_class
|
172
|
-
define_method Contentful::Support.snakify(name) do
|
173
|
-
properties[name.to_sym]
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Ensure inherited classes pick up coercions
|
178
|
-
def update_coercions!
|
179
|
-
return if @coercions_updated
|
180
|
-
|
181
|
-
if superclass.respond_to? :property_coercions
|
182
|
-
@property_coercions = superclass.property_coercions.dup.merge(@property_coercions || {})
|
183
|
-
end
|
184
|
-
|
185
|
-
if superclass.respond_to? :sys_coercions
|
186
|
-
@sys_coercions = superclass.sys_coercions.dup.merge(@sys_coercions || {})
|
187
|
-
end
|
188
|
-
|
189
|
-
if superclass.respond_to? :fields_coercions
|
190
|
-
@fields_coercions = superclass.fields_coercions.dup.merge(@fields_coercions || {})
|
191
|
-
end
|
192
|
-
|
193
|
-
@coercions_updated = true
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def self.included(base)
|
198
|
-
base.extend(ClassMethods)
|
199
|
-
end
|
200
223
|
end
|
201
224
|
end
|
@@ -6,26 +6,34 @@ module Contentful
|
|
6
6
|
include Enumerable
|
7
7
|
|
8
8
|
# Returns true for array-like resources
|
9
|
+
#
|
10
|
+
# @return [true]
|
9
11
|
def array?
|
10
12
|
true
|
11
13
|
end
|
12
14
|
|
13
15
|
# Delegates to items#each
|
16
|
+
#
|
17
|
+
# @yield [Contentful::Entry, Contentful::Asset]
|
14
18
|
def each_item(&block)
|
15
19
|
items.each(&block)
|
16
20
|
end
|
17
|
-
|
21
|
+
alias each each_item
|
18
22
|
|
19
23
|
# Delegates to items#empty?
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
20
26
|
def empty?
|
21
27
|
items.empty?
|
22
28
|
end
|
23
29
|
|
24
30
|
# Delegetes to items#size
|
31
|
+
#
|
32
|
+
# @return [Number]
|
25
33
|
def size
|
26
34
|
items.size
|
27
35
|
end
|
28
|
-
|
36
|
+
alias length size
|
29
37
|
end
|
30
38
|
end
|
31
39
|
end
|
@@ -6,6 +6,7 @@ module Contentful
|
|
6
6
|
#
|
7
7
|
# It depends on system properties being available
|
8
8
|
module AssetFields
|
9
|
+
# Special field coercions for Asset.
|
9
10
|
FIELDS_COERCIONS = {
|
10
11
|
title: :string,
|
11
12
|
description: :string,
|
@@ -13,16 +14,20 @@ module Contentful
|
|
13
14
|
}
|
14
15
|
|
15
16
|
# Returns all fields of the asset
|
17
|
+
#
|
18
|
+
# @return [Hash] localized fields
|
16
19
|
def fields(wanted_locale = default_locale)
|
17
20
|
@fields[locale || wanted_locale]
|
18
21
|
end
|
19
22
|
|
23
|
+
# @private
|
20
24
|
def initialize(object, *)
|
21
25
|
super
|
22
26
|
|
23
27
|
initialize_fields_for_localized_resource(object)
|
24
28
|
end
|
25
29
|
|
30
|
+
# @private
|
26
31
|
def inspect(info = nil)
|
27
32
|
if fields.empty?
|
28
33
|
super(info)
|
@@ -31,12 +36,14 @@ module Contentful
|
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
39
|
+
# @private
|
34
40
|
module ClassMethods
|
35
41
|
def fields_coercions
|
36
42
|
FIELDS_COERCIONS
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
46
|
+
# @private
|
40
47
|
def self.included(base)
|
41
48
|
base.extend(ClassMethods)
|
42
49
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Contentful
|
2
|
+
module Resource
|
3
|
+
# Module for simplifying Custom Resource Creation
|
4
|
+
# Allows auto-mapping of fields to properties and properties to fields
|
5
|
+
module CustomResource
|
6
|
+
# @private
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
|
10
|
+
update_mappings!
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def update_mappings!
|
15
|
+
properties.keys.each do |name|
|
16
|
+
define_singleton_method Contentful::Support.snakify(name).to_sym do |wanted_locale = default_locale|
|
17
|
+
properties[name] ||= fields(wanted_locale)[name]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def marshal_load(raw_object)
|
24
|
+
super raw_object
|
25
|
+
update_mappings!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -8,12 +8,16 @@ module Contentful
|
|
8
8
|
# It depends on system properties being available
|
9
9
|
module Fields
|
10
10
|
# Returns all fields of the asset
|
11
|
+
#
|
12
|
+
# @return [Hash] fields for Resource on selected locale
|
11
13
|
def fields(wanted_locale = default_locale)
|
12
14
|
wanted_locale = wanted_locale.to_s
|
13
|
-
@fields.
|
15
|
+
@fields.key?(wanted_locale) ? @fields[wanted_locale] : @fields[locale]
|
14
16
|
end
|
15
17
|
|
16
18
|
# Returns all fields of the asset with locales nested by field
|
19
|
+
#
|
20
|
+
# @return [Hash] fields for Resource grouped by field name
|
17
21
|
def fields_with_locales
|
18
22
|
remapped_fields = {}
|
19
23
|
locales.each do |locale|
|
@@ -26,11 +30,26 @@ module Contentful
|
|
26
30
|
remapped_fields
|
27
31
|
end
|
28
32
|
|
33
|
+
# @private
|
34
|
+
module ClassMethods
|
35
|
+
# No coercions, since no content type available
|
36
|
+
def fields_coercions
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @private
|
42
|
+
def self.included(base)
|
43
|
+
base.extend(ClassMethods)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
29
47
|
def initialize(object = nil, *)
|
30
48
|
super
|
31
49
|
extract_fields_from_object! object if object
|
32
50
|
end
|
33
51
|
|
52
|
+
# @private
|
34
53
|
def inspect(info = nil)
|
35
54
|
if fields.empty?
|
36
55
|
super(info)
|
@@ -48,17 +67,6 @@ module Contentful
|
|
48
67
|
def extract_fields_from_object!(object)
|
49
68
|
initialize_fields_for_localized_resource(object)
|
50
69
|
end
|
51
|
-
|
52
|
-
module ClassMethods
|
53
|
-
# No coercions, since no content type available
|
54
|
-
def fields_coercions
|
55
|
-
{}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.included(base)
|
60
|
-
base.extend(ClassMethods)
|
61
|
-
end
|
62
70
|
end
|
63
71
|
end
|
64
72
|
end
|