contentful 0.8.0 → 0.9.0

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