Merge pull request #18 from fast-jsonapi/dev

1.6.0
This commit is contained in:
Kevin Pheasey 2019-11-04 17:38:13 -05:00 committed by GitHub
commit 6a08165347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 304 additions and 115 deletions

View File

@ -4,5 +4,12 @@ rvm:
- 2.3.6
- 2.4.3
- 2.5.0
- 2.6
before_install:
- "travis_retry gem update --system 2.7.9"
- "travis_retry gem install bundler -v '1.17.3'"
install: BUNDLER_VERSION=1.17.3 bundle install --path=vendor/bundle --retry=3 --jobs=3
script:
- bundle exec rspec

View File

@ -4,13 +4,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.6.0] - 2019-11-04
### Added
- Allow relationship links to be delcared as a method ([#2](https://github.com/fast-jsonapi/fast_jsonapi/pull/2))
- Test against Ruby 2.6 ([#1](https://github.com/fast-jsonapi/fast_jsonapi/pull/1))
- Include `data` key when lazy-loaded relationships are included ([#10](https://github.com/fast-jsonapi/fast_jsonapi/pull/10))
- Conditional links [#15](https://github.com/fast-jsonapi/fast_jsonapi/pull/15)
- Include params on set_id block [#16](https://github.com/fast-jsonapi/fast_jsonapi/pull/16)
### Changed
- Optimize SerializationCore.get_included_records calculates remaining_items only once ([#4](https://github.com/fast-jsonapi/fast_jsonapi/pull/4))
- Optimize SerializtionCore.parse_include_item by mapping in place ([#5](https://github.com/fast-jsonapi/fast_jsonapi/pull/5))
- Define ObjectSerializer.set_key_transform mapping as a constant ([#7](https://github.com/fast-jsonapi/fast_jsonapi/pull/7))
- Optimize SerializtionCore.remaining_items by taking from original array ([#9](https://github.com/fast-jsonapi/fast_jsonapi/pull/9))
- Optimize ObjectSerializer.deep_symbolize by using each_with_object instead of Hash[map] ([#6](https://github.com/fast-jsonapi/fast_jsonapi/pull/6))
[Unreleased]: https://github.com/fast-jsonapi/fast_jsonapi/compare/dev...HEAD
[1.6.0]: https://github.com/fast-jsonapi/fast_jsonapi/compare/1.5...1.6.0

View File

@ -4,6 +4,10 @@
A lightning fast [JSON:API](http://jsonapi.org/) serializer for Ruby Objects.
Note: this gem deals only with implementing the JSON:API spec. If your API
responses are not formatted according to the JSON:API spec, this library will
not work for you.
# Performance Comparison
We compare serialization times with Active Model Serializer as part of RSpec performance tests included on this library. We want to ensure that with every change on this library, serialization time is at least `25 times` faster than Active Model Serializers on up to current benchmark of 1000 records. Please read the [performance document](https://github.com/Netflix/fast_jsonapi/blob/master/performance_methodology.md) for any questions related to methodology.
@ -101,6 +105,17 @@ movie.actor_ids = [1, 2, 3]
movie.owner_id = 3
movie.movie_type_id = 1
movie
movies =
2.times.map do |i|
m = Movie.new
m.id = i + 1
m.name = "test movie #{i}"
m.actor_ids = [1, 2, 3]
m.owner_id = 3
m.movie_type_id = 1
m
end
```
### Object Serialization
@ -263,6 +278,12 @@ class MovieSerializer
end
```
Relationship links can also be configured to be defined as a method on the object.
```ruby
has_many :actors, links: :actor_relationship_links
```
This will create a `self` reference for the relationship, and a `related` link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the `lazy_load_data` option:
```ruby
@ -304,7 +325,7 @@ options[:links] = {
prev: '...'
}
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
MovieSerializer.new([movie, movie], options).serialized_json
MovieSerializer.new(movies, options).serialized_json
```
### Collection Serialization
@ -316,15 +337,15 @@ options[:links] = {
next: '...',
prev: '...'
}
hash = MovieSerializer.new([movie, movie], options).serializable_hash
json_string = MovieSerializer.new([movie, movie], options).serialized_json
hash = MovieSerializer.new(movies, options).serializable_hash
json_string = MovieSerializer.new(movies, 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 this option is not provided or `nil` autodetect 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
@ -360,13 +381,19 @@ related to a current authenticated user. The `options[:params]` value covers the
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.
Leveraging the new params is easy, when you define a custom id, attribute or
relationship with a block you opt-in to using params by adding it as a block
parameter.
```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer
set_id do |movie, params|
# in here, params is a hash containing the `:admin` key
params[:admin] ? movie.owner_id : "movie-#{movie.id}"
end
attributes :name, :year
attribute :can_view_early do |movie, params|
# in here, params is a hash containing the `:current_user` key
@ -518,7 +545,7 @@ Option | Purpose | Example
------------ | ------------- | -------------
set_type | Type name of Object | ```set_type :movie ```
key | Key of Object | ```belongs_to :owner, key: :user ```
set_id | ID of Object | ```set_id :owner_id ``` or ```set_id { |record| "#{record.name.downcase}-#{record.id}" }```
set_id | ID of Object | ```set_id :owner_id ``` or ```set_id { \|record, params\| params[:admin] ? record.id : "#{record.name.downcase}-#{record.id}" }```
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 (If block is provided for the relationship, `id_method_name` is invoked on the return value of the block instead of the resource 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 ```
@ -541,7 +568,7 @@ Skylight relies on `ActiveSupport::Notifications` to track these two core method
require 'fast_jsonapi/instrumentation'
```
The two instrumented notifcations are supplied by these two constants:
The two instrumented notifications are supplied by these two constants:
* `FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION`
* `FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION`

View File

@ -1,29 +1,5 @@
require 'fast_jsonapi/scalar'
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.abs == 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
class Attribute < Scalar; end
end

View File

@ -1,18 +1,5 @@
require 'fast_jsonapi/scalar'
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
class Link < Scalar; end
end

View File

@ -17,6 +17,12 @@ module FastJsonapi
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
TRANSFORMS_MAPPING = {
camel: :camelize,
camel_lower: [:camelize, :lower],
dash: :dasherize,
underscore: :underscore
}.freeze
included do
# Set record_type based on the name of the serializer class
@ -43,7 +49,7 @@ module FastJsonapi
return serializable_hash unless @resource
serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params)
serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @includes, @params)
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
serializable_hash
end
@ -55,7 +61,7 @@ module FastJsonapi
included = []
fieldset = @fieldsets[self.class.record_type.to_sym]
@resource.each do |record|
data << self.class.record_hash(record, fieldset, @params)
data << self.class.record_hash(record, fieldset, @includes, @params)
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
end
@ -86,16 +92,16 @@ module FastJsonapi
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
if options[:include].present?
@includes = options[:include].delete_if(&:blank?).map(&:to_sym)
@includes = options[:include].reject(&:blank?).map(&:to_sym)
self.class.validate_includes!(@includes)
end
end
def deep_symbolize(collection)
if collection.is_a? Hash
Hash[collection.map do |k, v|
[k.to_sym, deep_symbolize(v)]
end]
collection.each_with_object({}) do |(k, v), hsh|
hsh[k.to_sym] = deep_symbolize(v)
end
elsif collection.is_a? Array
collection.map { |i| deep_symbolize(i) }
else
@ -130,20 +136,14 @@ module FastJsonapi
return @reflected_record_type if defined?(@reflected_record_type)
@reflected_record_type ||= begin
if self.name.end_with?('Serializer')
if self.name && self.name.end_with?('Serializer')
self.name.split('::').last.chomp('Serializer').underscore.to_sym
end
end
end
def set_key_transform(transform_name)
mapping = {
camel: :camelize,
camel_lower: [:camelize, :lower],
dash: :dasherize,
underscore: :underscore
}
self.transform_method = mapping[transform_name.to_sym]
self.transform_method = TRANSFORMS_MAPPING[transform_name.to_sym]
# ensure that the record type is correctly transformed
if record_type
@ -285,21 +285,26 @@ module FastJsonapi
{}
end
def link(link_name, link_method_name = nil, &block)
# def link(link_name, link_method_name = nil, &block)
def link(*params, &block)
self.data_links = {} if self.data_links.nil?
link_method_name = link_name if link_method_name.nil?
options = params.last.is_a?(Hash) ? params.pop : {}
link_name = params.first
link_method_name = params[-1]
key = run_key_transform(link_name)
self.data_links[key] = Link.new(
key: key,
method: block || link_method_name
method: block || link_method_name,
options: options
)
end
def validate_includes!(includes)
return if includes.blank?
includes.detect do |include_item|
includes.each do |include_item|
klass = self
parse_include_item(include_item).each do |parsed_include|
relationships_to_serialize = klass.relationships_to_serialize || {}

View File

@ -34,12 +34,12 @@ module FastJsonapi
@lazy_load_data = lazy_load_data
end
def serialize(record, serialization_params, output_hash)
def serialize(record, included, serialization_params, output_hash)
if include_relationship?(record, serialization_params)
empty_case = relationship_type == :has_many ? [] : nil
output_hash[key] = {}
unless lazy_load_data
unless (lazy_load_data && !included)
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case
end
add_links_hash(record, serialization_params, output_hash) if links.present?
@ -104,8 +104,12 @@ module FastJsonapi
end
def add_links_hash(record, params, output_hash)
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
Link.new(key: key, method: method).serialize(record, params, hash)\
if links.is_a?(Symbol)
output_hash[key][:links] = record.public_send(links)
else
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
Link.new(key: key, method: method).serialize(record, params, hash)\
end
end
end

View File

@ -0,0 +1,29 @@
module FastJsonapi
class Scalar
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 conditionally_allowed?(record, serialization_params)
output_hash[key] = if method.is_a?(Proc)
method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
else
record.public_send(method)
end
end
end
def conditionally_allowed?(record, serialization_params)
if conditional_proc.present?
conditional_proc.call(record, serialization_params)
else
true
end
end
end
end

View File

@ -44,17 +44,21 @@ module FastJsonapi
def attributes_hash(record, fieldset = nil, params = {})
attributes = attributes_to_serialize
attributes = attributes.slice(*fieldset) if fieldset.present?
attributes = {} if fieldset == []
attributes.each_with_object({}) do |(_k, attribute), hash|
attribute.serialize(record, params, hash)
end
end
def relationships_hash(record, relationships = nil, fieldset = nil, params = {})
def relationships_hash(record, relationships = nil, fieldset = nil, includes_list = nil, params = {})
relationships = relationships_to_serialize if relationships.nil?
relationships = relationships.slice(*fieldset) if fieldset.present?
relationships = {} if fieldset == []
relationships.each_with_object({}) do |(_k, relationship), hash|
relationship.serialize(record, params, hash)
relationships.each_with_object({}) do |(key, relationship), hash|
included = includes_list.present? && includes_list.include?(key)
relationship.serialize(record, included, params, hash)
end
end
@ -62,31 +66,31 @@ module FastJsonapi
meta_to_serialize.call(record, params)
end
def record_hash(record, fieldset, params = {})
def record_hash(record, fieldset, includes_list, params = {})
if cached
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
temp_hash = id_hash(id_from_record(record), record_type, true)
temp_hash = id_hash(id_from_record(record, params), record_type, true)
temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
temp_hash[:relationships] = {}
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, params) if cachable_relationships_to_serialize.present?
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, includes_list, params) if cachable_relationships_to_serialize.present?
temp_hash[:links] = links_hash(record, params) if data_links.present?
temp_hash
end
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, params)) if uncachable_relationships_to_serialize.present?
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, includes_list, params)) if uncachable_relationships_to_serialize.present?
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
record_hash
else
record_hash = id_hash(id_from_record(record), record_type, true)
record_hash = id_hash(id_from_record(record, params), record_type, true)
record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
record_hash[:relationships] = relationships_hash(record, nil, fieldset, params) if relationships_to_serialize.present?
record_hash[:relationships] = relationships_hash(record, nil, fieldset, includes_list, params) if relationships_to_serialize.present?
record_hash[:links] = links_hash(record, params) if data_links.present?
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
record_hash
end
end
def id_from_record(record)
return record_id.call(record) if record_id.is_a?(Proc)
def id_from_record(record, params)
return record_id.call(record, params) if record_id.is_a?(Proc)
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
@ -99,15 +103,14 @@ module FastJsonapi
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 }
include_item.to_s.split('.').map!(&:to_sym)
end
def remaining_items(items)
return unless items.size > 1
items_copy = items.dup
items_copy.delete_at(0)
[items_copy.join('.').to_sym]
[items[1..-1].join('.').to_sym]
end
# includes handler
@ -116,6 +119,8 @@ module FastJsonapi
includes_list.sort.each_with_object([]) do |include_item, included_records|
items = parse_include_item(include_item)
remaining_items = remaining_items(items)
items.each do |item|
next unless relationships_to_serialize && relationships_to_serialize[item]
relationship_item = relationships_to_serialize[item]
@ -136,17 +141,17 @@ module FastJsonapi
serializer = self.compute_serializer_name(inc_obj.class.name.demodulize.to_sym).to_s.constantize
end
if remaining_items(items)
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params)
if remaining_items.present?
serializer_records = serializer.get_included_records(inc_obj, remaining_items, known_included_objects, fieldsets, params)
included_records.concat(serializer_records) unless serializer_records.empty?
end
code = "#{record_type}_#{serializer.id_from_record(inc_obj)}"
code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}"
next if known_included_objects.key?(code)
known_included_objects[code] = inc_obj
included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], params)
included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], includes_list, params)
end
end
end

View File

@ -1,3 +1,3 @@
module FastJsonapi
VERSION = "1.5"
VERSION = '1.6.0'
end

View File

@ -10,7 +10,8 @@ describe FastJsonapi::ObjectSerializer do
options[:meta] = { total: 2 }
options[:include] = [:actors]
@serializer = MovieSerializer.new([movie, movie], options)
movies = build_movies(2)
@serializer = MovieSerializer.new(movies, options)
end
context 'serializable_hash' do

View File

@ -24,7 +24,8 @@ describe FastJsonapi::ObjectSerializer do
options[:meta] = { total: 2 }
options[:include] = [:actors]
@serializer = MovieSerializer.new([movie, movie], options)
movies = build_movies(2)
@serializer = MovieSerializer.new(movies, options)
end
context 'serializable_hash' do

View File

@ -16,7 +16,8 @@ describe FastJsonapi::ObjectSerializer do
options[:links] = { self: 'self' }
options[:include] = [:actors]
serializable_hash = CachingMovieSerializer.new([movie, movie], options).serializable_hash
movies = build_movies(2)
serializable_hash = CachingMovieSerializer.new(movies, options).serializable_hash
expect(serializable_hash[:data].length).to eq 2
expect(serializable_hash[:data][0][:relationships].length).to eq 3

View File

@ -193,7 +193,10 @@ describe FastJsonapi::ObjectSerializer do
end
describe '#set_id' do
subject(:serializable_hash) { MovieSerializer.new(resource).serializable_hash }
let(:params) { {} }
subject(:serializable_hash) do
MovieSerializer.new(resource, { params: params }).serializable_hash
end
context 'method name' do
before do
@ -213,7 +216,7 @@ describe FastJsonapi::ObjectSerializer do
end
context 'when an array of records is given' do
let(:resource) { [movie, movie] }
let(:resource) { build_movies(2) }
it 'returns correct hash which id equals owner_id' do
expect(serializable_hash[:data][0][:id].to_i).to eq movie.owner_id
@ -223,8 +226,12 @@ describe FastJsonapi::ObjectSerializer do
end
context 'with block' do
let(:params) { { prefix: 'movie' } }
before do
MovieSerializer.set_id { |record| "movie-#{record.owner_id}" }
MovieSerializer.set_id do |record, params|
"#{params[:prefix]}-#{record.owner_id}"
end
end
after do
@ -240,7 +247,7 @@ describe FastJsonapi::ObjectSerializer do
end
context 'when an array of records is given' do
let(:resource) { [movie, movie] }
let(:resource) { build_movies(2) }
it 'returns correct hash which id equals movie-id' do
expect(serializable_hash[:data][0][:id]).to eq "movie-#{movie.owner_id}"
@ -400,10 +407,30 @@ describe FastJsonapi::ObjectSerializer do
expect(action_serializable_hash[:data][:links][:url]).to eq "/action-movie/#{movie.id}"
end
end
describe 'optional links' do
subject(:downloadable_serializable_hash) { OptionalDownloadableMovieSerializer.new(movie, params).serializable_hash }
context 'when the link is provided' do
let(:params) { { params: { signed_url: signed_url } } }
let(:signed_url) { 'http://example.com/download_link?signature=abcdef' }
it 'includes the link' do
expect(downloadable_serializable_hash[:data][:links][:download]).to eq signed_url
end
end
context 'when the link is not provided' do
let(:params) { { params: {} } }
it 'does not include the link' do
expect(downloadable_serializable_hash[:data][:links]).to_not have_key(:download)
end
end
end
end
describe '#key_transform' do
subject(:hash) { movie_serializer_class.new([movie, movie], include: [:movie_type]).serializable_hash }
subject(:hash) { movie_serializer_class.new(build_movies(2), include: [:movie_type]).serializable_hash }
let(:movie_serializer_class) { "#{key_transform}_movie_serializer".classify.constantize }

View File

@ -22,6 +22,18 @@ describe FastJsonapi::ObjectSerializer do
expect(hash[:data][:relationships].keys.sort).to eq %i[actors advertising_campaign]
end
it 'returns no fields when none are specified' do
hash = MovieSerializer.new(movie, fields: { movie: [] }).serializable_hash
expect(hash[:data][:attributes].keys).to eq []
end
it 'returns no relationships when none are specified' do
hash = MovieSerializer.new(movie, fields: { movie: [] }).serializable_hash
expect(hash[:data][:relationships].keys).to eq []
end
it 'only returns specified fields for included relationships' do
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash
@ -45,4 +57,25 @@ describe FastJsonapi::ObjectSerializer do
expect(hash[:included][3][:relationships].keys.sort).to eq %i[movie]
end
context 'with no included fields specified' do
let(:fields) do
{
movie: %i[name actors advertising_campaign],
actor: []
}
end
it 'returns no fields for included relationships when none are specified' do
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
expect(hash[:included][2][:attributes].keys).to eq []
end
it 'returns no relationships when none are specified' do
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
expect(hash[:included][2][:relationships].keys).to eq []
end
end
end

View File

@ -67,5 +67,46 @@ describe FastJsonapi::ObjectSerializer do
expect(actor_hash).not_to have_key(:data)
end
end
context "including lazy loaded relationships" do
before(:context) do
class LazyLoadingMovieSerializer < MovieSerializer
has_many :actors, lazy_load_data: true, links: {
related: :actors_relationship_url
}
end
end
let(:serializer) { LazyLoadingMovieSerializer.new(movie, include: [:actors]) }
let(:actor_hash) { hash[:data][:relationships][:actors] }
it "includes the :data key" do
expect(actor_hash).to be_present
expect(actor_hash).to have_key(:data)
end
end
context "relationship links defined by a method on the object" do
before(:context) do
class Movie
def relationship_links
{ self: "http://movies.com/#{id}/relationships/actors" }
end
end
class LinksPassingMovieSerializer < MovieSerializer
has_many :actors, links: :relationship_links
end
end
let(:serializer) { LinksPassingMovieSerializer.new(movie) }
let(:links) { hash[:data][:relationships][:actors][:links] }
let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" }
it "generates relationship links in the object" do
expect(links).to be_present
expect(links[:self]).to eq(relationship_url)
end
end
end
end

View File

@ -4,13 +4,15 @@ describe FastJsonapi::ObjectSerializer do
include_context 'movie class'
include_context 'group class'
let(:movies) { build_movies(2) }
context 'when testing instance methods of object serializer' do
it 'returns correct hash when serializable_hash is called' do
options = {}
options[:meta] = { total: 2 }
options[:links] = { self: 'self' }
options[:include] = [:actors]
serializable_hash = MovieSerializer.new([movie, movie], options).serializable_hash
serializable_hash = MovieSerializer.new(movies, options).serializable_hash
expect(serializable_hash[:data].length).to eq 2
expect(serializable_hash[:data][0][:relationships].length).to eq 4
@ -58,7 +60,7 @@ describe FastJsonapi::ObjectSerializer do
it 'returns correct number of records when serialized_json is called for an array' do
options = {}
options[:meta] = { total: 2 }
json = MovieSerializer.new([movie, movie], options).serialized_json
json = MovieSerializer.new(movies, options).serialized_json
serializable_hash = JSON.parse(json)
expect(serializable_hash['data'].length).to eq 2
expect(serializable_hash['meta']).to be_instance_of(Hash)
@ -124,7 +126,7 @@ describe FastJsonapi::ObjectSerializer do
end
it 'returns multiple records' do
json_hash = MovieSerializer.new([movie, movie]).as_json
json_hash = MovieSerializer.new(movies).as_json
expect(json_hash['data'].length).to eq 2
end
@ -139,6 +141,13 @@ describe FastJsonapi::ObjectSerializer do
options = {}
options[:meta] = { total: 2 }
options[:include] = [:blah_blah]
expect { MovieSerializer.new(movies, options).serializable_hash }.to raise_error(ArgumentError)
end
it 'returns errors when serializing with non-existent and existent includes keys' do
options = {}
options[:meta] = { total: 2 }
options[:include] = [:actors, :blah_blah]
expect { MovieSerializer.new([movie, movie], options).serializable_hash }.to raise_error(ArgumentError)
end
@ -148,13 +157,19 @@ describe FastJsonapi::ObjectSerializer do
expect { MovieSerializer.new(movie, options) }.not_to raise_error
end
it 'does not throw an error with non-empty string array includes keys' do
options = {}
options[:include] = ['actors', 'owner']
expect { MovieSerializer.new(movie, options) }.not_to raise_error
end
it 'returns keys when serializing with empty string/nil array includes key' do
options = {}
options[:meta] = { total: 2 }
options[:include] = ['']
expect(MovieSerializer.new([movie, movie], options).serializable_hash.keys).to eq [:data, :meta]
expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta]
options[:include] = [nil]
expect(MovieSerializer.new([movie, movie], options).serializable_hash.keys).to eq [:data, :meta]
expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta]
end
end
@ -314,13 +329,6 @@ describe FastJsonapi::ObjectSerializer do
expect(BlahBlahSerializer.record_type).to be :blah_blah
end
it 'shouldnt set default_type for a serializer that doesnt follow convention' do
class BlahBlahSerializerBuilder
include FastJsonapi::ObjectSerializer
end
expect(BlahBlahSerializerBuilder.record_type).to be_nil
end
it 'should set default_type for a namespaced serializer' do
module V1
class BlahSerializer
@ -329,6 +337,20 @@ describe FastJsonapi::ObjectSerializer do
end
expect(V1::BlahSerializer.record_type).to be :blah
end
it 'shouldnt set default_type for a serializer that doesnt follow convention' do
class BlahBlahSerializerBuilder
include FastJsonapi::ObjectSerializer
end
expect(BlahBlahSerializerBuilder.record_type).to be_nil
end
it 'shouldnt set default_type for an anonymous serializer' do
serializer_class = Class.new do
include FastJsonapi::ObjectSerializer
end
expect(serializer_class.record_type).to be_nil
end
end
context 'when serializing included, serialize any links' do
@ -473,6 +495,16 @@ describe FastJsonapi::ObjectSerializer do
options[:include] = [:actors]
expect(serializable_hash['included']).to be_blank
end
end
end
context 'when include has frozen array' do
let(:options) { { include: [:actors].freeze }}
let(:json) { MovieOptionalRelationshipSerializer.new(movie, options).serialized_json }
it 'does not raise and error' do
expect(json['included']).to_not be_blank
end
end

View File

@ -52,7 +52,7 @@ describe FastJsonapi::ObjectSerializer do
end
it 'returns correct hash when record_hash is called' do
record_hash = MovieSerializer.send(:record_hash, movie, nil)
record_hash = MovieSerializer.send(:record_hash, movie, nil, nil)
expect(record_hash[:id]).to eq movie.id.to_s
expect(record_hash[:type]).to eq MovieSerializer.record_type
expect(record_hash).to have_key(:attributes) if MovieSerializer.attributes_to_serialize.present?

View File

@ -197,6 +197,12 @@ RSpec.shared_context 'movie class' do
link(:url) { |object| "/horror-movie/#{object.id}" }
end
class OptionalDownloadableMovieSerializer < MovieSerializer
link(:download, if: Proc.new { |record, params| params && params[:signed_url] }) do |movie, params|
params[:signed_url]
end
end
class MovieWithoutIdStructSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :release_year
@ -385,6 +391,7 @@ RSpec.shared_context 'movie class' do
ActionMovieSerializer
GenreMovieSerializer
HorrorMovieSerializer
OptionalDownloadableMovieSerializer
Movie
MovieSerializer
Actor