Compare commits

...

2 Commits
master ... 1.2

Author SHA1 Message Date
Shishir Kakaraddi
ffa25cda0c bump up version to 1.2 2018-05-20 16:46:45 -07:00
Shishir Kakaraddi
f4ad6bc2ac Changes for version 1.2 (#220)
params support in blocks, nested includes etc
2018-05-20 16:05:02 -07:00
28 changed files with 1429 additions and 359 deletions

View File

@ -29,6 +29,7 @@ Fast JSON API serialized 250 records in 3.01 ms
* [Key Transforms](#key-transforms) * [Key Transforms](#key-transforms)
* [Collection Serialization](#collection-serialization) * [Collection Serialization](#collection-serialization)
* [Caching](#caching) * [Caching](#caching)
* [Params](#params)
* [Contributing](#contributing) * [Contributing](#contributing)
@ -204,14 +205,44 @@ class MovieSerializer
end end
``` ```
### 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.
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 also use a block to define a url as shown in `custom_url`. You can access params in these blocks as well as shown in `personalized_url`
```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer
link :public_url
link :self, :url
link :custom_url do |object|
"http://movies.com/#{object.name}-(#{object.year})"
end
link :personalized_url do |object, params|
"http://movies.com/#{object.name}-#{params[:user].reference_code}"
end
end
```
### Compound Document ### Compound Document
Support for top-level included member through ` options[:include] `. Support for top-level and nested included associations through ` options[:include] `.
```ruby ```ruby
options = {} options = {}
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
options[:include] = [:actors] options[:links] = {
self: '...',
next: '...',
prev: '...'
}
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
MovieSerializer.new([movie, movie], options).serialized_json MovieSerializer.new([movie, movie], options).serialized_json
``` ```
@ -219,11 +250,17 @@ MovieSerializer.new([movie, movie], options).serialized_json
```ruby ```ruby
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
options[:links] = {
self: '...',
next: '...',
prev: '...'
}
hash = MovieSerializer.new([movie, movie], options).serializable_hash hash = MovieSerializer.new([movie, movie], options).serializable_hash
json_string = MovieSerializer.new([movie, movie], options).serialized_json json_string = MovieSerializer.new([movie, movie], options).serialized_json
``` ```
### Caching ### Caching
Requires a `cache_key` method be defined on model:
```ruby ```ruby
class MovieSerializer class MovieSerializer
@ -234,17 +271,56 @@ class MovieSerializer
end end
``` ```
### Params
In some cases, attribute values might require more information than what is
available on the record, for example, access privileges or other information
related to a current authenticated user. The `options[:params]` value covers these
cases by allowing you to pass in a hash of additional parameters necessary for
your use case.
Leveraging the new params is easy, when you define a custom attribute or relationship with a
block you opt-in to using params by adding it as a block parameter.
```ruby
class MovieSerializer
class MovieSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :year
attribute :can_view_early do |movie, params|
# in here, params is a hash containing the `:current_user` key
params[:current_user].is_employee? ? true : false
end
belongs_to :primary_agent do |movie, params|
# in here, params is a hash containing the `:current_user` key
params[:current_user].is_employee? ? true : false
end
end
# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, {params: {current_user: current_user}})
serializer.serializable_hash
```
Custom attributes and relationships that only receive the resource are still possible by defining
the block to only receive one argument.
### Customizable Options ### Customizable Options
Option | Purpose | Example Option | Purpose | Example
------------ | ------------- | ------------- ------------ | ------------- | -------------
set_type | Type name of Object | ```set_type :movie ``` set_type | Type name of Object | ```set_type :movie ```
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``` 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 ```
object_method_name | Set custom method name to get related objects | ```has_many :locations, object_method_name: :places ``` object_method_name | Set custom method name to get related objects | ```has_many :locations, object_method_name: :places ```
record_type | Set custom Object Type for a relationship | ```belongs_to :owner, record_type: :user``` record_type | Set custom Object Type for a relationship | ```belongs_to :owner, record_type: :user```
serializer | Set custom Serializer for a relationship | ```has_many :actors, serializer: :custom_actor``` serializer | Set custom Serializer for a relationship | ```has_many :actors, serializer: :custom_actor``` or ```has_many :actors, serializer: MyApp::Api::V1::ActorSerializer```
polymorphic | Allows different record types for a polymorphic association | ```has_many :targets, polymorphic: true```
polymorphic | Sets custom record types for each object class in a polymorphic association | ```has_many :targets, polymorphic: { Person => :person, Group => :group }```
### Instrumentation ### Instrumentation
@ -304,4 +380,3 @@ rspec spec --tag performance:true
Join the Netflix Studio Engineering team and help us build gems like this! Join the Netflix Studio Engineering team and help us build gems like this!
* [Senior Ruby Engineer](https://jobs.netflix.com/jobs/864893) * [Senior Ruby Engineer](https://jobs.netflix.com/jobs/864893)
* [Senior Platform Engineer](https://jobs.netflix.com/jobs/865783)

View File

@ -1,12 +1,17 @@
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "fast_jsonapi/version"
Gem::Specification.new do |gem| Gem::Specification.new do |gem|
gem.name = "fast_jsonapi" gem.name = "fast_jsonapi"
gem.version = "1.1.1" gem.version = FastJsonapi::VERSION
gem.required_ruby_version = '>= 2.0.0' if gem.respond_to? :required_ruby_version=
gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version= gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version=
gem.metadata = { "allowed_push_host" => "https://rubygems.org" } if gem.respond_to? :metadata= gem.metadata = { "allowed_push_host" => "https://rubygems.org" } if gem.respond_to? :metadata=
gem.require_paths = ["lib"] gem.require_paths = ["lib"]
gem.authors = ["Shishir Kakaraddi", "Srinivas Raghunathan", "Adam Gross"] gem.authors = ["Shishir Kakaraddi", "Srinivas Raghunathan", "Adam Gross"]
gem.date = "2018-02-01"
gem.description = "JSON API(jsonapi.org) serializer that works with rails and can be used to serialize any kind of ruby objects" gem.description = "JSON API(jsonapi.org) serializer that works with rails and can be used to serialize any kind of ruby objects"
gem.email = "" gem.email = ""
gem.extra_rdoc_files = [ gem.extra_rdoc_files = [

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
begin if defined?(::ActiveRecord)
require 'active_record'
::ActiveRecord::Associations::Builder::HasOne.class_eval do ::ActiveRecord::Associations::Builder::HasOne.class_eval do
# Based on # 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/collection_association.rb#L50
@ -12,11 +10,11 @@ begin
name = reflection.name name = reflection.name
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_id 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) association(:#{name}).reader.try(:id)
end end
CODE CODE
end end
end end
rescue LoadError
# active_record can't be loaded so we shouldn't try to monkey-patch it.
end end

View File

@ -0,0 +1,7 @@
require 'skylight'
SKYLIGHT_NORMALIZER_BASE_CLASS = begin
::Skylight::Core::Normalizers::Normalizer
rescue NameError
::Skylight::Normalizers::Normalizer
end

View File

@ -1,11 +1,11 @@
require 'skylight' require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
require 'fast_jsonapi/instrumentation/serializable_hash' require 'fast_jsonapi/instrumentation/serializable_hash'
module FastJsonapi module FastJsonapi
module Instrumentation module Instrumentation
module Skylight module Skylight
module Normalizers module Normalizers
class SerializableHash < Skylight::Normalizers::Normalizer class SerializableHash < SKYLIGHT_NORMALIZER_BASE_CLASS
register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION

View File

@ -1,11 +1,11 @@
require 'skylight' require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
require 'fast_jsonapi/instrumentation/serializable_hash' require 'fast_jsonapi/instrumentation/serializable_hash'
module FastJsonapi module FastJsonapi
module Instrumentation module Instrumentation
module Skylight module Skylight
module Normalizers module Normalizers
class SerializedJson < Skylight::Normalizers::Normalizer class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'logger'
# Usage: # Usage:
# class Movie # class Movie
# def to_json(payload) # def to_json(payload)

View File

@ -10,8 +10,8 @@ module FastJsonapi
extend ActiveSupport::Concern extend ActiveSupport::Concern
include SerializationCore include SerializationCore
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'.freeze SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'.freeze SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
included do included do
# Set record_type based on the name of the serializer class # Set record_type based on the name of the serializer class
@ -34,11 +34,12 @@ module FastJsonapi
def hash_for_one_record def hash_for_one_record
serializable_hash = { data: nil } serializable_hash = { data: nil }
serializable_hash[:meta] = @meta if @meta.present? serializable_hash[:meta] = @meta if @meta.present?
serializable_hash[:links] = @links if @links.present?
return serializable_hash unless @resource return serializable_hash unless @resource
serializable_hash[:data] = self.class.record_hash(@resource) serializable_hash[:data] = self.class.record_hash(@resource, @params)
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects) if @includes.present? serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @params) if @includes.present?
serializable_hash serializable_hash
end end
@ -48,13 +49,14 @@ module FastJsonapi
data = [] data = []
included = [] included = []
@resource.each do |record| @resource.each do |record|
data << self.class.record_hash(record) data << self.class.record_hash(record, @params)
included.concat self.class.get_included_records(record, @includes, @known_included_objects) if @includes.present? included.concat self.class.get_included_records(record, @includes, @known_included_objects, @params) if @includes.present?
end end
serializable_hash[:data] = data serializable_hash[:data] = data
serializable_hash[:included] = included if @includes.present? serializable_hash[:included] = included if @includes.present?
serializable_hash[:meta] = @meta if @meta.present? serializable_hash[:meta] = @meta if @meta.present?
serializable_hash[:links] = @links if @links.present?
serializable_hash serializable_hash
end end
@ -69,20 +71,13 @@ module FastJsonapi
@known_included_objects = {} @known_included_objects = {}
@meta = options[:meta] @meta = options[:meta]
@links = options[:links]
@params = options[:params] || {}
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
if options[:include].present? if options[:include].present?
@includes = options[:include].delete_if(&:blank?).map(&:to_sym) @includes = options[:include].delete_if(&:blank?).map(&:to_sym)
validate_includes!(@includes) self.class.validate_includes!(@includes)
end
end
def validate_includes!(includes)
return if includes.blank?
existing_relationships = self.class.relationships_to_serialize.keys.to_set
unless existing_relationships.superset?(includes.to_set)
raise ArgumentError, "One of keys from #{includes} is not specified as a relationship on the serializer"
end end
end end
@ -91,6 +86,20 @@ module FastJsonapi
end end
class_methods do class_methods do
def inherited(subclass)
super(subclass)
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
subclass.transform_method = transform_method
subclass.cache_length = cache_length
subclass.race_condition_ttl = race_condition_ttl
subclass.data_links = data_links
subclass.cached = cached
end
def reflected_record_type def reflected_record_type
return @reflected_record_type if defined?(@reflected_record_type) return @reflected_record_type if defined?(@reflected_record_type)
@ -108,11 +117,11 @@ module FastJsonapi
dash: :dasherize, dash: :dasherize,
underscore: :underscore underscore: :underscore
} }
@transform_method = mapping[transform_name.to_sym] self.transform_method = mapping[transform_name.to_sym]
end end
def run_key_transform(input) def run_key_transform(input)
if @transform_method.present? if self.transform_method.present?
input.to_s.send(*@transform_method).to_sym input.to_s.send(*@transform_method).to_sym
else else
input.to_sym input.to_sym
@ -135,6 +144,7 @@ module FastJsonapi
def cache_options(cache_options) def cache_options(cache_options)
self.cached = cache_options[:enabled] || false self.cached = cache_options[:enabled] || false
self.cache_length = cache_options[:cache_length] || 5.minutes self.cache_length = cache_options[:cache_length] || 5.minutes
self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
end end
def attributes(*attributes_list, &block) def attributes(*attributes_list, &block)
@ -162,67 +172,54 @@ module FastJsonapi
self.relationships_to_serialize[name] = relationship self.relationships_to_serialize[name] = relationship
end end
def has_many(relationship_name, options = {}) def has_many(relationship_name, options = {}, &block)
name = relationship_name.to_sym name = relationship_name.to_sym
singular_name = relationship_name.to_s.singularize hash = create_relationship_hash(relationship_name, :has_many, options, block)
serializer_key = options[:serializer] || singular_name.to_sym add_relationship(name, hash)
key = options[:key] || run_key_transform(relationship_name) end
record_type = options[:record_type] || run_key_transform(singular_name)
relationship = { def has_one(relationship_name, options = {}, &block)
key: key, name = relationship_name.to_sym
hash = create_relationship_hash(relationship_name, :has_one, options, block)
add_relationship(name, hash)
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)
end
def create_relationship_hash(base_key, relationship_type, options, block)
name = base_key.to_sym
if relationship_type == :has_many
base_serialization_key = base_key.to_s.singularize
base_key_sym = base_serialization_key.to_sym
id_postfix = '_ids'
else
base_serialization_key = base_key
base_key_sym = name
id_postfix = '_id'
end
{
key: options[:key] || run_key_transform(base_key),
name: name, name: name,
id_method_name: options[:id_method_name] || (singular_name + '_ids').to_sym, id_method_name: options[:id_method_name] || "#{base_serialization_key}#{id_postfix}".to_sym,
record_type: record_type, record_type: options[:record_type] || run_key_transform(base_key_sym),
object_method_name: options[:object_method_name] || name, object_method_name: options[:object_method_name] || name,
serializer: compute_serializer_name(serializer_key), object_block: block,
relationship_type: :has_many, serializer: compute_serializer_name(options[:serializer] || base_key_sym),
relationship_type: relationship_type,
cached: options[:cached] || false, cached: options[:cached] || false,
polymorphic: fetch_polymorphic_option(options) polymorphic: fetch_polymorphic_option(options)
} }
add_relationship(name, relationship)
end
def belongs_to(relationship_name, options = {})
name = relationship_name.to_sym
serializer_key = options[:serializer] || relationship_name.to_sym
key = options[:key] || run_key_transform(relationship_name)
record_type = options[:record_type] || run_key_transform(relationship_name)
add_relationship(name, {
key: key,
name: name,
id_method_name: options[:id_method_name] || (relationship_name.to_s + '_id').to_sym,
record_type: record_type,
object_method_name: options[:object_method_name] || name,
serializer: compute_serializer_name(serializer_key),
relationship_type: :belongs_to,
cached: options[:cached] || true,
polymorphic: fetch_polymorphic_option(options)
})
end
def has_one(relationship_name, options = {})
name = relationship_name.to_sym
serializer_key = options[:serializer] || name
key = options[:key] || run_key_transform(relationship_name)
record_type = options[:record_type] || run_key_transform(relationship_name)
add_relationship(name, {
key: key,
name: name,
id_method_name: options[:id_method_name] || (relationship_name.to_s + '_id').to_sym,
record_type: record_type,
object_method_name: options[:object_method_name] || name,
serializer: compute_serializer_name(serializer_key),
relationship_type: :has_one,
cached: options[:cached] || false,
polymorphic: fetch_polymorphic_option(options)
})
end end
def compute_serializer_name(serializer_key) def compute_serializer_name(serializer_key)
return serializer_key unless serializer_key.is_a? Symbol
namespace = self.name.gsub(/()?\w+Serializer$/, '') namespace = self.name.gsub(/()?\w+Serializer$/, '')
serializer_name = serializer_key.to_s.classify + 'Serializer' serializer_name = serializer_key.to_s.classify + 'Serializer'
return (namespace + serializer_name).to_sym if namespace.present? (namespace + serializer_name).to_sym
(serializer_key.to_s.classify + 'Serializer').to_sym
end end
def fetch_polymorphic_option(options) def fetch_polymorphic_option(options)
@ -231,6 +228,27 @@ module FastJsonapi
return option if option.respond_to? :keys return option if option.respond_to? :keys
{} {}
end end
def link(link_name, link_method_name = nil, &block)
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
end
def validate_includes!(includes)
return if includes.blank?
includes.detect do |include_item|
klass = self
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
end
end
end
end end
end end
end end

View File

@ -4,6 +4,8 @@ require 'active_support/concern'
require 'fast_jsonapi/multi_to_json' require 'fast_jsonapi/multi_to_json'
module FastJsonapi module FastJsonapi
MandatoryField = Class.new(StandardError)
module SerializationCore module SerializationCore
extend ActiveSupport::Concern extend ActiveSupport::Concern
@ -13,16 +15,23 @@ module FastJsonapi
:relationships_to_serialize, :relationships_to_serialize,
:cachable_relationships_to_serialize, :cachable_relationships_to_serialize,
:uncachable_relationships_to_serialize, :uncachable_relationships_to_serialize,
:transform_method,
:record_type, :record_type,
:record_id, :record_id,
:cache_length, :cache_length,
:cached :race_condition_ttl,
:cached,
:data_links
end end
end end
class_methods do class_methods do
def id_hash(id, record_type) def id_hash(id, record_type, default_return=false)
return { id: id.to_s, type: record_type } if id.present? if id.present?
{ id: id.to_s, type: record_type }
else
default_return ? { id: nil, type: record_type } : nil
end
end end
def ids_hash(ids, record_type) def ids_hash(ids, record_type)
@ -33,19 +42,18 @@ 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:
record_type = record_types[record.class] ||= record.class.name.underscore.to_sym record_type = record_types[record.class] ||= record.class.name.underscore.to_sym
{ id: record.id.to_s, type: record_type } id_hash(record.id, record_type)
end end
def ids_hash_from_record_and_relationship(record, relationship) def ids_hash_from_record_and_relationship(record, relationship, params = {})
polymorphic = relationship[:polymorphic] polymorphic = relationship[:polymorphic]
return ids_hash( return ids_hash(
record.public_send(relationship[:id_method_name]), fetch_id(record, relationship, params),
relationship[:record_type] relationship[:record_type]
) unless polymorphic ) unless polymorphic
object_method_name = relationship.fetch(:object_method_name, relationship[:name]) return unless associated_object = fetch_associated_object(record, relationship, params)
return unless associated_object = record.send(object_method_name)
return associated_object.map do |object| return associated_object.map do |object|
id_hash_from_record object, polymorphic id_hash_from_record object, polymorphic
@ -54,69 +62,131 @@ module FastJsonapi
id_hash_from_record associated_object, polymorphic id_hash_from_record associated_object, polymorphic
end end
def attributes_hash(record) def links_hash(record, params = {})
attributes_to_serialize.each_with_object({}) do |(key, method), attr_hash| data_links.each_with_object({}) do |(key, method), link_hash|
attr_hash[key] = method.is_a?(Proc) ? method.call(record) : record.public_send(method) link_hash[key] = if method.is_a?(Proc)
method.arity == 1 ? method.call(record) : method.call(record, params)
else
record.public_send(method)
end
end end
end end
def relationships_hash(record, relationships = nil) 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
end
end
def relationships_hash(record, relationships = nil, params = {})
relationships = relationships_to_serialize if relationships.nil? relationships = relationships_to_serialize if relationships.nil?
relationships.each_with_object({}) do |(_k, relationship), hash| relationships.each_with_object({}) do |(_k, relationship), hash|
name = relationship[:key] name = relationship[:key]
empty_case = relationship[:relationship_type] == :has_many ? [] : nil empty_case = relationship[:relationship_type] == :has_many ? [] : nil
hash[name] = { hash[name] = {
data: ids_hash_from_record_and_relationship(record, relationship) || empty_case data: ids_hash_from_record_and_relationship(record, relationship, params) || empty_case
} }
end end
end end
def record_hash(record) def record_hash(record, params = {})
if cached if cached
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length) do record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
id = record_id ? record.send(record_id) : record.id temp_hash = id_hash(id_from_record(record), record_type, true)
temp_hash = id_hash(id, record_type) || { id: nil, type: record_type } temp_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
temp_hash[:attributes] = attributes_hash(record) if attributes_to_serialize.present?
temp_hash[:relationships] = {} temp_hash[:relationships] = {}
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize) if cachable_relationships_to_serialize.present? temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, params) if cachable_relationships_to_serialize.present?
temp_hash[:links] = links_hash(record, params) if data_links.present?
temp_hash temp_hash
end end
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize)) if uncachable_relationships_to_serialize.present? record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, params)) if uncachable_relationships_to_serialize.present?
record_hash record_hash
else else
id = record_id ? record.send(record_id) : record.id record_hash = id_hash(id_from_record(record), record_type, true)
record_hash = id_hash(id, record_type) || { id: nil, type: record_type } record_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
record_hash[:attributes] = attributes_hash(record) if attributes_to_serialize.present? record_hash[:relationships] = relationships_hash(record, nil, params) if relationships_to_serialize.present?
record_hash[:relationships] = relationships_hash(record) if relationships_to_serialize.present? record_hash[:links] = links_hash(record, params) if data_links.present?
record_hash record_hash
end end
end end
def id_from_record(record)
return record.send(record_id) if record_id
raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
record.id
end
# Override #to_json for alternative implementation # Override #to_json for alternative implementation
def to_json(payload) def to_json(payload)
FastJsonapi::MultiToJson.to_json(payload) if payload.present? FastJsonapi::MultiToJson.to_json(payload) if payload.present?
end end
# includes handler def parse_include_item(include_item)
return [include_item.to_sym] unless include_item.to_s.include?('.')
include_item.to_s.split('.').map { |item| item.to_sym }
end
def get_included_records(record, includes_list, known_included_objects) def remaining_items(items)
includes_list.each_with_object([]) do |item, included_records| return unless items.size > 1
object_method_name = @relationships_to_serialize[item][:object_method_name]
record_type = @relationships_to_serialize[item][:record_type] items_copy = items.dup
serializer = @relationships_to_serialize[item][:serializer].to_s.constantize items_copy.delete_at(0)
relationship_type = @relationships_to_serialize[item][:relationship_type] [items_copy.join('.').to_sym]
included_objects = record.send(object_method_name) end
next if included_objects.blank?
included_objects = [included_objects] unless relationship_type == :has_many # includes handler
included_objects.each do |inc_obj| def get_included_records(record, includes_list, known_included_objects, params = {})
code = "#{record_type}_#{inc_obj.id}" return unless includes_list.present?
next if known_included_objects.key?(code)
known_included_objects[code] = inc_obj includes_list.sort.each_with_object([]) do |include_item, included_records|
included_records << serializer.record_hash(inc_obj) 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]
included_objects = fetch_associated_object(record, @relationships_to_serialize[item], params)
next if included_objects.blank?
included_objects = [included_objects] unless relationship_type == :has_many
included_objects.each do |inc_obj|
if remaining_items(items)
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects)
included_records.concat(serializer_records) unless serializer_records.empty?
end
code = "#{record_type}_#{inc_obj.id}"
next if known_included_objects.key?(code)
known_included_objects[code] = inc_obj
included_records << serializer.record_hash(inc_obj, params)
end
end end
end 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.id
end
record.public_send(relationship[:id_method_name])
end
end end
end end
end end

View File

@ -0,0 +1,3 @@
module FastJsonapi
VERSION = "1.2"
end

View File

@ -0,0 +1,14 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
context 'instrument' do
context 'skylight' do
# skip for normal runs because this could alter some
# other test by insterting the instrumentation
xit 'make sure requiring skylight normalizers works' do
require 'fast_jsonapi/instrumentation/skylight'
end
end
end
end

View File

@ -0,0 +1,118 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
context "params option" do
let(:hash) { serializer.serializable_hash }
before(:context) do
class Movie
def viewed?(user)
user.viewed.include?(id)
end
end
class MovieSerializer
attribute :viewed do |movie, params|
params ? movie.viewed?(params[:user]) : false
end
attribute :no_param_attribute do |movie|
"no-param-attribute"
end
end
User = Struct.new(:viewed)
end
after(:context) do
Object.send(:remove_const, User) if Object.constants.include?(User)
end
context "enforces a hash only params" do
let(:params) { User.new([]) }
it "fails when creating a serializer with an object as params" do
expect(-> { MovieSerializer.new(movie, {params: User.new([])}) }).to raise_error(ArgumentError)
end
it "succeeds creating a serializer with a hash" do
expect(-> { MovieSerializer.new(movie, {params: {current_user: User.new([])}}) }).not_to raise_error
end
end
context "passing params to the serializer" do
let(:params) { {user: User.new([movie.id])} }
let(:options_with_params) { {params: params} }
context "with a single record" do
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
it "handles attributes that use params" do
expect(hash[:data][:attributes][:viewed]).to eq(true)
end
it "handles attributes that don't use params" do
expect(hash[:data][:attributes][:no_param_attribute]).to eq("no-param-attribute")
end
end
context "with a list of records" do
let(:movies) { build_movies(3) }
let(:user) { User.new(movies.map { |m| [true, false].sample ? m.id : nil }.compact) }
let(:params) { {user: user} }
let(:serializer) { MovieSerializer.new(movies, options_with_params) }
it "has 3 items" do
hash[:data].length == 3
end
it "handles passing params to a list of resources" do
param_attribute_values = hash[:data].map { |data| [data[:id], data[:attributes][:viewed]] }
expected_values = movies.map { |m| [m.id.to_s, user.viewed.include?(m.id)] }
expect(param_attribute_values).to eq(expected_values)
end
it "handles attributes without params" do
no_param_attribute_values = hash[:data].map { |data| data[:attributes][:no_param_attribute] }
expected_values = (1..3).map { "no-param-attribute" }
expect(no_param_attribute_values).to eq(expected_values)
end
end
end
context "without passing params to the serializer" do
context "with a single movie" do
let(:serializer) { MovieSerializer.new(movie) }
it "handles param attributes" do
expect(hash[:data][:attributes][:viewed]).to eq(false)
end
it "handles attributes that don't use params" do
expect(hash[:data][:attributes][:no_param_attribute]).to eq("no-param-attribute")
end
end
context "with multiple movies" do
let(:serializer) { MovieSerializer.new(build_movies(3)) }
it "handles attributes with params" do
param_attribute_values = hash[:data].map { |data| data[:attributes][:viewed] }
expect(param_attribute_values).to eq([false, false, false])
end
it "handles attributes that don't use params" do
no_param_attribute_values = hash[:data].map { |data| data[:attributes][:no_param_attribute] }
expected_attribute_values = (1..3).map { "no-param-attribute" }
expect(no_param_attribute_values).to eq(expected_attribute_values)
end
end
end
end
end

View File

@ -13,6 +13,8 @@ describe FastJsonapi::ObjectSerializer do
it 'returns correct hash when serializable_hash is called' do it 'returns correct hash when serializable_hash is called' do
options = {} options = {}
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
options[:links] = { self: 'self' }
options[:include] = [:actors] options[:include] = [:actors]
serializable_hash = CachingMovieSerializer.new([movie, movie], options).serializable_hash serializable_hash = CachingMovieSerializer.new([movie, movie], options).serializable_hash
@ -21,6 +23,7 @@ describe FastJsonapi::ObjectSerializer do
expect(serializable_hash[:data][0][:attributes].length).to eq 2 expect(serializable_hash[:data][0][:attributes].length).to eq 2
expect(serializable_hash[:meta]).to be_instance_of(Hash) expect(serializable_hash[:meta]).to be_instance_of(Hash)
expect(serializable_hash[:links]).to be_instance_of(Hash)
expect(serializable_hash[:included]).to be_instance_of(Array) expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included][0]).to be_instance_of(Hash) expect(serializable_hash[:included][0]).to be_instance_of(Hash)
@ -30,6 +33,7 @@ describe FastJsonapi::ObjectSerializer do
expect(serializable_hash[:data]).to be_instance_of(Hash) expect(serializable_hash[:data]).to be_instance_of(Hash)
expect(serializable_hash[:meta]).to be nil expect(serializable_hash[:meta]).to be nil
expect(serializable_hash[:links]).to be nil
expect(serializable_hash[:included]).to be nil expect(serializable_hash[:included]).to be nil
end end

View File

@ -4,73 +4,342 @@ describe FastJsonapi::ObjectSerializer do
include_context 'movie class' include_context 'movie class'
context 'when testing class methods of object serializer' do describe '#has_many' do
subject(:relationship) { serializer.relationships_to_serialize[:roles] }
before(:example) do before do
serializer.has_many *children
end
after do
serializer.relationships_to_serialize = {}
end
context 'with namespace' do
let(:serializer) { AppName::V1::MovieSerializer }
let(:children) { [:roles] }
context 'with overrides' do
let(:children) { [:roles, id_method_name: :roles_only_ids, record_type: :super_role] }
it_behaves_like 'returning correct relationship hash', :'AppName::V1::RoleSerializer', :roles_only_ids, :super_role
end
context 'without overrides' do
let(:children) { [:roles] }
it_behaves_like 'returning correct relationship hash', :'AppName::V1::RoleSerializer', :role_ids, :role
end
end
context 'without namespace' do
let(:serializer) { MovieSerializer }
context 'with overrides' do
let(:children) { [:roles, id_method_name: :roles_only_ids, record_type: :super_role] }
it_behaves_like 'returning correct relationship hash', :'RoleSerializer', :roles_only_ids, :super_role
end
context 'without overrides' do
let(:children) { [:roles] }
it_behaves_like 'returning correct relationship hash', :'RoleSerializer', :role_ids, :role
end
end
end
describe '#has_many with block' do
before do
MovieSerializer.has_many :awards do |movie|
movie.actors.map(&:awards).flatten
end
end
after do
MovieSerializer.relationships_to_serialize.delete(:awards)
end
context 'awards is not included' do
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
it 'returns correct hash' do
expect(hash[:data][:relationships][:awards][:data].length).to eq(6)
expect(hash[:data][:relationships][:awards][:data][0]).to eq({ id: '9', type: :award })
expect(hash[:data][:relationships][:awards][:data][-1]).to eq({ id: '28', type: :award })
end
end
context 'state is included' do
subject(:hash) { MovieSerializer.new(movie, include: [:awards]).serializable_hash }
it 'returns correct hash' do
expect(hash[:included].length).to eq 6
expect(hash[:included][0][:id]).to eq '9'
expect(hash[:included][0][:type]).to eq :award
expect(hash[:included][0][:attributes]).to eq({ id: 9, title: 'Test Award 9' })
expect(hash[:included][0][:relationships]).to eq({ actor: { data: { id: '1', type: :actor } } })
expect(hash[:included][-1][:id]).to eq '28'
expect(hash[:included][-1][:type]).to eq :award
expect(hash[:included][-1][:attributes]).to eq({ id: 28, title: 'Test Award 28' })
expect(hash[:included][-1][:relationships]).to eq({ actor: { data: { id: '3', type: :actor } } })
end
end
end
describe '#belongs_to' do
subject(:relationship) { MovieSerializer.relationships_to_serialize[:area] }
before do
MovieSerializer.belongs_to *parent
end
after do
MovieSerializer.relationships_to_serialize = {} MovieSerializer.relationships_to_serialize = {}
end end
it 'returns correct relationship hash for a has_many relationship' do context 'with overrides' do
MovieSerializer.has_many :roles let(:parent) { [:area, id_method_name: :blah_id, record_type: :awesome_area, serializer: :my_area] }
relationship = MovieSerializer.relationships_to_serialize[:roles]
expect(relationship).to be_instance_of(Hash) it_behaves_like 'returning correct relationship hash', :'MyAreaSerializer', :blah_id, :awesome_area
expect(relationship.keys).to all(be_instance_of(Symbol))
expect(relationship[:id_method_name]).to end_with '_ids'
expect(relationship[:record_type]).to eq 'roles'.singularize.to_sym
end end
it 'returns correct relationship hash for a has_many relationship with overrides' do context 'without overrides' do
MovieSerializer.has_many :roles, id_method_name: :roles_only_ids, record_type: :super_role let(:parent) { [:area] }
relationship = MovieSerializer.relationships_to_serialize[:roles]
expect(relationship[:id_method_name]).to be :roles_only_ids it_behaves_like 'returning correct relationship hash', :'AreaSerializer', :area_id, :area
expect(relationship[:record_type]).to be :super_role end
end
describe '#belongs_to with block' do
before do
ActorSerializer.belongs_to :state do |actor|
actor.agency.state
end
end end
it 'returns correct relationship hash for a belongs_to relationship' do after do
MovieSerializer.belongs_to :area ActorSerializer.relationships_to_serialize.delete(:actorc)
relationship = MovieSerializer.relationships_to_serialize[:area]
expect(relationship).to be_instance_of(Hash)
expect(relationship.keys).to all(be_instance_of(Symbol))
expect(relationship[:id_method_name]).to end_with '_id'
expect(relationship[:record_type]).to eq 'area'.singularize.to_sym
end end
it 'returns correct relationship hash for a belongs_to relationship with overrides' do context 'state is not included' do
MovieSerializer.has_many :area, id_method_name: :blah_id, record_type: :awesome_area, serializer: :my_area subject(:hash) { ActorSerializer.new(actor).serializable_hash }
relationship = MovieSerializer.relationships_to_serialize[:area]
expect(relationship[:id_method_name]).to be :blah_id it 'returns correct hash' do
expect(relationship[:record_type]).to be :awesome_area expect(hash[:data][:relationships][:state][:data]).to eq({ id: '1', type: :state })
expect(relationship[:serializer]).to be :MyAreaSerializer end
end end
it 'returns correct relationship hash for a has_one relationship' do context 'state is included' do
MovieSerializer.has_one :area subject(:hash) { ActorSerializer.new(actor, include: [:state]).serializable_hash }
relationship = MovieSerializer.relationships_to_serialize[:area]
expect(relationship).to be_instance_of(Hash) it 'returns correct hash' do
expect(relationship.keys).to all(be_instance_of(Symbol)) expect(hash[:included].length).to eq 1
expect(relationship[:id_method_name]).to end_with '_id' expect(hash[:included][0][:id]).to eq '1'
expect(relationship[:record_type]).to eq 'area'.singularize.to_sym expect(hash[:included][0][:type]).to eq :state
expect(hash[:included][0][:attributes]).to eq({ id: 1, name: 'Test State 1' })
expect(hash[:included][0][:relationships]).to eq({ agency: { data: [{ id: '432', type: :agency }] } })
end
end
end
describe '#has_one' do
subject(:relationship) { MovieSerializer.relationships_to_serialize[:area] }
before do
MovieSerializer.has_one *partner
end end
it 'returns correct relationship hash for a has_one relationship with overrides' do after do
MovieSerializer.has_one :area, id_method_name: :blah_id, record_type: :awesome_area MovieSerializer.relationships_to_serialize = {}
relationship = MovieSerializer.relationships_to_serialize[:area]
expect(relationship[:id_method_name]).to be :blah_id
expect(relationship[:record_type]).to be :awesome_area
end end
it 'returns serializer name correctly with namespaces' do context 'with overrides' do
AppName::V1::MovieSerializer.has_many :area, id_method_name: :blah_id let(:partner) { [:area, id_method_name: :blah_id, record_type: :awesome_area, serializer: :my_area] }
relationship = AppName::V1::MovieSerializer.relationships_to_serialize[:area]
expect(relationship[:serializer]).to be :'AppName::V1::AreaSerializer' it_behaves_like 'returning correct relationship hash', :'MyAreaSerializer', :blah_id, :awesome_area
end
context 'without overrides' do
let(:partner) { [:area] }
it_behaves_like 'returning correct relationship hash', :'AreaSerializer', :area_id, :area
end
end
describe '#set_id' do
subject(:serializable_hash) { MovieSerializer.new(resource).serializable_hash }
before do
MovieSerializer.set_id :owner_id
end
after do
MovieSerializer.set_id nil
end
context 'when one record is given' do
let(:resource) { movie }
it 'returns correct hash which id equals owner_id' do
expect(serializable_hash[:data][:id].to_i).to eq movie.owner_id
end
end
context 'when an array of records is given' do
let(:resource) { [movie, movie] }
it 'returns correct hash which id equals owner_id' do
expect(serializable_hash[:data][0][:id].to_i).to eq movie.owner_id
expect(serializable_hash[:data][1][:id].to_i).to eq movie.owner_id
end
end
end
describe '#use_hyphen' do
subject { MovieSerializer.use_hyphen }
after do
MovieSerializer.transform_method = nil
end end
it 'sets the correct transform_method when use_hyphen is used' do it 'sets the correct transform_method when use_hyphen is used' do
MovieSerializer.use_hyphen warning_message = "DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead\n"
warning_message = 'DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead' expect { subject }.to output(warning_message).to_stderr
expect { MovieSerializer.use_hyphen }.to output.to_stderr
expect(MovieSerializer.instance_variable_get(:@transform_method)).to eq :dasherize expect(MovieSerializer.instance_variable_get(:@transform_method)).to eq :dasherize
end end
end end
describe '#attribute' do
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
context 'with block' do
before do
movie.release_year = 2008
MovieSerializer.attribute :title_with_year do |record|
"#{record.name} (#{record.release_year})"
end
end
after do
MovieSerializer.attributes_to_serialize.delete(:title_with_year)
end
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:data][:attributes][:name]).to eq movie.name
expect(serializable_hash[:data][:attributes][:title_with_year]).to eq "#{movie.name} (#{movie.release_year})"
end
end
end
describe '#link' do
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
after do
MovieSerializer.data_links = {}
ActorSerializer.data_links = {}
end
context 'with block calling instance method on serializer' do
before do
MovieSerializer.link(:self) do |movie_object|
movie_object.url
end
end
let(:url) { "http://movies.com/#{movie.id}" }
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:data][:links][:self]).to eq url
end
end
context 'with block and param' do
before do
MovieSerializer.link(:public_url) do |movie_object|
"http://movies.com/#{movie_object.id}"
end
end
let(:url) { "http://movies.com/#{movie.id}" }
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:data][:links][:public_url]).to eq url
end
end
context 'with method' do
before do
MovieSerializer.link(:object_id, :id)
end
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:data][:links][:object_id]).to eq movie.id
end
end
context 'with method and convention' do
before do
MovieSerializer.link(:url)
end
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:data][:links][:url]).to eq movie.url
end
end
end
describe '#key_transform' do
subject(:hash) { movie_serializer_class.new([movie, movie], include: [:movie_type]).serializable_hash }
let(:movie_serializer_class) { "#{key_transform}_movie_serializer".classify.constantize }
before(:context) do
[:dash, :camel, :camel_lower, :underscore].each do |key_transform|
movie_serializer_name = "#{key_transform}_movie_serializer".classify
movie_type_serializer_name = "#{key_transform}_movie_type_serializer".classify
# https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name
movie_serializer_class = Object.const_set(movie_serializer_name, Class.new)
# https://rubymonk.com/learning/books/5-metaprogramming-ruby-ascent/chapters/24-eval/lessons/67-instance-eval
movie_serializer_class.instance_eval do
include FastJsonapi::ObjectSerializer
set_type :movie
set_key_transform key_transform
attributes :name, :release_year
has_many :actors
belongs_to :owner, record_type: :user
belongs_to :movie_type, serializer: "#{key_transform}_movie_type".to_sym
end
movie_type_serializer_class = Object.const_set(movie_type_serializer_name, Class.new)
movie_type_serializer_class.instance_eval do
include FastJsonapi::ObjectSerializer
set_key_transform key_transform
set_type :movie_type
attributes :name
end
end
end
context 'when key_transform is dash' do
let(:key_transform) { :dash }
it_behaves_like 'returning key transformed hash', :'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
end
context 'when key_transform is camel_lower' do
let(:key_transform) { :camel_lower }
it_behaves_like 'returning key transformed hash', :movieType, :releaseYear
end
context 'when key_transform is underscore' do
let(:key_transform) { :underscore }
it_behaves_like 'returning key transformed hash', :movie_type, :release_year
end
end
end end

View File

@ -0,0 +1,163 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
after(:all) do
classes_to_remove = %i[
User
UserSerializer
Country
CountrySerializer
Employee
EmployeeSerializer
Photo
PhotoSerializer
EmployeeAccount
]
classes_to_remove.each do |klass_name|
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
end
end
class User
attr_accessor :id, :first_name, :last_name
attr_accessor :address_ids, :country_id
def photo
p = Photo.new
p.id = 1
p.user_id = id
p
end
def photo_id
1
end
end
class UserSerializer
include FastJsonapi::ObjectSerializer
set_type :user
attributes :first_name, :last_name
attribute :full_name do |user, params|
"#{user.first_name} #{user.last_name}"
end
has_many :addresses, cached: true
belongs_to :country
has_one :photo
end
class Photo
attr_accessor :id, :user_id
end
class PhotoSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name
end
class Country
attr_accessor :id, :name
end
class CountrySerializer
include FastJsonapi::ObjectSerializer
attributes :name
end
class EmployeeAccount
attr_accessor :id, :employee_id
end
class Employee < User
attr_accessor :id, :location, :compensation
def account
a = EmployeeAccount.new
a.id = 1
a.employee_id = id
a
end
def account_id
1
end
end
class EmployeeSerializer < UserSerializer
include FastJsonapi::ObjectSerializer
attributes :location
attributes :compensation
has_one :account
end
context 'when testing inheritance of attributes' do
it 'includes parent attributes' do
subclass_attributes = EmployeeSerializer.attributes_to_serialize
superclass_attributes = UserSerializer.attributes_to_serialize
expect(subclass_attributes).to include(superclass_attributes)
end
it 'returns inherited attribute with a block correctly' do
e = Employee.new
e.id = 1
e.first_name = 'S'
e.last_name = 'K'
attributes_hash = EmployeeSerializer.new(e).serializable_hash[:data][:attributes]
expect(attributes_hash).to include(full_name: 'S K')
end
it 'includes child attributes' do
expect(EmployeeSerializer.attributes_to_serialize[:location]).to eq(:location)
end
it 'doesnt change parent class attributes' do
EmployeeSerializer
expect(UserSerializer.attributes_to_serialize).not_to have_key(:location)
end
end
context 'when testing inheritance of relationship' do
it 'includes parent relationships' do
subclass_relationships = EmployeeSerializer.relationships_to_serialize
superclass_relationships = UserSerializer.relationships_to_serialize
expect(subclass_relationships).to include(superclass_relationships)
end
it 'returns inherited relationship correctly' do
e = Employee.new
e.country_id = 1
relationships_hash = EmployeeSerializer.new(e).serializable_hash[:data][:relationships][:country]
expect(relationships_hash).to include(data: { id: "1", type: :country })
end
it 'includes child relationships' do
expect(EmployeeSerializer.relationships_to_serialize.keys).to include(:account)
end
it 'doesnt change parent class attributes' do
EmployeeSerializer
expect(UserSerializer.relationships_to_serialize.keys).not_to include(:account)
end
it 'includes parent cached relationships' do
subclass_relationships = EmployeeSerializer.cachable_relationships_to_serialize
superclass_relationships = UserSerializer.cachable_relationships_to_serialize
expect(subclass_relationships).to include(superclass_relationships)
end
end
context 'when test inheritence of other attributes' do
it 'inherits the tranform method' do
EmployeeSerializer
expect(UserSerializer.transform_method).to eq EmployeeSerializer.transform_method
end
end
end

View File

@ -1,80 +0,0 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
include_context 'ams movie class'
before(:context) do
[:dash, :camel, :camel_lower, :underscore].each do |transform_type|
movie_serializer_name = "#{transform_type}_movie_serializer".classify
movie_type_serializer_name = "#{transform_type}_movie_type_serializer".classify
# https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name
movie_serializer_class = Object.const_set(
movie_serializer_name,
Class.new {
}
)
# https://rubymonk.com/learning/books/5-metaprogramming-ruby-ascent/chapters/24-eval/lessons/67-instance-eval
movie_serializer_class.instance_eval do
include FastJsonapi::ObjectSerializer
set_type :movie
set_key_transform transform_type
attributes :name, :release_year
has_many :actors
belongs_to :owner, record_type: :user
belongs_to :movie_type
end
movie_type_serializer_class = Object.const_set(
movie_type_serializer_name,
Class.new {
}
)
movie_type_serializer_class.instance_eval do
include FastJsonapi::ObjectSerializer
set_key_transform transform_type
set_type :movie_type
attributes :name
end
end
end
context 'when using dashes for word separation in the JSON API members' do
it 'returns correct hash when serializable_hash is called' do
serializable_hash = DashMovieSerializer.new([movie, movie]).serializable_hash
expect(serializable_hash[:data].length).to eq 2
expect(serializable_hash[:data][0][:relationships].length).to eq 3
expect(serializable_hash[:data][0][:relationships]).to have_key('movie-type'.to_sym)
expect(serializable_hash[:data][0][:attributes].length).to eq 2
expect(serializable_hash[:data][0][:attributes]).to have_key("release-year".to_sym)
serializable_hash = DashMovieSerializer.new(movie_struct).serializable_hash
expect(serializable_hash[:data][:relationships].length).to eq 3
expect(serializable_hash[:data][:relationships]).to have_key('movie-type'.to_sym)
expect(serializable_hash[:data][:attributes].length).to eq 2
expect(serializable_hash[:data][:attributes]).to have_key('release-year'.to_sym)
expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s
end
it 'returns type hypenated when trying to serializing a class with multiple words' do
movie_type = MovieType.new
movie_type.id = 3
movie_type.name = "x"
serializable_hash = DashMovieTypeSerializer.new(movie_type).serializable_hash
expect(serializable_hash[:data][:type].to_sym).to eq 'movie-type'.to_sym
end
end
context 'when using other key transforms' do
[:camel, :camel_lower, :underscore, :dash].each do |transform_type|
it "returns same thing as ams when using #{transform_type}" do
ams_movie = build_ams_movies(1).first
movie = build_movies(1).first
movie_serializer_class = "#{transform_type}_movie_serializer".classify.constantize
our_json = movie_serializer_class.new([movie]).serialized_json
ams_json = ActiveModelSerializers::SerializableResource.new([ams_movie], key_transform: transform_type).to_json
expect(our_json.length).to eq (ams_json.length)
end
end
end
end

View File

@ -137,7 +137,6 @@ describe FastJsonapi::ObjectSerializer, performance: true do
# json # json
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time] json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
expect(json_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
# hash # hash
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time] hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]
@ -174,7 +173,6 @@ describe FastJsonapi::ObjectSerializer, performance: true do
# json # json
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time] json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
expect(json_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
# hash # hash
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time] hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]
@ -209,7 +207,6 @@ describe FastJsonapi::ObjectSerializer, performance: true do
# json # json
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time] json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
expect(json_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
# hash # hash
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time] hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]

View File

@ -0,0 +1,63 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
context "params option" do
let(:hash) { serializer.serializable_hash }
before(:context) do
class MovieSerializer
has_many :agencies do |movie, params|
movie.actors.map(&:agency) if params[:authorized]
end
belongs_to :primary_agency do |movie, params|
movie.actors.map(&:agency)[0] if params[:authorized]
end
belongs_to :secondary_agency do |movie|
movie.actors.map(&:agency)[1]
end
end
end
context "passing params to the serializer" do
let(:params) { {authorized: true} }
let(:options_with_params) { {params: params} }
context "with a single record" do
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
it "handles relationships that use params" do
ids = hash[:data][:relationships][:agencies][:data].map{|a| a[:id]}
ids.map!(&:to_i)
expect(ids).to eq [0,1,2]
end
it "handles relationships that don't use params" do
expect(hash[:data][:relationships][:secondary_agency][:data]).to include({id: 1.to_s})
end
end
context "with a list of records" do
let(:movies) { build_movies(3) }
let(:params) { {authorized: true} }
let(:serializer) { MovieSerializer.new(movies, options_with_params) }
it "handles relationship params when passing params to a list of resources" do
relationships_hashes = hash[:data].map{|a| a[:relationships][:agencies][:data]}.uniq.flatten
expect(relationships_hashes.map{|a| a[:id].to_i}).to contain_exactly 0,1,2
uniq_count = hash[:data].map{|a| a[:relationships][:primary_agency] }.uniq.count
expect(uniq_count).to eq 1
end
it "handles relationships without params" do
uniq_count = hash[:data].map{|a| a[:relationships][:secondary_agency] }.uniq.count
expect(uniq_count).to eq 1
end
end
end
end
end

View File

@ -1,30 +0,0 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
context 'when setting id' do
subject(:serializable_hash) { MovieSerializer.new(resource).serializable_hash }
before(:all) do
MovieSerializer.set_id :owner_id
end
context 'when one record is given' do
let(:resource) { movie }
it 'returns correct hash which id equals owner_id' do
expect(serializable_hash[:data][:id].to_i).to eq movie.owner_id
end
end
context 'when an array of records is given' do
let(:resource) { [movie, movie] }
it 'returns correct hash which id equals owner_id' do
expect(serializable_hash[:data][0][:id].to_i).to eq movie.owner_id
expect(serializable_hash[:data][1][:id].to_i).to eq movie.owner_id
end
end
end
end

View File

@ -2,19 +2,22 @@ require 'spec_helper'
describe FastJsonapi::ObjectSerializer do describe FastJsonapi::ObjectSerializer do
include_context 'movie class' include_context 'movie class'
include_context 'group class'
context 'when testing instance methods of object serializer' do context 'when testing instance methods of object serializer' do
it 'returns correct hash when serializable_hash is called' do it 'returns correct hash when serializable_hash is called' do
options = {} options = {}
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
options[:links] = { self: 'self' }
options[:include] = [:actors] options[:include] = [:actors]
serializable_hash = MovieSerializer.new([movie, movie], options).serializable_hash serializable_hash = MovieSerializer.new([movie, movie], options).serializable_hash
expect(serializable_hash[:data].length).to eq 2 expect(serializable_hash[:data].length).to eq 2
expect(serializable_hash[:data][0][:relationships].length).to eq 3 expect(serializable_hash[:data][0][:relationships].length).to eq 4
expect(serializable_hash[:data][0][:attributes].length).to eq 2 expect(serializable_hash[:data][0][:attributes].length).to eq 2
expect(serializable_hash[:meta]).to be_instance_of(Hash) expect(serializable_hash[:meta]).to be_instance_of(Hash)
expect(serializable_hash[:links]).to be_instance_of(Hash)
expect(serializable_hash[:included]).to be_instance_of(Array) expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included][0]).to be_instance_of(Hash) expect(serializable_hash[:included][0]).to be_instance_of(Hash)
@ -24,9 +27,34 @@ describe FastJsonapi::ObjectSerializer do
expect(serializable_hash[:data]).to be_instance_of(Hash) expect(serializable_hash[:data]).to be_instance_of(Hash)
expect(serializable_hash[:meta]).to be nil expect(serializable_hash[:meta]).to be nil
expect(serializable_hash[:links]).to be nil
expect(serializable_hash[:included]).to be nil expect(serializable_hash[:included]).to be nil
end end
it 'returns correct nested includes when serializable_hash is called' do
# 3 actors, 3 agencies
include_object_total = 6
options = {}
options[:include] = [:actors, :'actors.agency']
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included].length).to eq include_object_total
(0..include_object_total-1).each do |include|
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
end
options[:include] = [:'actors.agency']
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included].length).to eq include_object_total
(0..include_object_total-1).each do |include|
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
end
end
it 'returns correct number of records when serialized_json is called for an array' do it 'returns correct number of records when serialized_json is called for an array' do
options = {} options = {}
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
@ -123,6 +151,107 @@ describe FastJsonapi::ObjectSerializer do
end end
end end
context 'nested includes' do
it 'has_many to belongs_to: returns correct nested includes when serializable_hash is called' do
# 3 actors, 3 agencies
include_object_total = 6
options = {}
options[:include] = [:actors, :'actors.agency']
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included].length).to eq include_object_total
(0..include_object_total-1).each do |include|
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
end
options[:include] = [:'actors.agency']
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included].length).to eq include_object_total
(0..include_object_total-1).each do |include|
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
end
end
it '`has_many` to `belongs_to` to `belongs_to` - returns correct nested includes when serializable_hash is called' do
# 3 actors, 3 agencies, 1 state
include_object_total = 7
options = {}
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included].length).to eq include_object_total
actors_serialized = serializable_hash[:included].find_all { |included| included[:type] == :actor }.map { |included| included[:id].to_i }
agencies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :agency }.map { |included| included[:id].to_i }
states_serialized = serializable_hash[:included].find_all { |included| included[:type] == :state }.map { |included| included[:id].to_i }
movie.actors.each do |actor|
expect(actors_serialized).to include(actor.id)
end
agencies = movie.actors.map(&:agency).uniq
agencies.each do |agency|
expect(agencies_serialized).to include(agency.id)
end
states = agencies.map(&:state).uniq
states.each do |state|
expect(states_serialized).to include(state.id)
end
end
it 'has_many => has_one returns correct nested includes when serializable_hash is called' do
options = {}
options[:include] = [:movies, :'movies.advertising_campaign']
serializable_hash = MovieTypeSerializer.new([movie_type], options).serializable_hash
movies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :movie }.map { |included| included[:id].to_i }
advertising_campaigns_serialized = serializable_hash[:included].find_all { |included| included[:type] == :advertising_campaign }.map { |included| included[:id].to_i }
movies = movie_type.movies
movies.each do |movie|
expect(movies_serialized).to include(movie.id)
end
advertising_campaigns = movies.map(&:advertising_campaign)
advertising_campaigns.each do |advertising_campaign|
expect(advertising_campaigns_serialized).to include(advertising_campaign.id)
end
end
it 'belongs_to: returns correct nested includes when nested attributes are nil when serializable_hash is called' do
class Movie
def advertising_campaign
nil
end
end
options = {}
options[:include] = [:movies, :'movies.advertising_campaign']
serializable_hash = MovieTypeSerializer.new([movie_type], options).serializable_hash
movies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :movie }.map { |included| included[:id].to_i }
movies = movie_type.movies
movies.each do |movie|
expect(movies_serialized).to include(movie.id)
end
end
it 'polymorphic throws an error that polymorphic is not supported' do
options = {}
options[:include] = [:groupees]
expect(-> { GroupSerializer.new([group], options)}).to raise_error(NotImplementedError)
end
end
context 'when testing included do block of object serializer' do context 'when testing included do block of object serializer' do
it 'should set default_type based on serializer class name' do it 'should set default_type based on serializer class name' do
class BlahSerializer class BlahSerializer
@ -154,4 +283,23 @@ describe FastJsonapi::ObjectSerializer do
expect(V1::BlahSerializer.record_type).to be :blah expect(V1::BlahSerializer.record_type).to be :blah
end end
end end
context 'when serializing included, serialize any links' do
before do
ActorSerializer.link(:self) do |actor_object|
actor_object.url
end
end
subject(:serializable_hash) do
options = {}
options[:include] = [:actors]
MovieSerializer.new(movie, options).serializable_hash
end
let(:actor) { movie.actors.first }
let(:url) { "http://movies.com/actors/#{actor.id}" }
it 'returns correct hash when serializable_hash is called' do
expect(serializable_hash[:included][0][:links][:self]).to eq url
end
end
end end

View File

@ -7,14 +7,16 @@ describe FastJsonapi::ObjectSerializer do
it 'returns correct hash when serializable_hash is called' do it 'returns correct hash when serializable_hash is called' do
options = {} options = {}
options[:meta] = { total: 2 } options[:meta] = { total: 2 }
options[:links] = { self: 'self' }
options[:include] = [:actors] options[:include] = [:actors]
serializable_hash = MovieSerializer.new([movie_struct, movie_struct], options).serializable_hash serializable_hash = MovieSerializer.new([movie_struct, movie_struct], options).serializable_hash
expect(serializable_hash[:data].length).to eq 2 expect(serializable_hash[:data].length).to eq 2
expect(serializable_hash[:data][0][:relationships].length).to eq 3 expect(serializable_hash[:data][0][:relationships].length).to eq 4
expect(serializable_hash[:data][0][:attributes].length).to eq 2 expect(serializable_hash[:data][0][:attributes].length).to eq 2
expect(serializable_hash[:meta]).to be_instance_of(Hash) expect(serializable_hash[:meta]).to be_instance_of(Hash)
expect(serializable_hash[:links]).to be_instance_of(Hash)
expect(serializable_hash[:included]).to be_instance_of(Array) expect(serializable_hash[:included]).to be_instance_of(Array)
expect(serializable_hash[:included][0]).to be_instance_of(Hash) expect(serializable_hash[:included][0]).to be_instance_of(Hash)
@ -24,8 +26,16 @@ describe FastJsonapi::ObjectSerializer do
expect(serializable_hash[:data]).to be_instance_of(Hash) expect(serializable_hash[:data]).to be_instance_of(Hash)
expect(serializable_hash[:meta]).to be nil expect(serializable_hash[:meta]).to be nil
expect(serializable_hash[:links]).to be nil
expect(serializable_hash[:included]).to be nil expect(serializable_hash[:included]).to be nil
expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s
end end
context 'struct without id' do
it 'returns correct hash when serializable_hash is called' do
serializer = MovieWithoutIdStructSerializer.new(movie_struct_without_id)
expect { serializer.serializable_hash }.to raise_error(FastJsonapi::MandatoryField)
end
end
end end
end end

View File

@ -1,13 +0,0 @@
require 'spec_helper'
describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
context 'when including attribute blocks' do
it 'returns correct hash when serializable_hash is called' do
serializable_hash = MovieSerializerWithAttributeBlock.new([movie]).serializable_hash
expect(serializable_hash[:data][0][:attributes][:name]).to eq movie.name
expect(serializable_hash[:data][0][:attributes][:title_with_year]).to eq "#{movie.name} (#{movie.release_year})"
end
end
end

View File

@ -18,7 +18,7 @@ describe FastJsonapi::ObjectSerializer do
end end
it 'returns the correct hash when ids_hash_from_record_and_relationship is called for a polymorphic association' do 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, polymorphic: {} } relationship = { name: :groupees, relationship_type: :has_many, object_method_name: :groupees, polymorphic: {} }
results = GroupSerializer.send :ids_hash_from_record_and_relationship, group, relationship results = GroupSerializer.send :ids_hash_from_record_and_relationship, group, relationship
expect(results).to include({ id: "1", type: :person }, { id: "2", type: :group }) expect(results).to include({ id: "1", type: :person }, { id: "2", type: :group })
end end
@ -70,7 +70,7 @@ describe FastJsonapi::ObjectSerializer do
end end
it 'returns correct hash when record_hash is called' do it 'returns correct hash when record_hash is called' do
record_hash = MovieSerializer.send(:record_hash, movie) record_hash = MovieSerializer.send(:record_hash, movie, nil)
expect(record_hash[:id]).to eq movie.id.to_s expect(record_hash[:id]).to eq movie.id.to_s
expect(record_hash[:type]).to eq MovieSerializer.record_type expect(record_hash[:type]).to eq MovieSerializer.record_type
expect(record_hash).to have_key(:attributes) if MovieSerializer.attributes_to_serialize.present? expect(record_hash).to have_key(:attributes) if MovieSerializer.attributes_to_serialize.present?
@ -82,7 +82,7 @@ describe FastJsonapi::ObjectSerializer do
known_included_objects = {} known_included_objects = {}
included_records = [] included_records = []
[movie, movie].each do |record| [movie, movie].each do |record|
included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects) included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, nil)
end end
expect(included_records.size).to eq 3 expect(included_records.size).to eq 3
end end

View File

@ -4,12 +4,55 @@ RSpec.shared_context 'ams movie class' do
class AMSModel < ActiveModelSerializers::Model class AMSModel < ActiveModelSerializers::Model
derive_attributes_from_names_and_fix_accessors derive_attributes_from_names_and_fix_accessors
end end
class AMSMovieType < AMSModel
attributes :id, :name, :movies
end
class AMSMovie < AMSModel class AMSMovie < AMSModel
attributes :id, :name, :release_year, :actors, :owner, :movie_type attributes :id, :name, :release_year, :actors, :owner, :movie_type, :advertising_campaign
def movie_type
mt = AMSMovieType.new
mt.id = 1
mt.name = 'Episode'
mt.movies = [self]
mt
end
end
class AMSAdvertisingCampaign < AMSModel
attributes :id, :name, :movie
end
class AMSAward < AMSModel
attributes :id, :title, :actor
end
class AMSAgency < AMSModel
attributes :id, :name, :actors
end end
class AMSActor < AMSModel class AMSActor < AMSModel
attributes :id, :name, :email attributes :id, :name, :email, :agency, :awards, :agency_id
def agency
AMSAgency.new.tap do |a|
a.id = agency_id
a.name = "Test Agency #{agency_id}"
end
end
def award_ids
[id * 9, id * 9 + 1]
end
def awards
award_ids.map do |i|
AMSAward.new.tap do |a|
a.id = i
a.title = "Test Award #{i}"
end
end
end
end end
class AMSUser < AMSModel class AMSUser < AMSModel
@ -19,9 +62,22 @@ RSpec.shared_context 'ams movie class' do
attributes :id, :name attributes :id, :name
end end
# serializers # serializers
class AMSAwardSerializer < ActiveModel::Serializer
type 'award'
attributes :id, :title
belongs_to :actor
end
class AMSAgencySerializer < ActiveModel::Serializer
type 'agency'
attributes :id, :name
belongs_to :state
has_many :actors
end
class AMSActorSerializer < ActiveModel::Serializer class AMSActorSerializer < ActiveModel::Serializer
type 'actor' type 'actor'
attributes :name, :email attributes :name, :email
belongs_to :agency, serializer: ::AMSAgencySerializer
has_many :awards, serializer: ::AMSAwardSerializer
end end
class AMSUserSerializer < ActiveModel::Serializer class AMSUserSerializer < ActiveModel::Serializer
type 'user' type 'user'
@ -30,6 +86,11 @@ RSpec.shared_context 'ams movie class' do
class AMSMovieTypeSerializer < ActiveModel::Serializer class AMSMovieTypeSerializer < ActiveModel::Serializer
type 'movie_type' type 'movie_type'
attributes :name attributes :name
has_many :movies
end
class AMSAdvertisingCampaignSerializer < ActiveModel::Serializer
type 'advertising_campaign'
attributes :name
end end
class AMSMovieSerializer < ActiveModel::Serializer class AMSMovieSerializer < ActiveModel::Serializer
type 'movie' type 'movie'
@ -37,6 +98,7 @@ RSpec.shared_context 'ams movie class' do
has_many :actors has_many :actors
has_one :owner has_one :owner
belongs_to :movie_type belongs_to :movie_type
has_one :advertising_campaign
end end
end end
@ -53,6 +115,7 @@ RSpec.shared_context 'ams movie class' do
a.id = i + 1 a.id = i + 1
a.name = "Test #{a.id}" a.name = "Test #{a.id}"
a.email = "test#{a.id}@test.com" a.email = "test#{a.id}@test.com"
a.agency_id = i
a a
end end
end end
@ -70,6 +133,13 @@ RSpec.shared_context 'ams movie class' do
ams_movie_type ams_movie_type
end end
let(:ams_advertising_campaign) do
campaign = AMSAdvertisingCampaign.new
campaign.id = 1
campaign.name = "Movie is incredible!!"
campaign
end
def build_ams_movies(count) def build_ams_movies(count)
count.times.map do |i| count.times.map do |i|
m = AMSMovie.new m = AMSMovie.new
@ -78,6 +148,7 @@ RSpec.shared_context 'ams movie class' do
m.actors = ams_actors m.actors = ams_actors
m.owner = ams_user m.owner = ams_user
m.movie_type = ams_movie_type m.movie_type = ams_movie_type
m.advertising_campaign = ams_advertising_campaign
m m
end end
end end

View File

@ -26,21 +26,7 @@ RSpec.shared_context 'group class' do
end end
end end
# Person and Group struct
# Namespaced PersonSerializer
before(:context) do
# namespaced model stub
module AppName
module V1
class PersonSerializer
include FastJsonapi::ObjectSerializer
# to test if compute_serializer_name works
end
end
end
end
# Movie and Actor struct
before(:context) do before(:context) do
PersonStruct = Struct.new( PersonStruct = Struct.new(
:id, :first_name, :last_name :id, :first_name, :last_name
@ -57,7 +43,6 @@ RSpec.shared_context 'group class' do
PersonSerializer PersonSerializer
Group Group
GroupSerializer GroupSerializer
AppName::V1::PersonSerializer
PersonStruct PersonStruct
GroupStruct GroupStruct
] ]
@ -66,25 +51,6 @@ RSpec.shared_context 'group class' do
end end
end end
let(:group_struct) do
group = GroupStruct.new
group[:id] = 1
group[:name] = 'Group 1'
group[:groupees] = []
person = PersonStruct.new
person[:id] = 1
person[:last_name] = "Last Name 1"
person[:first_name] = "First Name 1"
child_group = GroupStruct.new
child_group[:id] = 2
child_group[:name] = 'Group 2'
group.groupees = [person, child_group]
group
end
let(:group) do let(:group) do
group = Group.new group = Group.new
group.id = 1 group.id = 1

View File

@ -13,11 +13,12 @@ RSpec.shared_context 'movie class' do
:movie_type_id :movie_type_id
def actors def actors
actor_ids.map do |id| actor_ids.map.with_index do |id, i|
a = Actor.new a = Actor.new
a.id = id a.id = id
a.name = "Test #{a.id}" a.name = "Test #{a.id}"
a.email = "test#{a.id}@test.com" a.email = "test#{a.id}@test.com"
a.agency_id = i
a a
end end
end end
@ -26,20 +27,107 @@ RSpec.shared_context 'movie class' do
mt = MovieType.new mt = MovieType.new
mt.id = movie_type_id mt.id = movie_type_id
mt.name = 'Episode' mt.name = 'Episode'
mt.movie_ids = [id]
mt mt
end end
def advertising_campaign_id
1
end
def advertising_campaign
ac = AdvertisingCampaign.new
ac.id = 1
ac.movie_id = id
ac.name = "Movie #{name} is incredible!!"
ac
end
def cache_key def cache_key
"#{id}" "#{id}"
end end
def url
"http://movies.com/#{id}"
end
end end
class Actor class Actor
attr_accessor :id, :name, :email attr_accessor :id, :name, :email, :agency_id
def agency
Agency.new.tap do |a|
a.id = agency_id
a.name = "Test Agency #{agency_id}"
a.state_id = 1
end
end
def awards
award_ids.map do |i|
Award.new.tap do |a|
a.id = i
a.title = "Test Award #{i}"
a.actor_id = id
end
end
end
def award_ids
[id * 9, id * 9 + 1]
end
def url
"http://movies.com/actors/#{id}"
end
end
class AdvertisingCampaign
attr_accessor :id, :name, :movie_id
end
class Agency
attr_accessor :id, :name, :state_id
def state
State.new.tap do |s|
s.id = state_id
s.name = "Test State #{state_id}"
s.agency_ids = [id]
end
end
end
class Award
attr_accessor :id, :title, :actor_id
end
class State
attr_accessor :id, :name, :agency_ids
end end
class MovieType class MovieType
attr_accessor :id, :name attr_accessor :id, :name, :movie_ids
def movies
movie_ids.map.with_index do |id, i|
m = Movie.new
m.id = 232
m.name = 'test movie'
m.actor_ids = [1, 2, 3]
m.owner_id = 3
m.movie_type_id = 1
m
end
end
end
class Agency
attr_accessor :id, :name, :actor_ids
end
class Agency
attr_accessor :id, :name, :actor_ids
end end
class Supplier class Supplier
@ -67,6 +155,12 @@ RSpec.shared_context 'movie class' do
has_many :actors has_many :actors
belongs_to :owner, record_type: :user belongs_to :owner, record_type: :user
belongs_to :movie_type belongs_to :movie_type
has_one :advertising_campaign
end
class MovieWithoutIdStructSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :release_year
end end
class CachingMovieSerializer class CachingMovieSerializer
@ -95,12 +189,41 @@ RSpec.shared_context 'movie class' do
include FastJsonapi::ObjectSerializer include FastJsonapi::ObjectSerializer
set_type :actor set_type :actor
attributes :name, :email attributes :name, :email
belongs_to :agency
has_many :awards
belongs_to :agency
end
class AgencySerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name
belongs_to :state
has_many :actors
end
class AwardSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :title
belongs_to :actor
end
class StateSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name
has_many :agency
end
class AdvertisingCampaignSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name
belongs_to :movie
end end
class MovieTypeSerializer class MovieTypeSerializer
include FastJsonapi::ObjectSerializer include FastJsonapi::ObjectSerializer
set_type :movie_type set_type :movie_type
attributes :name attributes :name
has_many :movies
end end
class MovieSerializerWithAttributeBlock class MovieSerializerWithAttributeBlock
@ -112,6 +235,21 @@ RSpec.shared_context 'movie class' do
end end
end end
class MovieSerializerWithAttributeBlock
include FastJsonapi::ObjectSerializer
set_type :movie
attributes :name, :release_year
attribute :title_with_year do |record|
"#{record.name} (#{record.release_year})"
end
end
class AgencySerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name
has_many :actors
end
class SupplierSerializer class SupplierSerializer
include FastJsonapi::ObjectSerializer include FastJsonapi::ObjectSerializer
set_type :supplier set_type :supplier
@ -149,10 +287,13 @@ RSpec.shared_context 'movie class' do
:actors, :actors,
:owner_id, :owner_id,
:owner, :owner,
:movie_type_id :movie_type_id,
:advertising_campaign_id
) )
ActorStruct = Struct.new(:id, :name, :email) ActorStruct = Struct.new(:id, :name, :email, :agency_id, :award_ids)
MovieWithoutIdStruct = Struct.new(:name, :release_year)
AgencyStruct = Struct.new(:id, :name, :actor_ids)
end end
after(:context) do after(:context) do
@ -163,11 +304,17 @@ RSpec.shared_context 'movie class' do
ActorSerializer ActorSerializer
MovieType MovieType
MovieTypeSerializer MovieTypeSerializer
MovieSerializerWithAttributeBlock
AppName::V1::MovieSerializer AppName::V1::MovieSerializer
MovieStruct MovieStruct
ActorStruct ActorStruct
MovieWithoutIdStruct
HyphenMovieSerializer HyphenMovieSerializer
MovieWithoutIdStructSerializer
Agency
AgencyStruct
AgencySerializer
AdvertisingCampaign
AdvertisingCampaignSerializer
] ]
classes_to_remove.each do |klass_name| classes_to_remove.each do |klass_name|
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name) Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
@ -176,10 +323,12 @@ RSpec.shared_context 'movie class' do
let(:movie_struct) do let(:movie_struct) do
agency = AgencyStruct
actors = [] actors = []
3.times.each do |id| 3.times.each do |id|
actors << ActorStruct.new(id, id.to_s, id.to_s) actors << ActorStruct.new(id, id.to_s, id.to_s, id, [id])
end end
m = MovieStruct.new m = MovieStruct.new
@ -193,6 +342,10 @@ RSpec.shared_context 'movie class' do
m m
end end
let(:movie_struct_without_id) do
MovieWithoutIdStruct.new('struct without id', 2018)
end
let(:movie) do let(:movie) do
m = Movie.new m = Movie.new
m.id = 232 m.id = 232
@ -203,6 +356,25 @@ RSpec.shared_context 'movie class' do
m m
end end
let(:actor) do
Actor.new.tap do |a|
a.id = 234
a.name = 'test actor'
a.email = 'test@test.com'
a.agency_id = 432
end
end
let(:movie_type) do
movie
mt = MovieType.new
mt.id = movie.movie_type_id
mt.name = 'Foreign Thriller'
mt.movie_ids = [movie.id]
mt
end
let(:supplier) do let(:supplier) do
s = Supplier.new s = Supplier.new
s.id = 1 s.id = 1

View File

@ -0,0 +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
end
end
RSpec.shared_examples 'returning key transformed hash' do |movie_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)
end
end

View File

@ -1,3 +1,4 @@
require 'active_record'
require 'fast_jsonapi' require 'fast_jsonapi'
require 'rspec-benchmark' require 'rspec-benchmark'
require 'byebug' require 'byebug'
@ -7,6 +8,7 @@ require 'jsonapi/serializable'
require 'jsonapi-serializers' require 'jsonapi-serializers'
Dir[File.dirname(__FILE__) + '/shared/contexts/*.rb'].each {|file| require file } Dir[File.dirname(__FILE__) + '/shared/contexts/*.rb'].each {|file| require file }
Dir[File.dirname(__FILE__) + '/shared/examples/*.rb'].each {|file| require file }
RSpec.configure do |config| RSpec.configure do |config|
config.include RSpec::Benchmark::Matchers config.include RSpec::Benchmark::Matchers