contentful 1.2.2 → 2.0.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE.txt +1 -0
  4. data/README.md +8 -0
  5. data/contentful.gemspec +2 -1
  6. data/examples/custom_classes.rb +23 -26
  7. data/examples/raise_errors.rb +2 -2
  8. data/lib/contentful.rb +0 -1
  9. data/lib/contentful/array.rb +27 -19
  10. data/lib/contentful/array_like.rb +51 -0
  11. data/lib/contentful/asset.rb +43 -11
  12. data/lib/contentful/base_resource.rb +87 -0
  13. data/lib/contentful/client.rb +43 -34
  14. data/lib/contentful/coercions.rb +116 -0
  15. data/lib/contentful/content_type.rb +23 -8
  16. data/lib/contentful/content_type_cache.rb +26 -0
  17. data/lib/contentful/deleted_asset.rb +2 -5
  18. data/lib/contentful/deleted_entry.rb +2 -5
  19. data/lib/contentful/entry.rb +55 -33
  20. data/lib/contentful/error.rb +1 -1
  21. data/lib/contentful/field.rb +37 -9
  22. data/lib/contentful/fields_resource.rb +115 -0
  23. data/lib/contentful/file.rb +7 -8
  24. data/lib/contentful/link.rb +3 -6
  25. data/lib/contentful/locale.rb +6 -6
  26. data/lib/contentful/location.rb +7 -5
  27. data/lib/contentful/resource_builder.rb +72 -226
  28. data/lib/contentful/space.rb +16 -6
  29. data/lib/contentful/support.rb +41 -3
  30. data/lib/contentful/sync_page.rb +17 -10
  31. data/lib/contentful/version.rb +1 -1
  32. data/spec/array_spec.rb +4 -8
  33. data/spec/client_class_spec.rb +12 -23
  34. data/spec/client_configuration_spec.rb +13 -23
  35. data/spec/content_type_spec.rb +0 -5
  36. data/spec/entry_spec.rb +130 -125
  37. data/spec/error_requests_spec.rb +1 -1
  38. data/spec/field_spec.rb +0 -5
  39. data/spec/file_spec.rb +0 -5
  40. data/spec/fixtures/vcr_cassettes/entry.yml +54 -64
  41. data/spec/fixtures/vcr_cassettes/entry/include_resolution.yml +101 -0
  42. data/spec/fixtures/vcr_cassettes/entry/marshall.yml +227 -251
  43. data/spec/fixtures/vcr_cassettes/entry/raw.yml +88 -124
  44. data/spec/fixtures/vcr_cassettes/entry_locales.yml +56 -74
  45. data/spec/fixtures/vcr_cassettes/human.yml +63 -40
  46. data/spec/fixtures/vcr_cassettes/location.yml +99 -211
  47. data/spec/fixtures/vcr_cassettes/multi_locale_array_reference.yml +12 -16
  48. data/spec/fixtures/vcr_cassettes/not_found.yml +26 -21
  49. data/spec/fixtures/vcr_cassettes/nyancat.yml +53 -63
  50. data/spec/fixtures/vcr_cassettes/ratelimit.yml +1 -1
  51. data/spec/fixtures/vcr_cassettes/reloaded_entry.yml +54 -64
  52. data/spec/fixtures/vcr_cassettes/unauthorized.yml +1 -1
  53. data/spec/fixtures/vcr_cassettes/unavailable.yml +27 -15
  54. data/spec/link_spec.rb +3 -2
  55. data/spec/locale_spec.rb +0 -5
  56. data/spec/location_spec.rb +1 -6
  57. data/spec/request_spec.rb +3 -2
  58. data/spec/resource_building_spec.rb +10 -7
  59. data/spec/response_spec.rb +1 -1
  60. data/spec/space_spec.rb +0 -5
  61. data/spec/spec_helper.rb +3 -0
  62. data/spec/support/json_responses.rb +3 -3
  63. data/spec/sync_page_spec.rb +1 -6
  64. data/spec/sync_spec.rb +11 -7
  65. metadata +69 -20
  66. data/examples/dynamic_entries.rb +0 -124
  67. data/examples/resource_mapping.rb +0 -32
  68. data/lib/contentful/constants.rb +0 -504
  69. data/lib/contentful/dynamic_entry.rb +0 -57
  70. data/lib/contentful/resource.rb +0 -239
  71. data/lib/contentful/resource/array_like.rb +0 -39
  72. data/lib/contentful/resource/asset_fields.rb +0 -58
  73. data/lib/contentful/resource/custom_resource.rb +0 -29
  74. data/lib/contentful/resource/fields.rb +0 -73
  75. data/lib/contentful/resource/system_properties.rb +0 -55
  76. data/spec/coercions_spec.rb +0 -23
  77. data/spec/dynamic_entry_spec.rb +0 -75
  78. data/spec/resource_spec.rb +0 -79
@@ -1,57 +0,0 @@
1
- require_relative 'resource'
2
- require_relative 'resource/fields'
3
- require_relative 'location'
4
-
5
- module Contentful
6
- # Wrapper for Entries with Cached Content Types
7
- class DynamicEntry < Entry
8
- # Coercions from Contentful Types to Ruby native types
9
- KNOWN_TYPES = {
10
- 'String' => :string,
11
- 'Text' => :string,
12
- 'Symbol' => :string,
13
- 'Integer' => :integer,
14
- 'Float' => :float,
15
- 'Boolean' => :boolean,
16
- 'Date' => :date,
17
- 'Location' => Location
18
- }
19
-
20
- # @private
21
- def self.create(content_type)
22
- unless content_type.is_a? ContentType
23
- content_type = ContentType.new(content_type)
24
- end
25
-
26
- fields_coercions = Hash[
27
- content_type.fields.map do |field|
28
- [field.id.to_sym, KNOWN_TYPES[field.type]]
29
- end
30
- ]
31
-
32
- Class.new DynamicEntry do
33
- content_type.fields.each do |f|
34
- define_method Support.snakify(f.id).to_sym do |wanted_locale = nil|
35
- fields(wanted_locale)[f.id.to_sym]
36
- end
37
- end
38
-
39
- define_singleton_method :fields_coercions do
40
- fields_coercions
41
- end
42
-
43
- define_singleton_method :content_type do
44
- content_type
45
- end
46
-
47
- define_singleton_method :to_s do
48
- "Contentful::DynamicEntry[#{content_type.id}]"
49
- end
50
-
51
- define_singleton_method :inspect do
52
- "Contentful::DynamicEntry[#{content_type.id}]"
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,239 +0,0 @@
1
- require_relative 'resource/system_properties'
2
- require 'contentful/constants'
3
- require 'date'
4
-
5
- module Contentful
6
- # Include this module to declare a class to be a contentful resource.
7
- # This is done by the default in the existing resource classes
8
- #
9
- # You can define your own classes that behave like contentful resources:
10
- # See examples/custom_classes.rb to see how.
11
- #
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
17
- module Resource
18
- # @private
19
- # rubocop:disable Style/DoubleNegation
20
- COERCIONS = {
21
- string: ->(v) { v.nil? ? nil : v.to_s },
22
- integer: ->(v) { v.to_i },
23
- float: ->(v) { v.to_f },
24
- boolean: ->(v) { !!v },
25
- date: ->(v) { DateTime.parse(v) }
26
- }
27
- # rubocop:enable Style/DoubleNegation
28
-
29
- attr_reader :properties, :request, :client, :default_locale, :raw
30
-
31
- # @private
32
- def initialize(object = nil,
33
- request = nil,
34
- client = nil,
35
- default_locale = Contentful::Client::DEFAULT_CONFIGURATION[:default_locale])
36
- self.class.update_coercions!
37
- @default_locale = default_locale
38
-
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
- )
48
- @request = request
49
- @client = client
50
- @raw = object
51
- end
52
-
53
- # @private
54
- def inspect(info = nil)
55
- properties_info = properties.empty? ? '' : " @properties=#{properties.inspect}"
56
- "#<#{self.class}:#{properties_info}#{info}>"
57
- end
58
-
59
- # Returns true for resources that are entries
60
- def entry?
61
- false
62
- end
63
-
64
- # Returns true for resources that behave like an array
65
- def array?
66
- false
67
- end
68
-
69
- # Resources that don't include SystemProperties return nil for #sys
70
- def sys
71
- nil
72
- end
73
-
74
- # Resources that don't include Fields or AssetFields return nil for #fields
75
- def fields
76
- nil
77
- end
78
-
79
- # Issues the request that was made to fetch this response again.
80
- # Only works for top-level resources
81
- def reload
82
- if request
83
- request.get
84
- else
85
- false
86
- end
87
- end
88
-
89
- # Register the resources properties on class level by using the #property method
90
- # @private
91
- module ClassMethods
92
- # By default, fields come flattened in the current locale. This is different for sync
93
- def property_coercions
94
- @property_coercions ||= {}
95
- end
96
-
97
- # Defines which properties of a resource your class expects
98
- # Define them in :camelCase, they will be available as #snake_cased methods
99
- #
100
- # You can pass in a second "type" argument:
101
- # - If it is a class, it will be initialized for the property
102
- # - Symbols are looked up in the COERCION constant for a lambda that
103
- # defines a type conversion to apply
104
- #
105
- # Note: This second argument is not meant for contentful sub-resources,
106
- # but for structured objects (like locales in a space)
107
- # Sub-resources are handled by the resource builder
108
- def property(name, property_class = nil)
109
- property_coercions[name.to_sym] = property_class
110
- define_method Contentful::Support.snakify(name) do
111
- properties[name.to_sym]
112
- end
113
- end
114
-
115
- # Ensure inherited classes pick up coercions
116
- def update_coercions!
117
- return if @coercions_updated
118
-
119
- if superclass.respond_to? :property_coercions
120
- @property_coercions = superclass.property_coercions.dup.merge(@property_coercions || {})
121
- end
122
-
123
- if superclass.respond_to? :sys_coercions
124
- @sys_coercions = superclass.sys_coercions.dup.merge(@sys_coercions || {})
125
- end
126
-
127
- if superclass.respond_to? :fields_coercions
128
- @fields_coercions = superclass.fields_coercions.dup.merge(@fields_coercions || {})
129
- end
130
-
131
- @coercions_updated = true
132
- end
133
- end
134
-
135
- # @private
136
- def self.included(base)
137
- base.extend(ClassMethods)
138
- end
139
-
140
- private
141
-
142
- def initialize_fields_for_localized_resource(object)
143
- @fields = {}
144
-
145
- object.fetch('fields', {}).each do |field_name, nested_child_object|
146
- if Support.localized?(nested_child_object)
147
- nested_child_object.each do |object_locale, real_child_object|
148
- @fields[object_locale] ||= {}
149
- @fields[object_locale].merge! extract_from_object(
150
- { field_name => real_child_object }, :fields
151
- )
152
- end
153
- else
154
- # if sys.locale property not present (due to select operator) use default_locale
155
- @fields[locale || default_locale] ||= {}
156
- @fields[locale || default_locale].merge! extract_from_object({ field_name => nested_child_object }, :fields)
157
- end
158
- end
159
- end
160
-
161
- def extract_from_object(object, namespace, keys = nil)
162
- if object
163
- keys ||= object.keys
164
- keys.each.with_object({}) do |name, res|
165
- value = object.is_a?(::Array) ? object : object[name.to_s]
166
- kind = self.class.public_send(:"#{namespace}_coercions")[name.to_sym]
167
- res[name.to_sym] = coerce_value_or_array(value, kind)
168
- end
169
- else
170
- {}
171
- end
172
- end
173
-
174
- def coerce_value_or_array(value, what = nil)
175
- if value.is_a? ::Array
176
- value.map { |v| coerce_or_create_class(v, what) }
177
- elsif should_coerce_hash?(value)
178
- ::Hash[value.map do |k, v|
179
- to_coerce = pre_coerce(v)
180
- coercion = v.is_a?(Numeric) ? v : coerce_or_create_class(to_coerce, what)
181
- [k.to_sym, coercion]
182
- end]
183
- else
184
- coerce_or_create_class(value, what)
185
- end
186
- end
187
-
188
- def pre_coerce(value)
189
- case value
190
- when Numeric, true, false, nil
191
- value
192
- when Hash
193
- result = {}
194
- value.each_key do |k|
195
- result[k.to_sym] = pre_coerce(value[k])
196
- end
197
- result
198
- when ::Array
199
- value.map { |e| pre_coerce(e) }
200
- else
201
- value.to_s
202
- end
203
- end
204
-
205
- def should_coerce_hash?(value)
206
- value.is_a?(::Hash) &&
207
- !is_a?(Asset) &&
208
- !is_a?(Field) &&
209
- !location?(value) &&
210
- !link?(value) &&
211
- !image?(value)
212
- end
213
-
214
- def location?(value)
215
- value.key?('lat') || value.key?('lon')
216
- end
217
-
218
- def link?(value)
219
- value.key?('sys') && value['sys']['type'] == 'Link'
220
- end
221
-
222
- def image?(value)
223
- value.key?('image')
224
- end
225
-
226
- def coerce_or_create_class(value, what)
227
- case what
228
- when Symbol
229
- COERCIONS[what] ? COERCIONS[what][value] : value
230
- when Proc
231
- what[value]
232
- when Class
233
- what.new(value, client) if value
234
- else
235
- value
236
- end
237
- end
238
- end
239
- end
@@ -1,39 +0,0 @@
1
- module Contentful
2
- module Resource
3
- # Useful methods for array-like resources that can be included if an
4
- # :items property exists
5
- module ArrayLike
6
- include Enumerable
7
-
8
- # Returns true for array-like resources
9
- #
10
- # @return [true]
11
- def array?
12
- true
13
- end
14
-
15
- # Delegates to items#each
16
- #
17
- # @yield [Contentful::Entry, Contentful::Asset]
18
- def each_item(&block)
19
- items.each(&block)
20
- end
21
- alias each each_item
22
-
23
- # Delegates to items#empty?
24
- #
25
- # @return [Boolean]
26
- def empty?
27
- items.empty?
28
- end
29
-
30
- # Delegetes to items#size
31
- #
32
- # @return [Number]
33
- def size
34
- items.size
35
- end
36
- alias length size
37
- end
38
- end
39
- end
@@ -1,58 +0,0 @@
1
- require_relative '../file'
2
-
3
- module Contentful
4
- module Resource
5
- # Special fields for Asset. Don't include together wit Contentful::Resource::Fields
6
- #
7
- # It depends on system properties being available
8
- module AssetFields
9
- # Special field coercions for Asset.
10
- FIELDS_COERCIONS = {
11
- title: :string,
12
- description: :string,
13
- file: File
14
- }
15
-
16
- # Returns all fields of the asset
17
- #
18
- # @return [Hash] localized fields
19
- def fields(wanted_locale = default_locale)
20
- @fields[locale || wanted_locale] || {}
21
- end
22
-
23
- # @private
24
- def initialize(object, *)
25
- super
26
-
27
- initialize_fields_for_localized_resource(object)
28
- end
29
-
30
- # @private
31
- def inspect(info = nil)
32
- if fields.empty?
33
- super(info)
34
- else
35
- super("#{info} @fields=#{fields.inspect}")
36
- end
37
- end
38
-
39
- # @private
40
- module ClassMethods
41
- def fields_coercions
42
- FIELDS_COERCIONS
43
- end
44
- end
45
-
46
- # @private
47
- def self.included(base)
48
- base.extend(ClassMethods)
49
-
50
- base.fields_coercions.keys.each do |name|
51
- base.send :define_method, Contentful::Support.snakify(name) do
52
- fields[name.to_sym]
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,29 +0,0 @@
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
@@ -1,73 +0,0 @@
1
- require 'contentful/constants'
2
-
3
- module Contentful
4
- module Resource
5
- # Include this module into your Resource class to enable it
6
- # to deal with entry fields (but not asset fields)
7
- #
8
- # It depends on system properties being available
9
- module Fields
10
- # Returns all fields of the asset
11
- #
12
- # @return [Hash] fields for Resource on selected locale
13
- def fields(wanted_locale = nil)
14
- wanted_locale = (locale || default_locale) if wanted_locale.nil?
15
- @fields[wanted_locale.to_s] || {}
16
- end
17
-
18
- # Returns all fields of the asset with locales nested by field
19
- #
20
- # @return [Hash] fields for Resource grouped by field name
21
- def fields_with_locales
22
- remapped_fields = {}
23
- locales.each do |locale|
24
- fields(locale).each do |name, value|
25
- remapped_fields[name] ||= {}
26
- remapped_fields[name][locale.to_sym] = value
27
- end
28
- end
29
-
30
- remapped_fields
31
- end
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
47
- def initialize(object = nil, *)
48
- super
49
- extract_fields_from_object! object if object
50
- end
51
-
52
- # @private
53
- def inspect(info = nil)
54
- if fields.empty?
55
- super(info)
56
- else
57
- super("#{info} @fields=#{fields.inspect}")
58
- end
59
- end
60
-
61
- # Provides a list of the available locales for a Resource
62
- def locales
63
- @fields.keys
64
- end
65
-
66
- private
67
-
68
- def extract_fields_from_object!(object)
69
- initialize_fields_for_localized_resource(object)
70
- end
71
- end
72
- end
73
- end