commit
49193ab8f3
69
README.md
69
README.md
@ -30,6 +30,8 @@ Fast JSON API serialized 250 records in 3.01 ms
|
||||
* [Collection Serialization](#collection-serialization)
|
||||
* [Caching](#caching)
|
||||
* [Params](#params)
|
||||
* [Conditional Attributes](#conditional-attributes)
|
||||
* [Conditional Relationships](#conditional-relationships)
|
||||
* [Contributing](#contributing)
|
||||
|
||||
|
||||
@ -259,6 +261,26 @@ hash = MovieSerializer.new([movie, movie], options).serializable_hash
|
||||
json_string = MovieSerializer.new([movie, movie], options).serialized_json
|
||||
```
|
||||
|
||||
#### Control Over Collection Serialization
|
||||
|
||||
You can use `is_collection` option to have better control over collection serialization.
|
||||
|
||||
If this option is not provided or `nil` autedetect logic is used to try understand
|
||||
if provided resource is a single object or collection.
|
||||
|
||||
Autodetect logic is compatible with most DB toolkits (ActiveRecord, Sequel, etc.) but
|
||||
**cannot** guarantee that single vs collection will be always detected properly.
|
||||
|
||||
```ruby
|
||||
options[:is_collection]
|
||||
```
|
||||
|
||||
was introduced to be able to have precise control this behavior
|
||||
|
||||
- `nil` or not provided: will try to autodetect single vs collection (please, see notes above)
|
||||
- `true` will always treat input resource as *collection*
|
||||
- `false` will always treat input resource as *single object*
|
||||
|
||||
### Caching
|
||||
Requires a `cache_key` method be defined on model:
|
||||
|
||||
@ -307,6 +329,53 @@ serializer.serializable_hash
|
||||
Custom attributes and relationships that only receive the resource are still possible by defining
|
||||
the block to only receive one argument.
|
||||
|
||||
### Conditional Attributes
|
||||
|
||||
Conditional attributes can be defined by passing a Proc to the `if` key on the `attribute` method. Return `true` if the attribute should be serialized, and `false` if not. The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.
|
||||
|
||||
```ruby
|
||||
class MovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
attributes :name, :year
|
||||
attribute :release_year, if: Proc.new do |record|
|
||||
# Release year will only be serialized if it's greater than 1990
|
||||
record.release_year > 1990
|
||||
end
|
||||
|
||||
attribute :director, if: Proc.new do |record, params|
|
||||
# The director will be serialized only if the :admin key of params is true
|
||||
params && params[:admin] == true
|
||||
end
|
||||
end
|
||||
|
||||
# ...
|
||||
current_user = User.find(cookies[:current_user_id])
|
||||
serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }})
|
||||
serializer.serializable_hash
|
||||
```
|
||||
|
||||
### Conditional Relationships
|
||||
|
||||
Conditional relationships can be defined by passing a Proc to the `if` key. Return `true` if the relationship should be serialized, and `false` if not. The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.
|
||||
|
||||
```ruby
|
||||
class MovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
# Actors will only be serialized if the record has any associated actors
|
||||
has_many :actors, if: Proc.new { |record| record.actors.any? }
|
||||
|
||||
# Owner will only be serialized if the :admin key of params is true
|
||||
belongs_to :owner, if: Proc.new { |record, params| params && params[:admin] == true }
|
||||
end
|
||||
|
||||
# ...
|
||||
current_user = User.find(cookies[:current_user_id])
|
||||
serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }})
|
||||
serializer.serializable_hash
|
||||
```
|
||||
|
||||
### Customizable Options
|
||||
|
||||
Option | Purpose | Example
|
||||
|
@ -1,20 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
if defined?(::ActiveRecord)
|
||||
::ActiveRecord::Associations::Builder::HasOne.class_eval do
|
||||
# Based on
|
||||
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
||||
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
|
||||
def self.define_accessors(mixin, reflection)
|
||||
super
|
||||
name = reflection.name
|
||||
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
||||
def #{name}_id
|
||||
# if an attribute is already defined with this methods name we should just use it
|
||||
return read_attribute(__method__) if has_attribute?(__method__)
|
||||
association(:#{name}).reader.try(:id)
|
||||
end
|
||||
CODE
|
||||
end
|
||||
::ActiveRecord::Associations::Builder::HasOne.class_eval do
|
||||
# Based on
|
||||
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
||||
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
|
||||
def self.define_accessors(mixin, reflection)
|
||||
super
|
||||
name = reflection.name
|
||||
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
||||
def #{name}_id
|
||||
# if an attribute is already defined with this methods name we should just use it
|
||||
return read_attribute(__method__) if has_attribute?(__method__)
|
||||
association(:#{name}).reader.try(:id)
|
||||
end
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
@ -2,5 +2,9 @@
|
||||
|
||||
module FastJsonapi
|
||||
require 'fast_jsonapi/object_serializer'
|
||||
require 'extensions/has_one'
|
||||
if defined?(::Rails)
|
||||
require 'fast_jsonapi/railtie'
|
||||
elsif defined?(::ActiveRecord)
|
||||
require 'extensions/has_one'
|
||||
end
|
||||
end
|
||||
|
29
lib/fast_jsonapi/attribute.rb
Normal file
29
lib/fast_jsonapi/attribute.rb
Normal file
@ -0,0 +1,29 @@
|
||||
module FastJsonapi
|
||||
class Attribute
|
||||
attr_reader :key, :method, :conditional_proc
|
||||
|
||||
def initialize(key:, method:, options: {})
|
||||
@key = key
|
||||
@method = method
|
||||
@conditional_proc = options[:if]
|
||||
end
|
||||
|
||||
def serialize(record, serialization_params, output_hash)
|
||||
if include_attribute?(record, serialization_params)
|
||||
output_hash[key] = if method.is_a?(Proc)
|
||||
method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
|
||||
else
|
||||
record.public_send(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def include_attribute?(record, serialization_params)
|
||||
if conditional_proc.present?
|
||||
conditional_proc.call(record, serialization_params)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/fast_jsonapi/link.rb
Normal file
18
lib/fast_jsonapi/link.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module FastJsonapi
|
||||
class Link
|
||||
attr_reader :key, :method
|
||||
|
||||
def initialize(key:, method:)
|
||||
@key = key
|
||||
@method = method
|
||||
end
|
||||
|
||||
def serialize(record, serialization_params, output_hash)
|
||||
output_hash[key] = if method.is_a?(Proc)
|
||||
method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
|
||||
else
|
||||
record.public_send(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -3,6 +3,9 @@
|
||||
require 'active_support/core_ext/object'
|
||||
require 'active_support/concern'
|
||||
require 'active_support/inflector'
|
||||
require 'fast_jsonapi/attribute'
|
||||
require 'fast_jsonapi/relationship'
|
||||
require 'fast_jsonapi/link'
|
||||
require 'fast_jsonapi/serialization_core'
|
||||
|
||||
module FastJsonapi
|
||||
@ -25,7 +28,7 @@ module FastJsonapi
|
||||
end
|
||||
|
||||
def serializable_hash
|
||||
return hash_for_collection if is_collection?(@resource)
|
||||
return hash_for_collection if is_collection?(@resource, @is_collection)
|
||||
|
||||
hash_for_one_record
|
||||
end
|
||||
@ -72,6 +75,7 @@ module FastJsonapi
|
||||
@known_included_objects = {}
|
||||
@meta = options[:meta]
|
||||
@links = options[:links]
|
||||
@is_collection = options[:is_collection]
|
||||
@params = options[:params] || {}
|
||||
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
|
||||
|
||||
@ -81,8 +85,10 @@ module FastJsonapi
|
||||
end
|
||||
end
|
||||
|
||||
def is_collection?(resource)
|
||||
resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
|
||||
def is_collection?(resource, force_is_collection = nil)
|
||||
return force_is_collection unless force_is_collection.nil?
|
||||
|
||||
resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
|
||||
end
|
||||
|
||||
class_methods do
|
||||
@ -118,6 +124,9 @@ module FastJsonapi
|
||||
underscore: :underscore
|
||||
}
|
||||
self.transform_method = mapping[transform_name.to_sym]
|
||||
|
||||
# ensure that the record type is correctly transformed
|
||||
set_type(reflected_record_type) if reflected_record_type
|
||||
end
|
||||
|
||||
def run_key_transform(input)
|
||||
@ -149,48 +158,51 @@ module FastJsonapi
|
||||
|
||||
def attributes(*attributes_list, &block)
|
||||
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
||||
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
||||
self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
|
||||
|
||||
attributes_list.each do |attr_name|
|
||||
method_name = attr_name
|
||||
key = run_key_transform(method_name)
|
||||
attributes_to_serialize[key] = block || method_name
|
||||
attributes_to_serialize[key] = Attribute.new(
|
||||
key: key,
|
||||
method: block || method_name,
|
||||
options: options
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :attribute, :attributes
|
||||
|
||||
def add_relationship(name, relationship)
|
||||
def add_relationship(relationship)
|
||||
self.relationships_to_serialize = {} if relationships_to_serialize.nil?
|
||||
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
||||
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
||||
|
||||
if !relationship[:cached]
|
||||
self.uncachable_relationships_to_serialize[name] = relationship
|
||||
|
||||
if !relationship.cached
|
||||
self.uncachable_relationships_to_serialize[relationship.name] = relationship
|
||||
else
|
||||
self.cachable_relationships_to_serialize[name] = relationship
|
||||
self.cachable_relationships_to_serialize[relationship.name] = relationship
|
||||
end
|
||||
self.relationships_to_serialize[name] = relationship
|
||||
end
|
||||
self.relationships_to_serialize[relationship.name] = relationship
|
||||
end
|
||||
|
||||
def has_many(relationship_name, options = {}, &block)
|
||||
name = relationship_name.to_sym
|
||||
hash = create_relationship_hash(relationship_name, :has_many, options, block)
|
||||
add_relationship(name, hash)
|
||||
relationship = create_relationship(relationship_name, :has_many, options, block)
|
||||
add_relationship(relationship)
|
||||
end
|
||||
|
||||
def has_one(relationship_name, options = {}, &block)
|
||||
name = relationship_name.to_sym
|
||||
hash = create_relationship_hash(relationship_name, :has_one, options, block)
|
||||
add_relationship(name, hash)
|
||||
relationship = create_relationship(relationship_name, :has_one, options, block)
|
||||
add_relationship(relationship)
|
||||
end
|
||||
|
||||
def belongs_to(relationship_name, options = {}, &block)
|
||||
name = relationship_name.to_sym
|
||||
hash = create_relationship_hash(relationship_name, :belongs_to, options, block)
|
||||
add_relationship(name, hash)
|
||||
relationship = create_relationship(relationship_name, :belongs_to, options, block)
|
||||
add_relationship(relationship)
|
||||
end
|
||||
|
||||
def create_relationship_hash(base_key, relationship_type, options, block)
|
||||
def create_relationship(base_key, relationship_type, options, block)
|
||||
name = base_key.to_sym
|
||||
if relationship_type == :has_many
|
||||
base_serialization_key = base_key.to_s.singularize
|
||||
@ -201,7 +213,7 @@ module FastJsonapi
|
||||
base_key_sym = name
|
||||
id_postfix = '_id'
|
||||
end
|
||||
{
|
||||
Relationship.new(
|
||||
key: options[:key] || run_key_transform(base_key),
|
||||
name: name,
|
||||
id_method_name: options[:id_method_name] || "#{base_serialization_key}#{id_postfix}".to_sym,
|
||||
@ -210,9 +222,10 @@ module FastJsonapi
|
||||
object_block: block,
|
||||
serializer: compute_serializer_name(options[:serializer] || base_key_sym),
|
||||
relationship_type: relationship_type,
|
||||
cached: options[:cached] || false,
|
||||
polymorphic: fetch_polymorphic_option(options)
|
||||
}
|
||||
cached: options[:cached],
|
||||
polymorphic: fetch_polymorphic_option(options),
|
||||
conditional_proc: options[:if]
|
||||
)
|
||||
end
|
||||
|
||||
def compute_serializer_name(serializer_key)
|
||||
@ -233,7 +246,11 @@ module FastJsonapi
|
||||
self.data_links = {} if self.data_links.nil?
|
||||
link_method_name = link_name if link_method_name.nil?
|
||||
key = run_key_transform(link_name)
|
||||
self.data_links[key] = block || link_method_name
|
||||
|
||||
self.data_links[key] = Link.new(
|
||||
key: key,
|
||||
method: block || link_method_name
|
||||
)
|
||||
end
|
||||
|
||||
def validate_includes!(includes)
|
||||
@ -244,8 +261,8 @@ module FastJsonapi
|
||||
parse_include_item(include_item).each do |parsed_include|
|
||||
relationship_to_include = klass.relationships_to_serialize[parsed_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
|
||||
raise NotImplementedError if relationship_to_include.polymorphic.is_a?(Hash)
|
||||
klass = relationship_to_include.serializer.to_s.constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
11
lib/fast_jsonapi/railtie.rb
Normal file
11
lib/fast_jsonapi/railtie.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails/railtie'
|
||||
|
||||
class Railtie < Rails::Railtie
|
||||
initializer 'fast_jsonapi.active_record' do
|
||||
ActiveSupport.on_load :active_record do
|
||||
require 'extensions/has_one'
|
||||
end
|
||||
end
|
||||
end
|
99
lib/fast_jsonapi/relationship.rb
Normal file
99
lib/fast_jsonapi/relationship.rb
Normal file
@ -0,0 +1,99 @@
|
||||
module FastJsonapi
|
||||
class Relationship
|
||||
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc
|
||||
|
||||
def initialize(
|
||||
key:,
|
||||
name:,
|
||||
id_method_name:,
|
||||
record_type:,
|
||||
object_method_name:,
|
||||
object_block:,
|
||||
serializer:,
|
||||
relationship_type:,
|
||||
cached: false,
|
||||
polymorphic:,
|
||||
conditional_proc:
|
||||
)
|
||||
@key = key
|
||||
@name = name
|
||||
@id_method_name = id_method_name
|
||||
@record_type = record_type
|
||||
@object_method_name = object_method_name
|
||||
@object_block = object_block
|
||||
@serializer = serializer
|
||||
@relationship_type = relationship_type
|
||||
@cached = cached
|
||||
@polymorphic = polymorphic
|
||||
@conditional_proc = conditional_proc
|
||||
end
|
||||
|
||||
def serialize(record, serialization_params, output_hash)
|
||||
if include_relationship?(record, serialization_params)
|
||||
empty_case = relationship_type == :has_many ? [] : nil
|
||||
output_hash[key] = {
|
||||
data: ids_hash_from_record_and_relationship(record, serialization_params) || empty_case
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_associated_object(record, params)
|
||||
return object_block.call(record, params) unless object_block.nil?
|
||||
record.send(object_method_name)
|
||||
end
|
||||
|
||||
def include_relationship?(record, serialization_params)
|
||||
if conditional_proc.present?
|
||||
conditional_proc.call(record, serialization_params)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ids_hash_from_record_and_relationship(record, params = {})
|
||||
return ids_hash(
|
||||
fetch_id(record, params)
|
||||
) unless polymorphic
|
||||
|
||||
return unless associated_object = fetch_associated_object(record, params)
|
||||
|
||||
return associated_object.map do |object|
|
||||
id_hash_from_record object, polymorphic
|
||||
end if associated_object.respond_to? :map
|
||||
|
||||
id_hash_from_record associated_object, polymorphic
|
||||
end
|
||||
|
||||
def id_hash_from_record(record, record_types)
|
||||
# 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
|
||||
id_hash(record.id, associated_record_type)
|
||||
end
|
||||
|
||||
def ids_hash(ids)
|
||||
return ids.map { |id| id_hash(id, record_type) } if ids.respond_to? :map
|
||||
id_hash(ids, record_type) # ids variable is just a single id here
|
||||
end
|
||||
|
||||
def id_hash(id, record_type, default_return=false)
|
||||
if id.present?
|
||||
{ id: id.to_s, type: record_type }
|
||||
else
|
||||
default_return ? { id: nil, type: record_type } : nil
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_id(record, params)
|
||||
unless object_block.nil?
|
||||
object = object_block.call(record, params)
|
||||
|
||||
return object.map(&:id) if object.respond_to? :map
|
||||
return object.try(:id)
|
||||
end
|
||||
|
||||
record.public_send(id_method_name)
|
||||
end
|
||||
end
|
||||
end
|
@ -34,51 +34,15 @@ module FastJsonapi
|
||||
end
|
||||
end
|
||||
|
||||
def ids_hash(ids, record_type)
|
||||
return ids.map { |id| id_hash(id, record_type) } if ids.respond_to? :map
|
||||
id_hash(ids, record_type) # ids variable is just a single id here
|
||||
end
|
||||
|
||||
def id_hash_from_record(record, record_types)
|
||||
# memoize the record type within the record_types dictionary, then assigning to record_type:
|
||||
record_type = record_types[record.class] ||= record.class.name.underscore.to_sym
|
||||
id_hash(record.id, record_type)
|
||||
end
|
||||
|
||||
def ids_hash_from_record_and_relationship(record, relationship, params = {})
|
||||
polymorphic = relationship[:polymorphic]
|
||||
|
||||
return ids_hash(
|
||||
fetch_id(record, relationship, params),
|
||||
relationship[:record_type]
|
||||
) unless polymorphic
|
||||
|
||||
return unless associated_object = fetch_associated_object(record, relationship, params)
|
||||
|
||||
return associated_object.map do |object|
|
||||
id_hash_from_record object, polymorphic
|
||||
end if associated_object.respond_to? :map
|
||||
|
||||
id_hash_from_record associated_object, polymorphic
|
||||
end
|
||||
|
||||
def links_hash(record, params = {})
|
||||
data_links.each_with_object({}) do |(key, method), link_hash|
|
||||
link_hash[key] = if method.is_a?(Proc)
|
||||
method.arity == 1 ? method.call(record) : method.call(record, params)
|
||||
else
|
||||
record.public_send(method)
|
||||
end
|
||||
data_links.each_with_object({}) do |(_k, link), hash|
|
||||
link.serialize(record, params, hash)
|
||||
end
|
||||
end
|
||||
|
||||
def attributes_hash(record, params = {})
|
||||
attributes_to_serialize.each_with_object({}) do |(key, method), attr_hash|
|
||||
attr_hash[key] = if method.is_a?(Proc)
|
||||
method.arity == 1 ? method.call(record) : method.call(record, params)
|
||||
else
|
||||
record.public_send(method)
|
||||
end
|
||||
attributes_to_serialize.each_with_object({}) do |(_k, attribute), hash|
|
||||
attribute.serialize(record, params, hash)
|
||||
end
|
||||
end
|
||||
|
||||
@ -86,11 +50,7 @@ module FastJsonapi
|
||||
relationships = relationships_to_serialize if relationships.nil?
|
||||
|
||||
relationships.each_with_object({}) do |(_k, relationship), hash|
|
||||
name = relationship[:key]
|
||||
empty_case = relationship[:relationship_type] == :has_many ? [] : nil
|
||||
hash[name] = {
|
||||
data: ids_hash_from_record_and_relationship(record, relationship, params) || empty_case
|
||||
}
|
||||
relationship.serialize(record, params, hash)
|
||||
end
|
||||
end
|
||||
|
||||
@ -147,12 +107,14 @@ module FastJsonapi
|
||||
items = parse_include_item(include_item)
|
||||
items.each do |item|
|
||||
next unless relationships_to_serialize && relationships_to_serialize[item]
|
||||
raise NotImplementedError if @relationships_to_serialize[item][:polymorphic].is_a?(Hash)
|
||||
record_type = @relationships_to_serialize[item][:record_type]
|
||||
serializer = @relationships_to_serialize[item][:serializer].to_s.constantize
|
||||
relationship_type = @relationships_to_serialize[item][:relationship_type]
|
||||
relationship_item = relationships_to_serialize[item]
|
||||
next unless relationship_item.include_relationship?(record, params)
|
||||
raise NotImplementedError if relationship_item.polymorphic.is_a?(Hash)
|
||||
record_type = relationship_item.record_type
|
||||
serializer = relationship_item.serializer.to_s.constantize
|
||||
relationship_type = relationship_item.relationship_type
|
||||
|
||||
included_objects = fetch_associated_object(record, @relationships_to_serialize[item], params)
|
||||
included_objects = relationship_item.fetch_associated_object(record, params)
|
||||
next if included_objects.blank?
|
||||
included_objects = [included_objects] unless relationship_type == :has_many
|
||||
|
||||
@ -171,22 +133,6 @@ module FastJsonapi
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_associated_object(record, relationship, params)
|
||||
return relationship[:object_block].call(record, params) unless relationship[:object_block].nil?
|
||||
record.send(relationship[:object_method_name])
|
||||
end
|
||||
|
||||
def fetch_id(record, relationship, params)
|
||||
unless relationship[:object_block].nil?
|
||||
object = relationship[:object_block].call(record, params)
|
||||
|
||||
return object.map(&:id) if object.respond_to? :map
|
||||
return object.try(:id)
|
||||
end
|
||||
|
||||
record.public_send(relationship[:id_method_name])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -312,7 +312,6 @@ describe FastJsonapi::ObjectSerializer do
|
||||
movie_type_serializer_class.instance_eval do
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_key_transform key_transform
|
||||
set_type :movie_type
|
||||
attributes :name
|
||||
end
|
||||
end
|
||||
@ -321,25 +320,25 @@ describe FastJsonapi::ObjectSerializer do
|
||||
context 'when key_transform is dash' do
|
||||
let(:key_transform) { :dash }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :'movie-type', :'release-year'
|
||||
it_behaves_like 'returning key transformed hash', :'movie-type', :'dash-movie-type', :'release-year'
|
||||
end
|
||||
|
||||
context 'when key_transform is camel' do
|
||||
let(:key_transform) { :camel }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :MovieType, :ReleaseYear
|
||||
it_behaves_like 'returning key transformed hash', :MovieType, :CamelMovieType, :ReleaseYear
|
||||
end
|
||||
|
||||
context 'when key_transform is camel_lower' do
|
||||
let(:key_transform) { :camel_lower }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :movieType, :releaseYear
|
||||
it_behaves_like 'returning key transformed hash', :movieType, :camelLowerMovieType, :releaseYear
|
||||
end
|
||||
|
||||
context 'when key_transform is underscore' do
|
||||
let(:key_transform) { :underscore }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :movie_type, :release_year
|
||||
it_behaves_like 'returning key transformed hash', :movie_type, :underscore_movie_type, :release_year
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -113,7 +113,7 @@ describe FastJsonapi::ObjectSerializer do
|
||||
end
|
||||
|
||||
it 'includes child attributes' do
|
||||
expect(EmployeeSerializer.attributes_to_serialize[:location]).to eq(:location)
|
||||
expect(EmployeeSerializer.attributes_to_serialize[:location].method).to eq(:location)
|
||||
end
|
||||
|
||||
it 'doesnt change parent class attributes' do
|
||||
|
@ -309,4 +309,142 @@ describe FastJsonapi::ObjectSerializer do
|
||||
expect(serializable_hash[:included][0][:links][:self]).to eq url
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is_collection option present' do
|
||||
subject { MovieSerializer.new(resource, is_collection_options).serializable_hash }
|
||||
|
||||
context 'autodetect' do
|
||||
let(:is_collection_options) { {} }
|
||||
|
||||
context 'collection if no option present' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect(subject[:data]).to be_a(Array) }
|
||||
end
|
||||
|
||||
context 'single if no option present' do
|
||||
let(:resource) { movie }
|
||||
it { expect(subject[:data]).to be_a(Hash) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'force is_collection to true' do
|
||||
let(:is_collection_options) { { is_collection: true } }
|
||||
|
||||
context 'collection will pass' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect(subject[:data]).to be_a(Array) }
|
||||
end
|
||||
|
||||
context 'single will raise error' do
|
||||
let(:resource) { movie }
|
||||
it { expect { subject }.to raise_error(NoMethodError, /method(.*)each/) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'force is_collection to false' do
|
||||
let(:is_collection_options) { { is_collection: false } }
|
||||
|
||||
context 'collection will fail without id' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect { subject }.to raise_error(FastJsonapi::MandatoryField, /id is a mandatory field/) }
|
||||
end
|
||||
|
||||
context 'single will pass' do
|
||||
let(:resource) { movie }
|
||||
it { expect(subject[:data]).to be_a(Hash) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional attributes are determined by record data' do
|
||||
it 'returns optional attribute when attribute is included' do
|
||||
movie.release_year = 2001
|
||||
json = MovieOptionalRecordDataSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes']['release_year']).to eq movie.release_year
|
||||
end
|
||||
|
||||
it "doesn't return optional attribute when attribute is not included" do
|
||||
movie.release_year = 1970
|
||||
json = MovieOptionalRecordDataSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes'].has_key?('release_year')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional attributes are determined by params data' do
|
||||
it 'returns optional attribute when attribute is included' do
|
||||
movie.director = 'steven spielberg'
|
||||
json = MovieOptionalParamsDataSerializer.new(movie, { params: { admin: true }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes']['director']).to eq 'steven spielberg'
|
||||
end
|
||||
|
||||
it "doesn't return optional attribute when attribute is not included" do
|
||||
movie.director = 'steven spielberg'
|
||||
json = MovieOptionalParamsDataSerializer.new(movie, { params: { admin: false }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes'].has_key?('director')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional relationships are determined by record data' do
|
||||
it 'returns optional relationship when relationship is included' do
|
||||
json = MovieOptionalRelationshipSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_truthy
|
||||
end
|
||||
|
||||
context "when relationship is not included" do
|
||||
let(:json) {
|
||||
MovieOptionalRelationshipSerializer.new(movie, options).serialized_json
|
||||
}
|
||||
let(:options) {
|
||||
{}
|
||||
}
|
||||
let(:serializable_hash) {
|
||||
JSON.parse(json)
|
||||
}
|
||||
|
||||
it "doesn't return optional relationship" do
|
||||
movie.actor_ids = []
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_falsey
|
||||
end
|
||||
|
||||
it "doesn't include optional relationship" do
|
||||
movie.actor_ids = []
|
||||
options[:include] = [:actors]
|
||||
expect(serializable_hash['included']).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional relationships are determined by params data' do
|
||||
it 'returns optional relationship when relationship is included' do
|
||||
json = MovieOptionalRelationshipWithParamsSerializer.new(movie, { params: { admin: true }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships'].has_key?('owner')).to be_truthy
|
||||
end
|
||||
|
||||
context "when relationship is not included" do
|
||||
let(:json) {
|
||||
MovieOptionalRelationshipWithParamsSerializer.new(movie, options).serialized_json
|
||||
}
|
||||
let(:options) {
|
||||
{ params: { admin: false }}
|
||||
}
|
||||
let(:serializable_hash) {
|
||||
JSON.parse(json)
|
||||
}
|
||||
|
||||
it "doesn't return optional relationship" do
|
||||
expect(serializable_hash['data']['relationships'].has_key?('owner')).to be_falsey
|
||||
end
|
||||
|
||||
it "doesn't include optional relationship" do
|
||||
options[:include] = [:owner]
|
||||
expect(serializable_hash['included']).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -17,31 +17,13 @@ describe FastJsonapi::ObjectSerializer do
|
||||
expect(result_hash).to be nil
|
||||
end
|
||||
|
||||
it 'returns the correct hash when ids_hash_from_record_and_relationship is called for a polymorphic association' do
|
||||
relationship = { name: :groupees, relationship_type: :has_many, object_method_name: :groupees, polymorphic: {} }
|
||||
results = GroupSerializer.send :ids_hash_from_record_and_relationship, group, relationship
|
||||
expect(results).to include({ id: "1", type: :person }, { id: "2", type: :group })
|
||||
end
|
||||
|
||||
it 'returns correct hash when ids_hash is called' do
|
||||
inputs = [{ids: %w(1 2 3), record_type: :movie}, {ids: %w(x y z), record_type: 'person'}]
|
||||
inputs.each do |hash|
|
||||
results = MovieSerializer.send(:ids_hash, hash[:ids], hash[:record_type])
|
||||
expect(results.map{|h| h[:id]}).to eq hash[:ids]
|
||||
expect(results[0][:type]).to eq hash[:record_type]
|
||||
end
|
||||
|
||||
result = MovieSerializer.send(:ids_hash, [], 'movie')
|
||||
expect(result).to be_empty
|
||||
end
|
||||
|
||||
it 'returns correct hash when attributes_hash is called' do
|
||||
attributes_hash = MovieSerializer.send(:attributes_hash, movie)
|
||||
attribute_names = attributes_hash.keys.sort
|
||||
expect(attribute_names).to eq MovieSerializer.attributes_to_serialize.keys.sort
|
||||
MovieSerializer.attributes_to_serialize.each do |key, method_name|
|
||||
MovieSerializer.attributes_to_serialize.each do |key, attribute|
|
||||
value = attributes_hash[key]
|
||||
expect(value).to eq movie.send(method_name)
|
||||
expect(value).to eq movie.send(attribute.method)
|
||||
end
|
||||
end
|
||||
|
||||
@ -57,7 +39,7 @@ describe FastJsonapi::ObjectSerializer do
|
||||
relationships_hash = MovieSerializer.send(:relationships_hash, movie)
|
||||
relationship_names = relationships_hash.keys.sort
|
||||
relationships_hashes = MovieSerializer.relationships_to_serialize.values
|
||||
expected_names = relationships_hashes.map{|relationship| relationship[:key]}.sort
|
||||
expected_names = relationships_hashes.map{|relationship| relationship.key}.sort
|
||||
expect(relationship_names).to eq expected_names
|
||||
end
|
||||
|
||||
|
@ -278,6 +278,34 @@ RSpec.shared_context 'movie class' do
|
||||
set_type :account
|
||||
belongs_to :supplier
|
||||
end
|
||||
|
||||
class MovieOptionalRecordDataSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :release_year, if: Proc.new { |record| record.release_year >= 2000 }
|
||||
end
|
||||
|
||||
class MovieOptionalParamsDataSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :director, if: Proc.new { |record, params| params && params[:admin] == true }
|
||||
end
|
||||
|
||||
class MovieOptionalRelationshipSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
has_many :actors, if: Proc.new { |record| record.actors.any? }
|
||||
end
|
||||
|
||||
class MovieOptionalRelationshipWithParamsSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
belongs_to :owner, record_type: :user, if: Proc.new { |record, params| params && params[:admin] == true }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
RSpec.shared_examples 'returning correct relationship hash' do |serializer, id_method_name, record_type|
|
||||
it 'returns correct relationship hash' do
|
||||
expect(relationship).to be_instance_of(Hash)
|
||||
expect(relationship.keys).to all(be_instance_of(Symbol))
|
||||
expect(relationship[:serializer]).to be serializer
|
||||
expect(relationship[:id_method_name]).to be id_method_name
|
||||
expect(relationship[:record_type]).to be record_type
|
||||
expect(relationship).to be_instance_of(FastJsonapi::Relationship)
|
||||
# expect(relationship.keys).to all(be_instance_of(Symbol))
|
||||
expect(relationship.serializer).to be serializer
|
||||
expect(relationship.id_method_name).to be id_method_name
|
||||
expect(relationship.record_type).to be record_type
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'returning key transformed hash' do |movie_type, release_year|
|
||||
RSpec.shared_examples 'returning key transformed hash' do |movie_type, serializer_type, release_year|
|
||||
it 'returns correctly transformed hash' do
|
||||
expect(hash[:data][0][:attributes]).to have_key(release_year)
|
||||
expect(hash[:data][0][:relationships]).to have_key(movie_type)
|
||||
expect(hash[:data][0][:relationships][movie_type][:data][:type]).to eq(movie_type)
|
||||
expect(hash[:included][0][:type]).to eq(movie_type)
|
||||
expect(hash[:included][0][:type]).to eq(serializer_type)
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user