commit
6a08165347
@ -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
|
||||
|
||||
@ -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
|
||||
43
README.md
43
README.md
@ -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`
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 || {}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
29
lib/fast_jsonapi/scalar.rb
Normal file
29
lib/fast_jsonapi/scalar.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
module FastJsonapi
|
||||
VERSION = "1.5"
|
||||
VERSION = '1.6.0'
|
||||
end
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user