Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle a heterogeneous collection via serializers option #410

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/fast_jsonapi/object_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ def hash_for_one_record

return serializable_hash unless @resource

serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params)
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
serializer_class = serializer_for(@resource)
serializable_hash[:data] = serializer_class.record_hash(@resource, @fieldsets[serializer_class.record_type.to_sym], @params)
serializable_hash[:included] = serializer_class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
serializable_hash
end

Expand All @@ -53,10 +54,11 @@ def hash_for_collection

data = []
included = []
fieldset = @fieldsets[self.class.record_type.to_sym]
@resource.each do |record|
data << self.class.record_hash(record, fieldset, @params)
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
serializer_class = serializer_for(record)
fieldset = @fieldsets[serializer_class.record_type.to_sym]
data << serializer_class.record_hash(record, fieldset, @params)
included.concat serializer_class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
end

serializable_hash[:data] = data
Expand Down Expand Up @@ -89,6 +91,11 @@ def process_options(options)
@includes = options[:include].reject(&:blank?).map(&:to_sym)
self.class.validate_includes!(@includes)
end

@serializers = options.fetch(:serializers, {}).transform_keys do |key|
next key if key.is_a?(Class)
key.to_s.constantize
end
end

def deep_symbolize(collection)
Expand All @@ -109,6 +116,11 @@ def is_collection?(resource, force_is_collection = nil)
resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
end

def serializer_for(record)
return self.class if @serializers.blank?
@serializers[record.class] || raise(ArgumentError, "no serializer defined for #{record.class}")
end

class_methods do

def inherited(subclass)
Expand Down
114 changes: 114 additions & 0 deletions spec/lib/object_serializer_heterogeneous_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
require 'spec_helper'

describe FastJsonapi::ObjectSerializer do
class Vehicle
attr_accessor :id, :model, :year

def type
self.class.name.downcase
end
end

class Car < Vehicle
attr_accessor :purchased_at
end

class Bus < Vehicle
attr_accessor :passenger_count
end

class Truck < Vehicle
attr_accessor :load
end

class VehicleSerializer
include FastJsonapi::ObjectSerializer
attributes :model, :year
end

class CarSerializer < VehicleSerializer
attribute :purchased_at
end

class BusSerializer < VehicleSerializer
attribute :passenger_count
end

let(:car) do
car = Car.new
car.id = 1
car.model = 'Toyota Corolla'
car.year = 1987
car.purchased_at = Time.new(2018, 1, 1)
car
end

let(:bus) do
bus = Bus.new
bus.id = 2
bus.model = 'Nova Bus LFS'
bus.year = 2014
bus.passenger_count = 60
bus
end

let(:truck) do
truck = Truck.new
truck.id = 3
truck.model = 'Ford F150'
truck.year = 2000
truck
end

context 'when serializing a heterogenous collection' do
it 'should use the correct serializer for each item' do
vehicles = VehicleSerializer.new([car, bus], serializers: { Car: CarSerializer, Bus: BusSerializer }).to_hash
car, bus = vehicles[:data]

expect(car[:type]).to eq(:car)
expect(car[:attributes]).to eq(model: 'Toyota Corolla', year: 1987, purchased_at: Time.new(2018, 1, 1))

expect(bus[:type]).to eq(:bus)
expect(bus[:attributes]).to eq(model: 'Nova Bus LFS', year: 2014, passenger_count: 60)
end

context 'if there is no serializer given for the class' do
it 'should raise ArgumentError' do
expect { VehicleSerializer.new([truck], serializers: { Car: CarSerializer, Bus: BusSerializer }).to_hash }
.to raise_error(ArgumentError, 'no serializer defined for Truck')
end
end

context 'when given an empty set of serializers' do
it 'should use the serializer being called' do
data = VehicleSerializer.new([truck], serializers: {}).to_hash[:data][0]
expect(data[:type]).to eq(:vehicle)
expect(data[:attributes]).to eq(model: 'Ford F150', year: 2000)
end
end
end

context 'when serializing an arbitrary object' do
it 'should use the correct serializer' do
data = VehicleSerializer.new(car, serializers: { Car: CarSerializer, Bus: BusSerializer }).to_hash[:data]

expect(data[:type]).to eq(:car)
expect(data[:attributes]).to eq(model: 'Toyota Corolla', year: 1987, purchased_at: Time.new(2018, 1, 1))
end

context 'if there is no serializer given for the class' do
it 'should raise ArgumentError' do
expect { VehicleSerializer.new(truck, serializers: { Car: CarSerializer, Bus: BusSerializer }).to_hash }
.to raise_error(ArgumentError, 'no serializer defined for Truck')
end
end

context 'when given an empty set of serializers' do
it 'should use the serializer being called' do
data = VehicleSerializer.new(truck, serializers: {}).to_hash[:data]
expect(data[:type]).to eq(:vehicle)
expect(data[:attributes]).to eq(model: 'Ford F150', year: 2000)
end
end
end
end