contentful-management 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -29
  3. data/.rubocop_todo.yml +24 -0
  4. data/.travis.yml +1 -1
  5. data/.yardopts +4 -0
  6. data/CHANGELOG.md +12 -0
  7. data/Gemfile +1 -0
  8. data/Guardfile +39 -34
  9. data/README.md +42 -1
  10. data/Rakefile +2 -0
  11. data/contentful-management.gemspec +2 -1
  12. data/lib/contentful/management/api_key.rb +65 -0
  13. data/lib/contentful/management/array.rb +3 -2
  14. data/lib/contentful/management/asset.rb +124 -58
  15. data/lib/contentful/management/client.rb +60 -19
  16. data/lib/contentful/management/content_type.rb +104 -49
  17. data/lib/contentful/management/content_type_entry_methods_factory.rb +21 -0
  18. data/lib/contentful/management/dynamic_entry.rb +22 -18
  19. data/lib/contentful/management/entry.rb +138 -80
  20. data/lib/contentful/management/error.rb +23 -23
  21. data/lib/contentful/management/field.rb +16 -13
  22. data/lib/contentful/management/http_client.rb +29 -0
  23. data/lib/contentful/management/link.rb +3 -3
  24. data/lib/contentful/management/locale.rb +55 -26
  25. data/lib/contentful/management/location.rb +1 -1
  26. data/lib/contentful/management/request.rb +11 -12
  27. data/lib/contentful/management/resource.rb +27 -12
  28. data/lib/contentful/management/resource/array_like.rb +1 -1
  29. data/lib/contentful/management/resource/asset_fields.rb +1 -0
  30. data/lib/contentful/management/resource/entry_fields.rb +2 -0
  31. data/lib/contentful/management/resource/field_aware.rb +16 -5
  32. data/lib/contentful/management/resource/fields.rb +16 -8
  33. data/lib/contentful/management/resource/refresher.rb +1 -0
  34. data/lib/contentful/management/resource/system_properties.rb +13 -6
  35. data/lib/contentful/management/resource_builder.rb +43 -40
  36. data/lib/contentful/management/space.rb +67 -28
  37. data/lib/contentful/management/space_api_key_methods_factory.rb +15 -0
  38. data/lib/contentful/management/space_asset_methods_factory.rb +4 -0
  39. data/lib/contentful/management/space_association_all_published_method_factory.rb +11 -0
  40. data/lib/contentful/management/space_association_methods_factory.rb +4 -2
  41. data/lib/contentful/management/space_content_type_methods_factory.rb +4 -0
  42. data/lib/contentful/management/space_entry_methods_factory.rb +4 -0
  43. data/lib/contentful/management/space_locale_methods_factory.rb +2 -0
  44. data/lib/contentful/management/space_webhook_methods_factory.rb +2 -0
  45. data/lib/contentful/management/validation.rb +3 -2
  46. data/lib/contentful/management/version.rb +4 -1
  47. data/lib/contentful/management/webhook.rb +42 -21
  48. data/spec/fixtures/vcr_cassettes/api_key/all_for_space.yml +194 -0
  49. data/spec/fixtures/vcr_cassettes/api_key/create_for_space.yml +113 -0
  50. data/spec/fixtures/vcr_cassettes/api_key/find.yml +113 -0
  51. data/spec/fixtures/vcr_cassettes/api_key/find_for_space_not_found.yml +71 -0
  52. data/spec/fixtures/vcr_cassettes/asset/all_public.yml +112 -0
  53. data/spec/fixtures/vcr_cassettes/content_type/all_public.yml +106 -0
  54. data/spec/fixtures/vcr_cassettes/entry/all_public.yml +102 -0
  55. data/spec/fixtures/vcr_cassettes/locale/destroy.yml +330 -0
  56. data/spec/fixtures/vcr_cassettes/locale/update_both.yml +306 -0
  57. data/spec/fixtures/vcr_cassettes/locale/update_code.yml +306 -0
  58. data/spec/fixtures/vcr_cassettes/locale/update_name.yml +306 -0
  59. data/spec/fixtures/vcr_cassettes/space/api_key/all.yml +410 -0
  60. data/spec/fixtures/vcr_cassettes/space/api_key/create.yml +329 -0
  61. data/spec/fixtures/vcr_cassettes/space/api_key/find.yml +329 -0
  62. data/spec/fixtures/vcr_cassettes/space/asset/all_public.yml +328 -0
  63. data/spec/fixtures/vcr_cassettes/space/content_type/all_public.yml +322 -0
  64. data/spec/fixtures/vcr_cassettes/space/entry/all_public.yml +318 -0
  65. data/spec/lib/contentful/management/api_key_spec.rb +55 -0
  66. data/spec/lib/contentful/management/asset_spec.rb +10 -0
  67. data/spec/lib/contentful/management/content_type_spec.rb +10 -0
  68. data/spec/lib/contentful/management/entry_spec.rb +10 -0
  69. data/spec/lib/contentful/management/locale_spec.rb +58 -0
  70. data/spec/lib/contentful/management/space_spec.rb +53 -0
  71. metadata +58 -9
@@ -1,20 +1,41 @@
1
1
  module Contentful
2
2
  module Management
3
+ # Wrapper for Entry manipulation for a specific Content Type
4
+ # @private
3
5
  class ContentTypeEntryMethodsFactory
4
6
  attr_reader :content_type
5
7
 
8
+ # @private
6
9
  def initialize(content_type)
7
10
  @content_type = content_type
8
11
  end
9
12
 
13
+ # Gets all entries for a specific ContentType
14
+ #
15
+ # @param [Hash] params
16
+ # @see _ For complete option list: http://docs.contentfulcda.apiary.io/#reference/search-parameters
17
+ # @option params [String] 'sys.id'
18
+ # @option params [String] :limit
19
+ # @option params [String] :skip
20
+ # @option params [String] :order
21
+ #
22
+ # @return [Contentful::Management::Array<Contentful::Management::Entry>]
10
23
  def all(params = {})
11
24
  Entry.all(content_type.space.id, params.merge(content_type: content_type.id))
12
25
  end
13
26
 
27
+ # Creates an entry for a content type.
28
+ #
29
+ # @param [Hash] attributes
30
+ #
31
+ # @return [Contentful::Management::Entry]
14
32
  def create(attributes)
15
33
  Entry.create(content_type, attributes)
16
34
  end
17
35
 
36
+ # Instantiates an empty entry for a content type.
37
+ #
38
+ # @return [Contentful::Management::Entry]
18
39
  def new
19
40
  dynamic_entry_class = content_type.client.register_dynamic_entry(content_type.id, DynamicEntry.create(content_type))
20
41
  dynamic_entry = dynamic_entry_class.new
@@ -4,27 +4,30 @@ require_relative 'location'
4
4
 
5
5
  module Contentful
6
6
  module Management
7
+ # Wrapper for Entries with Cached Content Types
7
8
  class DynamicEntry < Contentful::Management::Entry
9
+ # Coercions from Contentful Types to Ruby native types
8
10
  KNOWN_TYPES = {
9
- 'String' => :string,
10
- 'Text' => :string,
11
- 'Symbol' => :string,
12
- 'Integer' => :integer,
13
- 'Float' => :float,
14
- 'Boolean' => :boolean,
15
- 'Date' => :date,
16
- 'Location' => Location
11
+ 'String' => :string,
12
+ 'Text' => :string,
13
+ 'Symbol' => :string,
14
+ 'Integer' => :integer,
15
+ 'Float' => :float,
16
+ 'Boolean' => :boolean,
17
+ 'Date' => :date,
18
+ 'Location' => Location
17
19
  }
18
20
 
21
+ # @private
19
22
  def self.create(content_type)
20
23
  unless content_type.is_a? ContentType
21
24
  content_type = ContentType.new(content_type)
22
25
  end
23
26
 
24
27
  fields_coercions = Hash[
25
- content_type.fields.map do |field|
26
- [field.id.to_sym, KNOWN_TYPES[field.type]]
27
- end
28
+ content_type.fields.map do |field|
29
+ [field.id.to_sym, KNOWN_TYPES[field.type]]
30
+ end
28
31
  ]
29
32
 
30
33
  Class.new DynamicEntry do
@@ -33,16 +36,16 @@ module Contentful
33
36
  define_method accessor_name do
34
37
  fields[field.id.to_sym]
35
38
  end
36
- define_method "#{ accessor_name }_with_locales" do
39
+ define_method "#{accessor_name}_with_locales" do
37
40
  fields_for_query[field.id.to_sym]
38
41
  end
39
- define_method "#{ accessor_name }=" do |value|
42
+ define_method "#{accessor_name}=" do |value|
40
43
  if localized_or_default_locale(field, locale)
41
- @fields[locale] ||= {}
42
- @fields[locale][field.id.to_sym] = value
44
+ @fields[locale] ||= {}
45
+ @fields[locale][field.id.to_sym] = value
43
46
  end
44
47
  end
45
- define_method "#{ accessor_name }_with_locales=" do |values|
48
+ define_method "#{accessor_name}_with_locales=" do |values|
46
49
  values.each do |locale, value|
47
50
  if localized_or_default_locale(field, locale)
48
51
  @fields[locale] ||= {}
@@ -61,15 +64,16 @@ module Contentful
61
64
  end
62
65
 
63
66
  define_singleton_method :to_s do
64
- "Contentful::Management::DynamicEntry[#{ content_type.id }]"
67
+ "Contentful::Management::DynamicEntry[#{content_type.id}]"
65
68
  end
66
69
 
67
70
  define_singleton_method :inspect do
68
- "Contentful::Management::DynamicEntry[#{ content_type.id }]"
71
+ "Contentful::Management::DynamicEntry[#{content_type.id}]"
69
72
  end
70
73
  end
71
74
  end
72
75
 
76
+ # @private
73
77
  def localized_or_default_locale(field, locale)
74
78
  field.localized || default_locale == locale
75
79
  end
@@ -6,7 +6,7 @@ require_relative 'resource/field_aware'
6
6
  module Contentful
7
7
  module Management
8
8
  # Resource class for Entry.
9
- # https://www.contentful.com/developers/documentation/content-management-api/#resources-entries
9
+ # @see _ https://www.contentful.com/developers/documentation/content-management-api/#resources-entries
10
10
  class Entry
11
11
  include Contentful::Management::Resource
12
12
  include Contentful::Management::Resource::SystemProperties
@@ -17,12 +17,41 @@ module Contentful
17
17
  attr_accessor :content_type
18
18
 
19
19
  # Gets a collection of entries.
20
- # Takes an id of space and hash of parameters with optional content_type_id.
21
- # Returns a Contentful::Management::Array of Contentful::Management::Entry.
20
+ #
21
+ # @param [String] space_id
22
+ # @param [Hash] parameters
23
+ # @see _ For complete option list: http://docs.contentfulcda.apiary.io/#reference/search-parameters
24
+ # @option parameters [String] 'sys.id' Entry ID
25
+ # @option parameters [String] :content_type
26
+ # @option parameters [Integer] :limit
27
+ # @option parameters [Integer] :skip
28
+ #
29
+ # @return [Contentful::Management::Array<Contentful::Management::Entry>]
22
30
  def self.all(space_id, parameters = {})
23
31
  request = Request.new(
24
- "/#{ space_id }/entries",
25
- parameters
32
+ "/#{space_id}/entries",
33
+ parameters
34
+ )
35
+ response = request.get
36
+ result = ResourceBuilder.new(response, {}, {})
37
+ result.run
38
+ end
39
+
40
+ # Gets a collection of published entries.
41
+ #
42
+ # @param [String] space_id
43
+ # @param [Hash] parameters
44
+ # @see _ For complete option list: http://docs.contentfulcda.apiary.io/#reference/search-parameters
45
+ # @option parameters [String] 'sys.id' Entry ID
46
+ # @option parameters [String] :content_type
47
+ # @option parameters [Integer] :limit
48
+ # @option parameters [Integer] :skip
49
+ #
50
+ # @return [Contentful::Management::Array<Contentful::Management::Entry>]
51
+ def self.all_published(space_id, parameters = {})
52
+ request = Request.new(
53
+ "/#{space_id}/public/entries",
54
+ parameters
26
55
  )
27
56
  response = request.get
28
57
  result = ResourceBuilder.new(response, {}, {})
@@ -30,34 +59,43 @@ module Contentful
30
59
  end
31
60
 
32
61
  # Gets a specific entry.
33
- # Takes an id of space and entry.
34
- # Returns a Contentful::Management::Entry.
62
+ #
63
+ # @param [String] space_id
64
+ # @param [String] entry_id
65
+ #
66
+ # @return [Contentful::Management::Entry]
35
67
  def self.find(space_id, entry_id)
36
- request = Request.new("/#{ space_id }/entries/#{ entry_id }")
68
+ request = Request.new("/#{space_id}/entries/#{entry_id}")
37
69
  response = request.get
38
70
  result = ResourceBuilder.new(response, {}, {})
39
71
  result.run
40
72
  end
41
73
 
42
74
  # Creates an entry.
43
- # Takes a content type object and hash with attributes of content type.
44
- # Returns a Contentful::Management::Entry.
75
+ #
76
+ # @param [Contentful::Management::ContentType] content_type
77
+ # @param [Hash] attributes extracted from Content Type fields
78
+ #
79
+ # @return [Contentful::Management::Entry]
45
80
  def self.create(content_type, attributes)
46
81
  custom_id = attributes[:id]
47
82
  locale = attributes[:locale]
48
83
  fields_for_create = if attributes[:fields] # create from initialized dynamic entry via save
49
84
  tmp_entry = new
50
85
  tmp_entry.instance_variable_set(:@fields, attributes.delete(:fields) || {})
51
- Contentful::Management::Support.deep_hash_merge(tmp_entry.fields_for_query, tmp_entry.fields_from_attributes(attributes))
86
+ Contentful::Management::Support.deep_hash_merge(
87
+ tmp_entry.fields_for_query,
88
+ tmp_entry.fields_from_attributes(attributes)
89
+ )
52
90
  else
53
91
  fields_with_locale content_type, attributes
54
92
  end
55
93
 
56
94
  request = Request.new(
57
- "/#{ content_type.sys[:space].id }/entries/#{ custom_id }",
58
- {fields: fields_for_create},
59
- nil,
60
- content_type_id: content_type.id
95
+ "/#{content_type.sys[:space].id}/entries/#{custom_id}",
96
+ { fields: fields_for_create },
97
+ nil,
98
+ content_type_id: content_type.id
61
99
  )
62
100
  response = custom_id.nil? ? request.post : request.put
63
101
  result = ResourceBuilder.new(response, {}, {})
@@ -67,6 +105,11 @@ module Contentful
67
105
  entry
68
106
  end
69
107
 
108
+ # Gets Hash of fields for the current locale
109
+ #
110
+ # @param [String] wanted_locale
111
+ #
112
+ # @return [Hash] localized fields
70
113
  def fields(wanted_locale = default_locale)
71
114
  requested_locale = locale || wanted_locale
72
115
  @fields[requested_locale] = {} unless @fields[requested_locale]
@@ -76,16 +119,18 @@ module Contentful
76
119
  end
77
120
 
78
121
  # Updates an entry.
79
- # Takes an optional hash with attributes of content type.
80
- # Returns a Contentful::Management::Entry.
122
+ #
123
+ # @param [Hash] attributes extracted from Content Type fields
124
+ #
125
+ # @return [Contentful::Management::Entry]
81
126
  def update(attributes)
82
127
  fields_for_update = Contentful::Management::Support.deep_hash_merge(fields_for_query, fields_from_attributes(attributes))
83
128
 
84
129
  request = Request.new(
85
- "/#{ space.id }/entries/#{ id }",
86
- {fields: fields_for_update},
87
- id = nil,
88
- version: sys[:version]
130
+ "/#{space.id}/entries/#{id}",
131
+ { fields: fields_for_update },
132
+ nil,
133
+ version: sys[:version]
89
134
  )
90
135
  response = request.put
91
136
  result = ResourceBuilder.new(response, {}, {}).run
@@ -93,7 +138,9 @@ module Contentful
93
138
  end
94
139
 
95
140
  # If an entry is a new object gets created in the Contentful, otherwise the existing entry gets updated.
96
- # See README for details.
141
+ # @see _ README for details.
142
+ #
143
+ # @return [Contentful::Management::Entry]
97
144
  def save
98
145
  if id
99
146
  update({})
@@ -104,13 +151,14 @@ module Contentful
104
151
  end
105
152
 
106
153
  # Publishes an entry.
107
- # Returns a Contentful::Management::Entry.
154
+ #
155
+ # @return [Contentful::Management::Entry]
108
156
  def publish
109
157
  request = Request.new(
110
- "/#{ space.id }/entries/#{ id }/published",
111
- {},
112
- id = nil,
113
- version: sys[:version]
158
+ "/#{space.id}/entries/#{id}/published",
159
+ {},
160
+ nil,
161
+ version: sys[:version]
114
162
  )
115
163
  response = request.put
116
164
  result = ResourceBuilder.new(response, {}, {}).run
@@ -118,13 +166,14 @@ module Contentful
118
166
  end
119
167
 
120
168
  # Unpublishes an entry.
121
- # Returns a Contentful::Management::Entry.
169
+ #
170
+ # @return [Contentful::Management::Entry]
122
171
  def unpublish
123
172
  request = Request.new(
124
- "/#{ space.id }/entries/#{ id }/published",
125
- {},
126
- id = nil,
127
- version: sys[:version]
173
+ "/#{space.id}/entries/#{id}/published",
174
+ {},
175
+ nil,
176
+ version: sys[:version]
128
177
  )
129
178
  response = request.delete
130
179
  result = ResourceBuilder.new(response, {}, {}).run
@@ -132,13 +181,14 @@ module Contentful
132
181
  end
133
182
 
134
183
  # Archives an entry.
135
- # Returns a Contentful::Management::Entry.
184
+ #
185
+ # @return [Contentful::Management::Entry]
136
186
  def archive
137
187
  request = Request.new(
138
- "/#{ space.id }/entries/#{ id }/archived",
139
- {},
140
- id = nil,
141
- version: sys[:version]
188
+ "/#{space.id}/entries/#{id}/archived",
189
+ {},
190
+ nil,
191
+ version: sys[:version]
142
192
  )
143
193
  response = request.put
144
194
  result = ResourceBuilder.new(response, {}, {}).run
@@ -146,13 +196,14 @@ module Contentful
146
196
  end
147
197
 
148
198
  # Unarchives an entry.
149
- # Returns a Contentful::Management::Entry.
199
+ #
200
+ # @return [Contentful::Management::Entry]
150
201
  def unarchive
151
202
  request = Request.new(
152
- "/#{ space.id }/entries/#{ id }/archived",
153
- {},
154
- id = nil,
155
- version: sys[:version]
203
+ "/#{space.id}/entries/#{id}/archived",
204
+ {},
205
+ nil,
206
+ version: sys[:version]
156
207
  )
157
208
  response = request.delete
158
209
  result = ResourceBuilder.new(response, {}, {}).run
@@ -160,9 +211,10 @@ module Contentful
160
211
  end
161
212
 
162
213
  # Destroys an entry.
163
- # Returns true if succeed.
214
+ #
215
+ # @return [true, Contentful::Management::Error] success
164
216
  def destroy
165
- request = Request.new("/#{ space.id }/entries/#{ id }")
217
+ request = Request.new("/#{space.id}/entries/#{id}")
166
218
  response = request.delete
167
219
  if response.status == :no_content
168
220
  return true
@@ -173,24 +225,30 @@ module Contentful
173
225
  end
174
226
 
175
227
  # Checks if an entry is published.
176
- # Returns true if published.
228
+ #
229
+ # @return [Boolean]
177
230
  def published?
178
231
  sys[:publishedAt] ? true : false
179
232
  end
180
233
 
181
234
  # Checks if an entry is archived.
182
- # Returns true if published.
235
+ #
236
+ # @return [Boolean]
183
237
  def archived?
184
238
  sys[:archivedAt] ? true : false
185
239
  end
186
240
 
187
241
  # Returns the currently supported local.
242
+ #
243
+ # @return [String] current_locale
188
244
  def locale
189
245
  sys[:locale] || default_locale
190
246
  end
191
247
 
192
- # Parser for assets attributes from query.
248
+ # Parser for entry attributes from query.
193
249
  # Returns a hash of existing fields.
250
+ #
251
+ # @private
194
252
  def fields_for_query
195
253
  raw_fields = instance_variable_get(:@fields)
196
254
  fields_names = flatten_field_names(raw_fields)
@@ -201,14 +259,16 @@ module Contentful
201
259
  end
202
260
  end
203
261
 
262
+ # @private
204
263
  def flatten_field_names(fields)
205
- without_locales = fields.map { |k,v| v }
264
+ without_locales = fields.map { |_, v| v }
206
265
  without_locales.map(&:keys).flatten.uniq
207
266
  end
208
267
 
268
+ # @private
209
269
  def fields_from_attributes(attributes)
210
270
  attributes.each do |id, value|
211
- attributes[id] = {locale => parse_update_attribute(value)}
271
+ attributes[id] = { locale => parse_update_attribute(value) }
212
272
  end
213
273
  end
214
274
 
@@ -216,29 +276,29 @@ module Contentful
216
276
 
217
277
  def self.parse_attribute_with_field(attribute, field)
218
278
  case field.type
219
- when ContentType::LINK then
220
- {sys: {type: field.type, linkType: field.link_type, id: attribute.id}} if attribute
221
- when ContentType::ARRAY then
222
- parse_fields_array(attribute)
223
- when ContentType::LOCATION then
224
- {lat: attribute.properties[:lat], lon: attribute.properties[:lon]} if attribute
225
- else
226
- attribute
279
+ when ContentType::LINK then
280
+ { sys: { type: field.type, linkType: field.link_type, id: attribute.id } } if attribute
281
+ when ContentType::ARRAY then
282
+ parse_fields_array(attribute)
283
+ when ContentType::LOCATION then
284
+ { lat: attribute.properties[:lat], lon: attribute.properties[:lon] } if attribute
285
+ else
286
+ attribute
227
287
  end
228
288
  end
229
289
 
230
290
  def parse_update_attribute(attribute)
231
291
  case attribute
232
- when Asset
233
- self.class.hash_with_link_object('Asset', attribute)
234
- when Entry
235
- self.class.hash_with_link_object('Entry', attribute)
236
- when Location
237
- {lat: attribute.properties[:lat], lon: attribute.properties[:lon]}
238
- when ::Array
239
- self.class.parse_fields_array(attribute)
240
- else
241
- attribute
292
+ when Asset
293
+ self.class.hash_with_link_object('Asset', attribute)
294
+ when Entry
295
+ self.class.hash_with_link_object('Entry', attribute)
296
+ when Location
297
+ { lat: attribute.properties[:lat], lon: attribute.properties[:lon] }
298
+ when ::Array
299
+ self.class.parse_fields_array(attribute)
300
+ else
301
+ attribute
242
302
  end
243
303
  end
244
304
 
@@ -248,12 +308,10 @@ module Contentful
248
308
 
249
309
  Contentful::Management::Resource::FieldAware.create_fields_for_content_type(self)
250
310
 
251
- if self.respond_to? name
252
- return self.send(name, *args, &block)
253
- end
311
+ return send(name, *args, &block) if respond_to? name
254
312
  end
255
313
 
256
- raise NameError.new("undefined local variable or method `#{name}' for #{self.class}:#{self.sys[:id]}", name)
314
+ fail NameError.new("undefined local variable or method `#{name}' for #{self.class}:#{sys[:id]}", name)
257
315
  end
258
316
 
259
317
  def fetch_content_type
@@ -261,7 +319,7 @@ module Contentful
261
319
  end
262
320
 
263
321
  def self.hash_with_link_object(type, attribute)
264
- {sys: {type: 'Link', linkType: type, id: attribute.id}}
322
+ { sys: { type: 'Link', linkType: type, id: attribute.id } }
265
323
  end
266
324
 
267
325
  def self.parse_fields_array(attributes)
@@ -272,12 +330,12 @@ module Contentful
272
330
  def self.parse_objects_array(attributes)
273
331
  attributes.each_with_object([]) do |attr, arr|
274
332
  arr << case attr
275
- when Entry then
276
- hash_with_link_object('Entry', attr)
277
- when Asset then
278
- hash_with_link_object('Asset', attr)
279
- when Hash then
280
- attr
333
+ when Entry then
334
+ hash_with_link_object('Entry', attr)
335
+ when Asset then
336
+ hash_with_link_object('Asset', attr)
337
+ when Hash then
338
+ attr
281
339
  end
282
340
  end
283
341
  end
@@ -289,8 +347,8 @@ module Contentful
289
347
  attributes.keep_if { |key| field_names.include?(key) }
290
348
 
291
349
  attributes.each do |id, value|
292
- field = fields.select { |field| field.id.to_sym == id.to_sym }.first
293
- attributes[id] = {locale => parse_attribute_with_field(value, field)}
350
+ field = fields.detect { |f| f.id.to_sym == id.to_sym }
351
+ attributes[id] = { locale => parse_attribute_with_field(value, field) }
294
352
  end
295
353
  end
296
354
  end