Skip to content

Commit 8ad3491

Browse files
committed
Merge pull request #288 from cerebris/rebase/274
Rebase/274 Add support for creating resources with polymorphic has one.
2 parents 0738497 + 62dff6b commit 8ad3491

10 files changed

Lines changed: 309 additions & 94 deletions

File tree

README.md

Lines changed: 63 additions & 63 deletions
Large diffs are not rendered by default.

lib/jsonapi/acts_as_resource_controller.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ def ensure_correct_media_type
8686
end
8787

8888
def setup_request
89-
@request = JSONAPI::Request.new(params, context: context,
90-
key_formatter: key_formatter)
89+
@request = JSONAPI::Request.new(params, context: context, key_formatter: key_formatter)
9190
render_errors(@request.errors) unless @request.errors.empty?
9291
rescue => e
9392
handle_exceptions(e)

lib/jsonapi/request.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,7 @@ def parse_params(params, allowed_fields)
392392
when 'relationships'
393393
value.each do |link_key, link_value|
394394
param = unformat_key(link_key)
395-
396395
association = @resource_klass._association(param)
397-
398396
if association.is_a?(JSONAPI::Association::HasOne)
399397
if link_value.nil?
400398
linkage = nil
@@ -404,12 +402,17 @@ def parse_params(params, allowed_fields)
404402

405403
links_object = parse_has_one_links_object(linkage)
406404
if !association.polymorphic? && links_object[:type] && (links_object[:type].to_s != association.type.to_s)
407-
raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
405+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
408406
end
409407

410408
unless links_object[:id].nil?
411409
association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
412-
checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
410+
association_id = association_resource.verify_key(links_object[:id], @context)
411+
if association.polymorphic?
412+
checked_has_one_associations[param] = { id: association_id, type: unformat_key(links_object[:type].to_s) }
413+
else
414+
checked_has_one_associations[param] = association_id
415+
end
413416
else
414417
checked_has_one_associations[param] = nil
415418
end
@@ -426,12 +429,12 @@ def parse_params(params, allowed_fields)
426429

427430
# Since we do not yet support polymorphic associations we will raise an error if the type does not match the
428431
# association's type.
429-
# TODO: Support Polymorphic associations
432+
# ToDo: Support Polymorphic associations
430433

431434
if links_object.length == 0
432435
checked_has_many_associations[param] = []
433436
else
434-
if links_object.length > 1 || !links_object.key?(unformat_key(association.type).to_s)
437+
if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
435438
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
436439
end
437440

@@ -519,19 +522,19 @@ def parse_update_association_operation(data, association_type, parent_key)
519522
association = resource_klass._association(association_type)
520523
if association.is_a?(JSONAPI::Association::HasOne)
521524
if association.polymorphic?
522-
object_params = {relationships: {format_key(association.name) => {data: data}}}
525+
object_params = { relationships: { format_key(association.name) => { data: data } } }
523526
verified_param_set = parse_params(object_params, updatable_fields)
524527

525528
@operations.push JSONAPI::ReplacePolymorphicHasOneAssociationOperation.new(
526529
resource_klass,
527530
context: @context,
528531
resource_id: parent_key,
529532
association_type: association_type,
530-
key_value: verified_param_set[:has_one].values[0],
531-
key_type: data['type']
533+
key_value: verified_param_set[:has_one].values[0][:id],
534+
key_type: verified_param_set[:has_one].values[0][:type]
532535
)
533536
else
534-
object_params = {relationships: {format_key(association.name) => {data: data}}}
537+
object_params = { relationships: { format_key(association.name) => { data: data } } }
535538
verified_param_set = parse_params(object_params, updatable_fields)
536539

537540
@operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
@@ -542,7 +545,7 @@ def parse_update_association_operation(data, association_type, parent_key)
542545
key_value: verified_param_set[:has_one].values[0]
543546
)
544547
end
545-
else
548+
elsif association.is_a?(JSONAPI::Association::HasMany)
546549
unless association.acts_as_set
547550
fail JSONAPI::Exceptions::HasManySetReplacementForbidden.new
548551
end

lib/jsonapi/resource.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ def _replace_has_one_link(association_type, association_key_value)
196196
end
197197

198198
def _replace_polymorphic_has_one_link(association_type, key_value, key_type)
199-
association = self.class._associations[association_type]
199+
association = self.class._associations[association_type.to_sym]
200200

201-
send("#{association.foreign_key}=", key_value)
202-
send("#{association.polymorphic_type}=", key_type.singularize.capitalize)
201+
model.send("#{association.foreign_key}=", key_value)
202+
model.send("#{association.polymorphic_type}=", key_type.to_s.classify)
203203

204204
@save_needed = true
205205

@@ -239,7 +239,12 @@ def _replace_fields(field_data)
239239
if value.nil?
240240
remove_has_one_link(association_type)
241241
else
242-
replace_has_one_link(association_type, value)
242+
case value
243+
when Hash
244+
replace_polymorphic_has_one_link(association_type.to_s, value.fetch(:id), value.fetch(:type))
245+
else
246+
replace_has_one_link(association_type, value)
247+
end
243248
end
244249
end if field_data[:has_one]
245250

@@ -714,6 +719,9 @@ def _associate(klass, *attrs)
714719
end
715720

716721
return records.collect do |record|
722+
if association.polymorphic?
723+
resource_klass = Resource.resource_for(self.class.module_path + record.class.to_s.underscore)
724+
end
717725
resource_klass.new(record, @context)
718726
end
719727
end unless method_defined?(attr)

lib/jsonapi/resource_serializer.rb

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module JSONAPI
22
class ResourceSerializer
3+
34
# Options can include
45
# include:
56
# Purpose: determines which objects will be side loaded with the source objects in a linked section
@@ -232,6 +233,7 @@ def related_link(source, association)
232233
def has_one_linkage(source, association)
233234
linkage = {}
234235
linkage_id = foreign_key_value(source, association)
236+
235237
if linkage_id
236238
linkage[:type] = format_key(association.type_for_source(source))
237239
linkage[:id] = linkage_id
@@ -243,9 +245,10 @@ def has_one_linkage(source, association)
243245

244246
def has_many_linkage(source, association)
245247
linkage = []
246-
linkage_ids = foreign_key_value(source, association)
247-
linkage_ids.each do |linkage_id|
248-
linkage.append(type: format_key(association.type), id: linkage_id)
248+
linkage_types_and_values = foreign_key_types_and_values(source, association)
249+
250+
linkage_types_and_values.each do |type, value|
251+
linkage.append({type: format_key(type), id: value})
249252
end
250253
linkage
251254
end
@@ -276,15 +279,24 @@ def link_object(source, association, include_linkage = false)
276279
end
277280
end
278281

279-
# Extracts the foreign key value for an association.
282+
# Extracts the foreign key value for a has_one association.
280283
def foreign_key_value(source, association)
281284
foreign_key = association.foreign_key
282285
value = source.send(foreign_key)
286+
IdValueFormatter.format(value)
287+
end
283288

289+
def foreign_key_types_and_values(source, association)
284290
if association.is_a?(JSONAPI::Association::HasMany)
285-
value.map { |value| IdValueFormatter.format(value) }
286-
elsif association.is_a?(JSONAPI::Association::HasOne)
287-
IdValueFormatter.format(value)
291+
if association.polymorphic?
292+
source.model.send(association.name).pluck(:type, :id).map do |type, id|
293+
[type.pluralize, IdValueFormatter.format(id)]
294+
end
295+
else
296+
source.send(association.foreign_key).map do |value|
297+
[association.type, IdValueFormatter.format(value)]
298+
end
299+
end
288300
end
289301
end
290302

test/controllers/controller_test.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1944,7 +1944,13 @@ def test_get_related_resource
19441944
"related" => "http://test.host/people/1/hair_cut"
19451945
},
19461946
"data" => nil
1947-
}
1947+
},
1948+
vehicles: {
1949+
links: {
1950+
self: "http://test.host/people/1/relationships/vehicles",
1951+
related: "http://test.host/people/1/vehicles"
1952+
}
1953+
}
19481954
}
19491955
}
19501956
},

test/fixtures/active_record.rb

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,19 @@
187187
t.string :name
188188
t.timestamps null: false
189189
end
190+
191+
create_table :vehicles, force: true do |t|
192+
t.string :type
193+
t.integer :person_id
194+
end
190195
end
191196

192197
### MODELS
193198
class Person < ActiveRecord::Base
194199
has_many :posts, foreign_key: 'author_id'
195200
has_many :comments, foreign_key: 'author_id'
196201
has_many :expense_entries, foreign_key: 'employee_id', dependent: :restrict_with_exception
202+
has_many :vehicles
197203
belongs_to :preferences
198204
belongs_to :hair_cut
199205

@@ -368,6 +374,16 @@ class Picture < ActiveRecord::Base
368374
belongs_to :imageable, polymorphic: true
369375
end
370376

377+
class Vehicle < ActiveRecord::Base
378+
belongs_to :person
379+
end
380+
381+
class Car < Vehicle
382+
end
383+
384+
class Boat < Vehicle
385+
end
386+
371387
class Document < ActiveRecord::Base
372388
has_many :pictures, as: :imageable
373389
end
@@ -576,6 +592,7 @@ class PersonResource < JSONAPI::Resource
576592

577593
has_many :comments
578594
has_many :posts
595+
has_many :vehicles, polymorphic: true
579596

580597
has_one :preferences
581598
has_one :hair_cut
@@ -595,6 +612,16 @@ def self.verify_custom_filter(filter, values, context)
595612
end
596613
end
597614

615+
class VehicleResource < JSONAPI::Resource
616+
has_one :person
617+
end
618+
619+
class CarResource < VehicleResource
620+
end
621+
622+
class BoatResource < VehicleResource
623+
end
624+
598625
class CommentResource < JSONAPI::Resource
599626
attributes :body
600627
has_one :post
@@ -825,10 +852,6 @@ class CategoryResource < JSONAPI::Resource
825852
class PictureResource < JSONAPI::Resource
826853
attribute :name
827854
has_one :imageable, polymorphic: true
828-
829-
def imageable_type=(type)
830-
model.imageable_type = type
831-
end
832855
end
833856

834857
class DocumentResource < JSONAPI::Resource
@@ -892,6 +915,9 @@ def subject
892915
EmployeeResource = EmployeeResource.dup
893916
FriendResource = FriendResource.dup
894917
HairCutResource = HairCutResource.dup
918+
VehicleResource = VehicleResource.dup
919+
CarResource = CarResource.dup
920+
BoatResource = BoatResource.dup
895921
end
896922
end
897923

test/fixtures/vehicles.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
car:
2+
id: 1
3+
type: Car
4+
person_id: 1
5+
boat:
6+
id: 2
7+
type: Boat
8+
person_id: 1

0 commit comments

Comments
 (0)