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.
@@ -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)
@@ -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
@@ -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}#{ @type == :single ? "/#{id}" : '' }"
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
- !! @absolute
35
+ @endpoint.start_with?('http')
39
36
  end
40
37
 
41
38
  # Returns a new Request object with the same data
@@ -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
- # to be returned by the client by default
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
- def initialize(object = nil, request = nil, client = nil, default_locale = Contentful::Client::DEFAULT_CONFIGURATION[:default_locale])
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 = extract_from_object(object, :property,
30
- self.class.property_coercions.keys)
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
- @api_object = object
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 { |k, v|
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
- !self.is_a?(Asset) &&
123
- !self.is_a?(Field) &&
124
- !is_location?(value) &&
125
- !is_link?(value) &&
126
- !is_image?(value)
194
+ !is_a?(Asset) &&
195
+ !is_a?(Field) &&
196
+ !location?(value) &&
197
+ !link?(value) &&
198
+ !image?(value)
127
199
  end
128
200
 
129
- def is_location?(value)
130
- value.has_key?("lat") || value.has_key?("lon")
201
+ def location?(value)
202
+ value.key?('lat') || value.key?('lon')
131
203
  end
132
204
 
133
- def is_link?(value)
134
- value.has_key?("sys") && value["sys"]["type"] == "Link"
205
+ def link?(value)
206
+ value.key?('sys') && value['sys']['type'] == 'Link'
135
207
  end
136
208
 
137
- def is_image?(value)
138
- value.has_key?("image")
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
- alias_method :each, :each_item
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
- alias_method :length, :size
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.has_key?(wanted_locale) ? @fields[wanted_locale] : @fields[locale]
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