contentful 1.2.2 → 2.0.0

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