Merge branch 'upstream-dev' into add-links-option-to-relationship-serializer
This commit is contained in:
commit
1ad20d6b7b
@ -221,7 +221,7 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Links Per Object
|
### Links Per Object
|
||||||
Links are defined in FastJsonapi using the `link` method. By default, link are read directly from the model property of the same name.In this example, `public_url` is expected to be a property of the object being serialized.
|
Links are defined in FastJsonapi using the `link` method. By default, link are read directly from the model property of the same name. In this example, `public_url` is expected to be a property of the object being serialized.
|
||||||
|
|
||||||
You can configure the method to use on the object for example a link with key `self` will get set to the value returned by a method called `url` on the movie object.
|
You can configure the method to use on the object for example a link with key `self` will get set to the value returned by a method called `url` on the movie object.
|
||||||
|
|
||||||
@ -449,6 +449,7 @@ serializer.serializable_hash
|
|||||||
Option | Purpose | Example
|
Option | Purpose | Example
|
||||||
------------ | ------------- | -------------
|
------------ | ------------- | -------------
|
||||||
set_type | Type name of Object | ```set_type :movie ```
|
set_type | Type name of Object | ```set_type :movie ```
|
||||||
|
key | Key of Object | ```belongs_to :owner, key: :user ```
|
||||||
set_id | ID of Object | ```set_id :owner_id ```
|
set_id | ID of Object | ```set_id :owner_id ```
|
||||||
cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours, race_condition_ttl: 10.seconds```
|
cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours, race_condition_ttl: 10.seconds```
|
||||||
id_method_name | Set custom method name to get ID of an object | ```has_many :locations, id_method_name: :place_ids ```
|
id_method_name | Set custom method name to get ID of an object | ```has_many :locations, id_method_name: :place_ids ```
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'active_support/core_ext/object'
|
require 'active_support/json'
|
||||||
require 'active_support/concern'
|
require 'active_support/concern'
|
||||||
require 'active_support/inflector'
|
require 'active_support/inflector'
|
||||||
require 'fast_jsonapi/attribute'
|
require 'fast_jsonapi/attribute'
|
||||||
@ -65,7 +65,7 @@ module FastJsonapi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def serialized_json
|
def serialized_json
|
||||||
self.class.to_json(serializable_hash)
|
ActiveSupport::JSON.encode(serializable_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -143,7 +143,11 @@ module FastJsonapi
|
|||||||
self.transform_method = mapping[transform_name.to_sym]
|
self.transform_method = mapping[transform_name.to_sym]
|
||||||
|
|
||||||
# ensure that the record type is correctly transformed
|
# ensure that the record type is correctly transformed
|
||||||
set_type(reflected_record_type) if reflected_record_type
|
if record_type
|
||||||
|
set_type(record_type)
|
||||||
|
elsif reflected_record_type
|
||||||
|
set_type(reflected_record_type)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_key_transform(input)
|
def run_key_transform(input)
|
||||||
@ -250,6 +254,7 @@ module FastJsonapi
|
|||||||
cached: options[:cached],
|
cached: options[:cached],
|
||||||
polymorphic: fetch_polymorphic_option(options),
|
polymorphic: fetch_polymorphic_option(options),
|
||||||
conditional_proc: options[:if],
|
conditional_proc: options[:if],
|
||||||
|
transform_method: @transform_method,
|
||||||
links: options[:links],
|
links: options[:links],
|
||||||
lazy_load_data: options[:lazy_load_data]
|
lazy_load_data: options[:lazy_load_data]
|
||||||
)
|
)
|
||||||
@ -294,10 +299,10 @@ module FastJsonapi
|
|||||||
includes.detect do |include_item|
|
includes.detect do |include_item|
|
||||||
klass = self
|
klass = self
|
||||||
parse_include_item(include_item).each do |parsed_include|
|
parse_include_item(include_item).each do |parsed_include|
|
||||||
relationship_to_include = klass.relationships_to_serialize[parsed_include]
|
relationships_to_serialize = klass.relationships_to_serialize || {}
|
||||||
|
relationship_to_include = relationships_to_serialize[parsed_include]
|
||||||
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
|
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
|
||||||
raise NotImplementedError if relationship_to_include.polymorphic.is_a?(Hash)
|
klass = relationship_to_include.serializer.to_s.constantize unless relationship_to_include.polymorphic.is_a?(Hash)
|
||||||
klass = relationship_to_include.serializer.to_s.constantize
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
module FastJsonapi
|
module FastJsonapi
|
||||||
class Relationship
|
class Relationship
|
||||||
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :links, :lazy_load_data
|
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
key:,
|
key:,
|
||||||
@ -14,6 +14,7 @@ module FastJsonapi
|
|||||||
cached: false,
|
cached: false,
|
||||||
polymorphic:,
|
polymorphic:,
|
||||||
conditional_proc:,
|
conditional_proc:,
|
||||||
|
transform_method:,
|
||||||
links:,
|
links:,
|
||||||
lazy_load_data: false
|
lazy_load_data: false
|
||||||
)
|
)
|
||||||
@ -28,6 +29,7 @@ module FastJsonapi
|
|||||||
@cached = cached
|
@cached = cached
|
||||||
@polymorphic = polymorphic
|
@polymorphic = polymorphic
|
||||||
@conditional_proc = conditional_proc
|
@conditional_proc = conditional_proc
|
||||||
|
@transform_method = transform_method
|
||||||
@links = links || {}
|
@links = links || {}
|
||||||
@lazy_load_data = lazy_load_data
|
@lazy_load_data = lazy_load_data
|
||||||
end
|
end
|
||||||
@ -75,7 +77,7 @@ module FastJsonapi
|
|||||||
|
|
||||||
def id_hash_from_record(record, record_types)
|
def id_hash_from_record(record, record_types)
|
||||||
# memoize the record type within the record_types dictionary, then assigning to record_type:
|
# memoize the record type within the record_types dictionary, then assigning to record_type:
|
||||||
associated_record_type = record_types[record.class] ||= record.class.name.underscore.to_sym
|
associated_record_type = record_types[record.class] ||= run_key_transform(record.class.name.demodulize.underscore)
|
||||||
id_hash(record.id, associated_record_type)
|
id_hash(record.id, associated_record_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -103,7 +105,15 @@ module FastJsonapi
|
|||||||
|
|
||||||
def add_links_hash(record, params, output_hash)
|
def add_links_hash(record, params, output_hash)
|
||||||
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
|
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
|
||||||
Link.new(key: key, method: method).serialize(record, params, hash)
|
Link.new(key: key, method: method).serialize(record, params, hash)\
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_key_transform(input)
|
||||||
|
if self.transform_method.present?
|
||||||
|
input.to_s.send(*self.transform_method).to_sym
|
||||||
|
else
|
||||||
|
input.to_sym
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -119,9 +119,10 @@ module FastJsonapi
|
|||||||
next unless relationships_to_serialize && relationships_to_serialize[item]
|
next unless relationships_to_serialize && relationships_to_serialize[item]
|
||||||
relationship_item = relationships_to_serialize[item]
|
relationship_item = relationships_to_serialize[item]
|
||||||
next unless relationship_item.include_relationship?(record, params)
|
next unless relationship_item.include_relationship?(record, params)
|
||||||
raise NotImplementedError if relationship_item.polymorphic.is_a?(Hash)
|
unless relationship_item.polymorphic.is_a?(Hash)
|
||||||
record_type = relationship_item.record_type
|
record_type = relationship_item.record_type
|
||||||
serializer = relationship_item.serializer.to_s.constantize
|
serializer = relationship_item.serializer.to_s.constantize
|
||||||
|
end
|
||||||
relationship_type = relationship_item.relationship_type
|
relationship_type = relationship_item.relationship_type
|
||||||
|
|
||||||
included_objects = relationship_item.fetch_associated_object(record, params)
|
included_objects = relationship_item.fetch_associated_object(record, params)
|
||||||
@ -129,12 +130,17 @@ module FastJsonapi
|
|||||||
included_objects = [included_objects] unless relationship_type == :has_many
|
included_objects = [included_objects] unless relationship_type == :has_many
|
||||||
|
|
||||||
included_objects.each do |inc_obj|
|
included_objects.each do |inc_obj|
|
||||||
|
if relationship_item.polymorphic.is_a?(Hash)
|
||||||
|
record_type = inc_obj.class.name.demodulize.underscore
|
||||||
|
serializer = self.compute_serializer_name(inc_obj.class.name.demodulize.to_sym).to_s.constantize
|
||||||
|
end
|
||||||
|
|
||||||
if remaining_items(items)
|
if remaining_items(items)
|
||||||
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params)
|
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params)
|
||||||
included_records.concat(serializer_records) unless serializer_records.empty?
|
included_records.concat(serializer_records) unless serializer_records.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
code = "#{record_type}_#{inc_obj.id}"
|
code = "#{record_type}_#{serializer.id_from_record(inc_obj)}"
|
||||||
next if known_included_objects.key?(code)
|
next if known_included_objects.key?(code)
|
||||||
|
|
||||||
known_included_objects[code] = inc_obj
|
known_included_objects[code] = inc_obj
|
||||||
|
|||||||
@ -411,4 +411,34 @@ describe FastJsonapi::ObjectSerializer do
|
|||||||
it_behaves_like 'returning key transformed hash', :movie_type, :underscore_movie_type, :release_year
|
it_behaves_like 'returning key transformed hash', :movie_type, :underscore_movie_type, :release_year
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#set_key_transform after #set_type' do
|
||||||
|
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
|
||||||
|
|
||||||
|
before do
|
||||||
|
MovieSerializer.set_type type_name
|
||||||
|
MovieSerializer.set_key_transform :camel
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
MovieSerializer.transform_method = nil
|
||||||
|
MovieSerializer.set_type :movie
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when sets singular type name' do
|
||||||
|
let(:type_name) { :film }
|
||||||
|
|
||||||
|
it 'returns correct hash which type equals transformed set_type value' do
|
||||||
|
expect(serializable_hash[:data][:type]).to eq :Film
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when sets plural type name' do
|
||||||
|
let(:type_name) { :films }
|
||||||
|
|
||||||
|
it 'returns correct hash which type equals transformed set_type value' do
|
||||||
|
expect(serializable_hash[:data][:type]).to eq :Films
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
51
spec/lib/object_serializer_polymorphic_spec.rb
Normal file
51
spec/lib/object_serializer_polymorphic_spec.rb
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe FastJsonapi::ObjectSerializer do
|
||||||
|
class List
|
||||||
|
attr_accessor :id, :name, :items
|
||||||
|
end
|
||||||
|
|
||||||
|
class ChecklistItem
|
||||||
|
attr_accessor :id, :name
|
||||||
|
end
|
||||||
|
|
||||||
|
class Car
|
||||||
|
attr_accessor :id, :model, :year
|
||||||
|
end
|
||||||
|
|
||||||
|
class ListSerializer
|
||||||
|
include FastJsonapi::ObjectSerializer
|
||||||
|
set_type :list
|
||||||
|
attributes :name
|
||||||
|
set_key_transform :dash
|
||||||
|
has_many :items, polymorphic: true
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:car) do
|
||||||
|
car = Car.new
|
||||||
|
car.id = 1
|
||||||
|
car.model = 'Toyota Corolla'
|
||||||
|
car.year = 1987
|
||||||
|
car
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:checklist_item) do
|
||||||
|
checklist_item = ChecklistItem.new
|
||||||
|
checklist_item.id = 2
|
||||||
|
checklist_item.name = 'Do this action!'
|
||||||
|
checklist_item
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when serializing id and type of polymorphic relationships' do
|
||||||
|
it 'should return correct type when transform_method is specified' do
|
||||||
|
list = List.new
|
||||||
|
list.id = 1
|
||||||
|
list.items = [checklist_item, car]
|
||||||
|
list_hash = ListSerializer.new(list).to_hash
|
||||||
|
record_type = list_hash[:data][:relationships][:items][:data][0][:type]
|
||||||
|
expect(record_type).to eq 'checklist-item'.to_sym
|
||||||
|
record_type = list_hash[:data][:relationships][:items][:data][1][:type]
|
||||||
|
expect(record_type).to eq 'car'.to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -158,6 +158,32 @@ describe FastJsonapi::ObjectSerializer do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'id attribute is the same for actors and not a primary key' do
|
||||||
|
before do
|
||||||
|
ActorSerializer.set_id :email
|
||||||
|
movie.actor_ids = [0, 0, 0]
|
||||||
|
class << movie
|
||||||
|
def actors
|
||||||
|
super.each_with_index { |actor, i| actor.email = "actor#{i}@email.com" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
after { ActorSerializer.set_id nil }
|
||||||
|
|
||||||
|
let(:options) { { include: ['actors'] } }
|
||||||
|
subject { MovieSerializer.new(movie, options).serializable_hash }
|
||||||
|
|
||||||
|
it 'returns all actors in includes' do
|
||||||
|
|
||||||
|
expect(
|
||||||
|
subject[:included].select { |i| i[:type] == :actor }.map { |i| i[:id] }
|
||||||
|
).to eq(
|
||||||
|
movie.actors.map(&:email)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'nested includes' do
|
context 'nested includes' do
|
||||||
it 'has_many to belongs_to: returns correct nested includes when serializable_hash is called' do
|
it 'has_many to belongs_to: returns correct nested includes when serializable_hash is called' do
|
||||||
# 3 actors, 3 agencies
|
# 3 actors, 3 agencies
|
||||||
@ -252,10 +278,24 @@ describe FastJsonapi::ObjectSerializer do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'polymorphic throws an error that polymorphic is not supported' do
|
it 'polymorphic has_many: returns correct nested includes when serializable_hash is called' do
|
||||||
options = {}
|
options = {}
|
||||||
options[:include] = [:groupees]
|
options[:include] = [:groupees]
|
||||||
expect(-> { GroupSerializer.new([group], options)}).to raise_error(NotImplementedError)
|
|
||||||
|
serializable_hash = GroupSerializer.new([group], options).serializable_hash
|
||||||
|
|
||||||
|
persons_serialized = serializable_hash[:included].find_all { |included| included[:type] == :person }.map { |included| included[:id].to_i }
|
||||||
|
groups_serialized = serializable_hash[:included].find_all { |included| included[:type] == :group }.map { |included| included[:id].to_i }
|
||||||
|
|
||||||
|
persons = group.groupees.find_all { |groupee| groupee.is_a?(Person) }
|
||||||
|
persons.each do |person|
|
||||||
|
expect(persons_serialized).to include(person.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
groups = group.groupees.find_all { |groupee| groupee.is_a?(Group) }
|
||||||
|
groups.each do |group|
|
||||||
|
expect(groups_serialized).to include(group.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user