Compare commits

..

1 Commits

Author SHA1 Message Date
Stas SUȘCOV
e9f6ee7c96
Release the intermediary v1.7.3 2020-06-22 15:09:00 +01:00
40 changed files with 275 additions and 619 deletions

View File

@ -4,16 +4,16 @@ on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
strategy:
matrix:
ruby: [2.4, 2.7, '3.0', 3.1, truffleruby-head]
ruby: [2.4, 2.5, 2.6, 2.7]
steps:
- uses: actions/checkout@master
- name: Sets up the Ruby version
uses: ruby/setup-ruby@v1
uses: actions/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
@ -26,15 +26,16 @@ jobs:
- name: Runs code QA and tests
run: bundle exec rake
- name: Publish to Rubygems
- name: Publish to GPR
continue-on-error: true
if: ${{ github.ref == 'refs/heads/master' }}
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
printf -- "---\n:rubygems_api_key: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
gem build *.gemspec
gem push *.gem
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
env:
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
GEM_HOST_API_KEY: ${{secrets.GPR_AUTH_TOKEN}}
OWNER: fast-jsonapi

7
.gitignore vendored
View File

@ -40,9 +40,6 @@ test.db
# For those who install gems locally to a vendor dir
/vendor
# Don't checkin Gemfile.lock
# See: https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
# Don't checkin Gemfile.lock
# See: http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
Gemfile.lock
# Gem builds
/*.gem

View File

@ -2,10 +2,6 @@ require:
- rubocop-performance
- rubocop-rspec
AllCops:
NewCops: enable
SuggestExtensions: false
Style/FrozenStringLiteralComment:
Enabled: false
@ -42,9 +38,6 @@ Performance/TimesMap:
Exclude:
- 'spec/**/**.rb'
Gemspec/RequiredRubyVersion:
Enabled: false
# TODO: Fix these...
Style/Documentation:
Enabled: false
@ -83,20 +76,3 @@ Naming/PredicateName:
Naming/AccessorMethodName:
Exclude:
- 'lib/**/**.rb'
Style/CaseLikeIf:
Exclude:
- 'lib/fast_jsonapi/object_serializer.rb'
Style/OptionalBooleanParameter:
Exclude:
- 'lib/fast_jsonapi/serialization_core.rb'
- 'lib/fast_jsonapi/relationship.rb'
Lint/DuplicateBranch:
Exclude:
- 'lib/fast_jsonapi/relationship.rb'
Style/DocumentDynamicEvalDefinition:
Exclude:
- 'lib/extensions/has_one.rb'

View File

@ -4,42 +4,6 @@ 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]
- ...
## [2.2.0] - 2021-03-11
### Added
- Proper error is raised on unsupported includes (#125)
### Changed
- Documentation updates (#137 #139 #143 #146)
### Fixed
- Empty relationships are no longer added to serialized doc (#116)
- Ruby v3 compatibility (#160)
## [2.1.0] - 2020-08-30
### Added
- Optional meta field to relationships (#99 #100)
- Support for `params` on cache keys (#117)
### Changed
- Performance instrumentation (#110 #39)
- Improved collection detection (#112)
### Fixed
- Ensure caching correctly incorporates fieldset information into the cache key to prevent incorrect fieldset caching (#90)
- Performance optimizations for nested includes (#103)
## [2.0.0] - 2020-06-22
The project was renamed to `jsonapi-serializer`! (#94)
### Changed
- Remove `ObjectSerializer#serialized_json` (#91)
## [1.7.2] - 2020-05-18
### Fixed
- Relationship#record_type_for does not assign static record type for polymorphic relationships (#83)

View File

@ -2,3 +2,6 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in fast_jsonapi.gemspec
gemspec
# TODO: Remove once the gem is released...
gem 'jsonapi-rspec', github: 'jsonapi-rb/jsonapi-rspec'

View File

@ -1,6 +1,6 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@ -192,7 +192,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,

225
README.md
View File

@ -1,24 +1,12 @@
# JSON:API Serialization Library
# Fast JSON API
## :warning: :construction: v2 (the `master` branch) is in maintenance mode! :construction: :warning:
We'll gladly accept bugfixes and security-related fixes for v2 (the `master` branch), but at this stage, contributions for new features/improvements are welcome only for v3. Please feel free to leave comments in the [v3 Pull Request](https://github.com/jsonapi-serializer/jsonapi-serializer/pull/141).
---
A fast [JSON:API](https://jsonapi.org/) serializer for Ruby Objects.
Previously this project was called **fast_jsonapi**, we forked the project
and renamed it to **jsonapi/serializer** in order to keep it alive.
We would like to thank the Netflix team for the initial work and to all our
contributors and users for the continuous support!
A lightning fast [JSON:API](http://jsonapi.org/) serializer for Ruby Objects.
# Performance Comparison
We compare serialization times with `ActiveModelSerializer` and alternative
implementations as part of performance tests available at
[jsonapi-serializer/comparisons](https://github.com/jsonapi-serializer/comparisons).
[fast-jsonapi/comparisons](https://github.com/fast-jsonapi/comparisons).
We want to ensure that with every
change on this library, serialization time stays significantly faster than
@ -44,9 +32,6 @@ article in the `docs` folder for any questions related to methodology.
* [Specifying a Relationship Serializer](#specifying-a-relationship-serializer)
* [Sparse Fieldsets](#sparse-fieldsets)
* [Using helper methods](#using-helper-methods)
* [Performance Instrumentation](#performance-instrumentation)
* [Deserialization](#deserialization)
* [Migrating from Netflix/fast_jsonapi](#migrating-from-netflixfast_jsonapi)
* [Contributing](#contributing)
@ -63,7 +48,7 @@ article in the `docs` folder for any questions related to methodology.
Add this line to your application's Gemfile:
```ruby
gem 'jsonapi-serializer'
gem 'fast_jsonapi', '~> 1.7.2', git: 'https://github.com/fast-jsonapi/fast_jsonapi'
```
Execute:
@ -94,8 +79,7 @@ end
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
set_type :movie # optional
set_id :owner_id # optional
attributes :name, :year
@ -176,16 +160,12 @@ json_string = MovieSerializer.new(movie).serializable_hash.to_json
```
#### The Optionality of `set_type`
By default fast_jsonapi will try to figure the type based on the name of the serializer class. For example `class MovieSerializer` will automatically have a type of `:movie`. If your serializer class name does not follow this format, you have to manually state the `set_type` at the serializer.
### Key Transforms
By default fast_jsonapi underscores the key names. It supports the same key transforms that are supported by AMS. Here is the syntax of specifying a key transform
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
# Available options :camel, :camel_lower, :dash, :underscore(default)
set_key_transform :camel
end
@ -200,13 +180,13 @@ set_key_transform :underscore # "some_key" => "some_key"
```
### Attributes
Attributes are defined using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute.
Attributes are defined in FastJsonapi using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute.
By default, attributes are read directly from the model property of the same name. In this example, `name` is expected to be a property of the object being serialized:
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attribute :name
end
@ -216,7 +196,7 @@ Custom attributes that must be serialized but do not exist on the model can be d
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attributes :name, :year
@ -230,7 +210,7 @@ The block syntax can also be used to override the property on the object:
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attribute :name do |object|
"#{object.name} Part 2"
@ -242,7 +222,7 @@ Attributes can also use a different name by passing the original method or acces
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attributes :name
@ -251,7 +231,7 @@ end
```
### Links Per Object
Links are defined using the `link` method. By default, links are read directly from the model property of the same name. In this example, `public_url` is expected to be a property of the object being serialized.
Links are defined in FastJsonapi using the `link` method. By default, links 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.
@ -259,29 +239,29 @@ You can also use a block to define a url as shown in `custom_url`. You can acces
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
link :public_url
link :self, :url
link :custom_url do |object|
"https://movies.com/#{object.name}-(#{object.year})"
"http://movies.com/#{object.name}-(#{object.year})"
end
link :personalized_url do |object, params|
"https://movies.com/#{object.name}-#{params[:user].reference_code}"
"http://movies.com/#{object.name}-#{params[:user].reference_code}"
end
end
```
#### Links on a Relationship
You can specify [relationship links](https://jsonapi.org/format/#document-resource-object-relationships) by using the `links:` option on the serializer. Relationship links in JSON API are useful if you want to load a parent document and then load associated documents later due to size constraints (see [related resource links](https://jsonapi.org/format/#document-resource-object-related-resource-links))
You can specify [relationship links](http://jsonapi.org/format/#document-resource-object-relationships) by using the `links:` option on the serializer. Relationship links in JSON API are useful if you want to load a parent document and then load associated documents later due to size constraints (see [related resource links](http://jsonapi.org/format/#document-resource-object-related-resource-links))
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
has_many :actors, links: {
self: :url,
@ -316,7 +296,7 @@ For every resource in the collection, you can include a meta object containing n
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
meta do |movie|
{
@ -326,23 +306,6 @@ class MovieSerializer
end
```
#### Meta on a Relationship
You can specify [relationship meta](https://jsonapi.org/format/#document-resource-object-relationships) by using the `meta:` option on the serializer. Relationship meta in JSON API is useful if you wish to provide non-standard meta-information about the relationship.
Meta can be defined either by passing a static hash or by using Proc to the `meta` key. In the latter case, the record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.
```ruby
class MovieSerializer
include JSONAPI::Serializer
has_many :actors, meta: Proc.new do |movie_record, params|
{ count: movie_record.actors.length }
end
end
```
### Compound Document
Support for top-level and nested included associations through `options[:include]`.
@ -398,10 +361,10 @@ To enable caching, use `cache_options store: <cache_store>`:
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
# use rails cache with a separate namespace and fixed expiry
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
cache_options store: Rails.cache, namespace: 'fast-jsonapi', expires_in: 1.hour
end
```
@ -412,31 +375,12 @@ end
- `options` is everything that was passed to `cache_options` except `store`, so it can be everyhing the cache store supports
- `&block` should be executed to fetch new data if cache is empty
So for the example above it will call the cache instance like this:
So for the example above, FastJsonapi will call the cache instance like this:
```ruby
Rails.cache.fetch(record, namespace: 'jsonapi-serializer', expires_in: 1.hour) { ... }
Rails.cache.fetch(record, namespace: 'fast-jsonapi, expires_in: 1.hour) { ... }
```
#### Caching and Sparse Fieldsets
If caching is enabled and fields are provided to the serializer, the fieldset will be appended to the cache key's namespace.
For example, given the following serializer definition and instance:
```ruby
class ActorSerializer
include JSONAPI::Serializer
attributes :first_name, :last_name
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end
serializer = ActorSerializer.new(actor, { fields: { actor: [:first_name] } })
```
The following cache namespace will be generated: `'jsonapi-serializer-fieldset:first_name'`.
### Params
In some cases, attribute values might require more information than what is
@ -451,7 +395,7 @@ parameter.
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
set_id do |movie, params|
# in here, params is a hash containing the `:admin` key
@ -466,7 +410,7 @@ class MovieSerializer
belongs_to :primary_agent do |movie, params|
# in here, params is a hash containing the `:current_user` key
params[:current_user]
params[:current_user].is_employee? ? true : false
end
end
@ -485,7 +429,7 @@ Conditional attributes can be defined by passing a Proc to the `if` key on the `
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attributes :name, :year
attribute :release_year, if: Proc.new { |record|
@ -497,13 +441,6 @@ class MovieSerializer
# The director will be serialized only if the :admin key of params is true
params && params[:admin] == true
}
# Custom attribute `name_year` will only be serialized if both `name` and `year` fields are present
attribute :name_year, if: Proc.new { |record|
record.name.present? && record.year.present?
} do |object|
"#{object.name} - #{object.year}"
end
end
# ...
@ -518,7 +455,7 @@ Conditional relationships can be defined by passing a Proc to the `if` key. Retu
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
# Actors will only be serialized if the record has any associated actors
has_many :actors, if: Proc.new { |record| record.actors.any? }
@ -539,7 +476,7 @@ In many cases, the relationship can automatically detect the serializer to use.
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
# resolves to StudioSerializer
belongs_to :studio
@ -552,7 +489,7 @@ At other times, such as when a property name differs from the class name, you ma
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
# resolves to MovieStudioSerializer
belongs_to :studio, serializer: :movie_studio
@ -565,7 +502,7 @@ For more advanced cases, such as polymorphic relationships and Single Table Inhe
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
has_many :actors, serializer: Proc.new do |record, params|
if record.comedian?
@ -585,7 +522,7 @@ Attributes and relationships can be selectively returned per record type by usin
```ruby
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attributes :name, :year
end
@ -616,7 +553,7 @@ module AvatarHelper
end
class UserSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
include AvatarHelper # mixes in your helper method as class method
@ -641,7 +578,7 @@ module AvatarHelper
end
class UserSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
extend AvatarHelper # mixes in your helper method as class method
@ -671,27 +608,36 @@ serializer | Set custom Serializer for a relationship | `has_many :actors, seria
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 }`
### Performance Instrumentation
### Instrumentation
Performance instrumentation is available by using the
`active_support/notifications`.
To enable it, include the module in your serializer class:
`fast_jsonapi` also has builtin [Skylight](https://www.skylight.io/) integration. To enable, add the following to an initializer:
```ruby
require 'jsonapi/serializer'
require 'jsonapi/serializer/instrumentation'
class MovieSerializer
include JSONAPI::Serializer
include JSONAPI::Serializer::Instrumentation
# ...
end
require 'fast_jsonapi/instrumentation/skylight'
```
[Skylight](https://www.skylight.io/) integration is also available and
supported by us, follow the Skylight documentation to enable it.
Skylight relies on `ActiveSupport::Notifications` to track these two core methods. If you would like to use these notifications without using Skylight, simply require the instrumentation integration:
```ruby
require 'fast_jsonapi/instrumentation'
```
The two instrumented notifications are supplied by these two constants:
* `FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION`
* `FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION`
It is also possible to instrument one method without the other by using one of the following require statements:
```ruby
require 'fast_jsonapi/instrumentation/serializable_hash'
require 'fast_jsonapi/instrumentation/serialized_json'
```
Same goes for the Skylight integration:
```ruby
require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash'
require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json'
```
### Running Tests
The project has and requires unit tests, functional tests and performance
@ -701,61 +647,6 @@ tests. To run tests use the following command:
rspec
```
## Deserialization
We currently do not support deserialization, but we recommend to use any of the next gems:
### [JSONAPI.rb](https://github.com/stas/jsonapi.rb)
This gem provides the next features alongside deserialization:
- Collection meta
- Error handling
- Includes and sparse fields
- Filtering and sorting
- Pagination
## Migrating from Netflix/fast_jsonapi
If you come from [Netflix/fast_jsonapi](https://github.com/Netflix/fast_jsonapi), here is the instructions to switch.
### Modify your Gemfile
```diff
- gem 'fast_jsonapi'
+ gem 'jsonapi-serializer'
```
### Replace all constant references
```diff
class MovieSerializer
- include FastJsonapi::ObjectSerializer
+ include JSONAPI::Serializer
end
```
### Replace removed methods
```diff
- json_string = MovieSerializer.new(movie).serialized_json
+ json_string = MovieSerializer.new(movie).serializable_hash.to_json
```
### Replace require references
```diff
- require 'fast_jsonapi'
+ require 'jsonapi/serializer'
```
### Update your cache options
See [docs](https://github.com/jsonapi-serializer/jsonapi-serializer#caching).
```diff
- cache_options enabled: true, cache_length: 12.hours
+ cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
```
## Contributing
Please follow the instructions we provide as part of the issue and
@ -763,4 +654,4 @@ pull request creation processes.
This project is intended to be a safe, welcoming space for collaboration, and
contributors are expected to adhere to the
[Contributor Covenant](https://contributor-covenant.org) code of conduct.
[Contributor Covenant](http://contributor-covenant.org) code of conduct.

View File

@ -13,7 +13,7 @@ require 'oj'
require 'fast_jsonapi'
class BaseSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
def to_json
Oj.dump(serializable_hash)

View File

@ -23,7 +23,7 @@ cases.
We came up with patterns that we can rely upon such as:
* We always use [JSON:API](https://jsonapi.org/) for our APIs
* We always use [JSON:API](http://jsonapi.org/) for our APIs
* We almost always serialize a homogenous list of objects (Example: An array of
movies)

View File

@ -1,23 +1,34 @@
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'jsonapi/serializer/version'
require 'fast_jsonapi/version'
Gem::Specification.new do |gem|
gem.name = 'jsonapi-serializer'
gem.version = JSONAPI::Serializer::VERSION
gem.name = 'fast_jsonapi'
gem.version = FastJsonapi::VERSION
gem.authors = ['JSON:API Serializer Community']
gem.authors = [
'Shishir Kakaraddi',
'Srinivas Raghunathan',
'Adam Gross',
'github/fast-jsonapi community'
]
gem.email = ''
gem.summary = 'Fast JSON:API serialization library'
gem.description = 'Fast, simple and easy to use '\
'JSON:API serialization library (also known as fast_jsonapi).'
gem.homepage = 'https://github.com/jsonapi-serializer/jsonapi-serializer'
gem.summary = 'Fast JSON:API (jsonapi.org) serialization library'
gem.description =
'Fast JSON:API (jsonapi.org) serialization library ' \
'to work with any kind of objects'
gem.homepage = 'http://github.com/fast-jsonapi/fast_jsonapi'
gem.licenses = ['Apache-2.0']
gem.files = Dir['lib/**/*']
gem.require_paths = ['lib']
gem.extra_rdoc_files = ['LICENSE.txt', 'README.md']
gem.post_install_message = (
'This was previously known as `fast_jsonapi` ' \
'and it is now renamed to `jsonapi-serializer`. ' \
'Please see the project web page on how to migrate. Thank you!'
)
gem.add_runtime_dependency('activesupport', '>= 4.2')
@ -25,7 +36,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency('bundler')
gem.add_development_dependency('byebug')
gem.add_development_dependency('ffaker')
gem.add_development_dependency('jsonapi-rspec', '>= 0.0.5')
gem.add_development_dependency('jsonapi-rspec')
gem.add_development_dependency('rake')
gem.add_development_dependency('rspec')
gem.add_development_dependency('rubocop')
@ -33,5 +44,4 @@ Gem::Specification.new do |gem|
gem.add_development_dependency('rubocop-rspec')
gem.add_development_dependency('simplecov')
gem.add_development_dependency('sqlite3')
gem.metadata['rubygems_mfa_required'] = 'true'
end

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'jsonapi/serializer/errors'
module FastJsonapi
require 'fast_jsonapi/object_serializer'
if defined?(::Rails)

View File

@ -6,16 +6,7 @@ module FastJsonapi
# @param [Array<Object>] *params any number of parameters to be passed to the Proc
# @return [Object] the result of the Proc call with the supplied parameters
def call_proc(proc, *params)
# The parameters array for a lambda created from a symbol (&:foo) differs
# from explictly defined procs/lambdas, so we can't deduce the number of
# parameters from the array length (and differs between Ruby 2.x and 3).
# In the case of negative arity -- unlimited/unknown argument count --
# just send the object to act as the method receiver.
if proc.arity.negative?
proc.call(params.first)
else
proc.call(*params.take(proc.parameters.length))
end
proc.call(*params.take(proc.parameters.length))
end
end
end

View File

@ -1,7 +1,2 @@
require 'jsonapi/serializer/instrumentation'
warn(
'DEPRECATION: Performance instrumentation is no longer automatic. See: ' \
'https://github.com/jsonapi-serializer/jsonapi-serializer' \
'#performance-instrumentation'
)
require 'fast_jsonapi/instrumentation/serializable_hash'
require 'fast_jsonapi/instrumentation/serialized_json'

View File

@ -0,0 +1,13 @@
require 'active_support/notifications'
module FastJsonapi
module ObjectSerializer
alias serializable_hash_without_instrumentation serializable_hash
def serializable_hash
ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, { name: self.class.name }) do
serializable_hash_without_instrumentation
end
end
end
end

View File

@ -0,0 +1,13 @@
require 'active_support/notifications'
module FastJsonapi
module ObjectSerializer
alias serialized_json_without_instrumentation serialized_json
def serialized_json
ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, { name: self.class.name }) do
serialized_json_without_instrumentation
end
end
end
end

View File

@ -1,3 +1,2 @@
require 'skylight'
warn('DEPRECATION: Skylight support was moved into the `skylight` gem.')
require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash'
require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json'

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

@ -0,0 +1,20 @@
require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
require 'fast_jsonapi/instrumentation/serializable_hash'
module FastJsonapi
module Instrumentation
module Skylight
module Normalizers
class SerializableHash < SKYLIGHT_NORMALIZER_BASE_CLASS
register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION}".freeze
def normalize(_trace, _name, payload)
[CAT, payload[:name], nil]
end
end
end
end
end
end

View File

@ -0,0 +1,20 @@
require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
require 'fast_jsonapi/instrumentation/serializable_hash'
module FastJsonapi
module Instrumentation
module Skylight
module Normalizers
class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION}".freeze
def normalize(_trace, _name, payload)
[CAT, payload[:name], nil]
end
end
end
end
end
end

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'active_support'
require 'active_support/time'
require 'active_support/concern'
require 'active_support/inflector'
@ -16,6 +15,8 @@ module FastJsonapi
extend ActiveSupport::Concern
include SerializationCore
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
TRANSFORMS_MAPPING = {
camel: :camelize,
camel_lower: [:camelize, :lower],
@ -35,9 +36,7 @@ module FastJsonapi
end
def serializable_hash
if self.class.is_collection?(@resource, @is_collection)
return hash_for_collection
end
return hash_for_collection if is_collection?(@resource, @is_collection)
hash_for_one_record
end
@ -73,6 +72,15 @@ module FastJsonapi
serializable_hash
end
def serialized_json(*args)
warn(
'DEPRECATION: `#serialized_json` will be removed in the next release. '\
'More details: https://github.com/fast-jsonapi/fast_jsonapi/pull/44'
)
serializable_hash.to_json(*args)
end
alias to_json serialized_json
private
def process_options(options)
@ -81,7 +89,7 @@ module FastJsonapi
return if options.blank?
@known_included_objects = Set.new
@known_included_objects = {}
@meta = options[:meta]
@links = options[:links]
@is_collection = options[:is_collection]
@ -106,16 +114,13 @@ module FastJsonapi
end
end
def is_collection?(resource, force_is_collection = nil)
return force_is_collection unless force_is_collection.nil?
resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
end
class_methods do
# Detects a collection/enumerable
#
# @return [TrueClass] on a successful detection
def is_collection?(resource, force_is_collection = nil)
return force_is_collection unless force_is_collection.nil?
resource.is_a?(Enumerable) && !resource.respond_to?(:each_pair)
end
def inherited(subclass)
super(subclass)
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
@ -134,7 +139,9 @@ module FastJsonapi
def reflected_record_type
return @reflected_record_type if defined?(@reflected_record_type)
@reflected_record_type ||= (name.split('::').last.chomp('Serializer').underscore.to_sym if name&.end_with?('Serializer'))
@reflected_record_type ||= begin
name.split('::').last.chomp('Serializer').underscore.to_sym if name&.end_with?('Serializer')
end
end
def set_key_transform(transform_name)
@ -227,10 +234,10 @@ module FastJsonapi
# TODO: Remove this undocumented option.
# Delegate the caching to the serializer exclusively.
if relationship.cached
cachable_relationships_to_serialize[relationship.name] = relationship
else
if !relationship.cached
uncachable_relationships_to_serialize[relationship.name] = relationship
else
cachable_relationships_to_serialize[relationship.name] = relationship
end
relationships_to_serialize[relationship.name] = relationship
end
@ -285,7 +292,6 @@ module FastJsonapi
polymorphic: polymorphic,
conditional_proc: options[:if],
transform_method: @transform_method,
meta: options[:meta],
links: options[:links],
lazy_load_data: options[:lazy_load_data]
)
@ -301,7 +307,7 @@ module FastJsonapi
def serializer_for(name)
namespace = self.name.gsub(/()?\w+Serializer$/, '')
serializer_name = "#{name.to_s.demodulize.classify}Serializer"
serializer_name = name.to_s.demodulize.classify + 'Serializer'
serializer_class_name = namespace + serializer_name
begin
serializer_class_name.constantize
@ -339,11 +345,21 @@ module FastJsonapi
def validate_includes!(includes)
return if includes.blank?
parse_includes_list(includes).each_key do |include_item|
relationship_to_include = relationships_to_serialize[include_item]
raise(JSONAPI::Serializer::UnsupportedIncludeError.new(include_item, name)) unless relationship_to_include
includes.each do |include_item|
klass = self
parse_include_item(include_item).each do |parsed_include|
relationships_to_serialize = klass.relationships_to_serialize || {}
relationship_to_include = relationships_to_serialize[parsed_include]
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
relationship_to_include.static_serializer # called for a side-effect to check for a known serializer class.
if relationship_to_include.static_serializer
klass = relationship_to_include.static_serializer
else
# the serializer may change based on the object (e.g. polymorphic relationships),
# so inner relationships cannot be validated
break
end
end
end
end
end

View File

@ -1,6 +1,6 @@
module FastJsonapi
class Relationship
attr_reader :owner, :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :meta, :lazy_load_data
attr_reader :owner, :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data
def initialize(
owner:,
@ -12,12 +12,11 @@ module FastJsonapi
object_block:,
serializer:,
relationship_type:,
cached: false,
polymorphic:,
conditional_proc:,
transform_method:,
links:,
meta:,
cached: false,
lazy_load_data: false
)
@owner = owner
@ -34,7 +33,6 @@ module FastJsonapi
@conditional_proc = conditional_proc
@transform_method = transform_method
@links = links || {}
@meta = meta || {}
@lazy_load_data = lazy_load_data
@record_types_for = {}
@serializers_for_name = {}
@ -46,8 +44,6 @@ module FastJsonapi
output_hash[key] = {}
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case unless lazy_load_data && !included
add_meta_hash(record, serialization_params, output_hash) if meta.present?
add_links_hash(record, serialization_params, output_hash) if links.present?
end
end
@ -150,19 +146,11 @@ module FastJsonapi
record.public_send(links)
else
links.each_with_object({}) do |(key, method), hash|
Link.new(key: key, method: method).serialize(record, params, hash)
Link.new(key: key, method: method).serialize(record, params, hash)\
end
end
end
def add_meta_hash(record, params, output_hash)
output_hash[key][:meta] = if meta.is_a?(Proc)
FastJsonapi.call_proc(meta, record, params)
else
meta
end
end
def run_key_transform(input)
if transform_method.present?
input.to_s.send(*transform_method).to_sym

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require 'active_support'
require 'active_support/concern'
require 'digest/sha1'
module FastJsonapi
MandatoryField = Class.new(StandardError)
@ -68,15 +66,15 @@ module FastJsonapi
def record_hash(record, fieldset, includes_list, params = {})
if cache_store_instance
cache_opts = record_cache_options(cache_store_options, fieldset, includes_list, params)
record_hash = cache_store_instance.fetch(record, **cache_opts) do
record_hash = cache_store_instance.fetch(record, **cache_store_options) do
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, 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, includes_list, 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?
else
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?
@ -88,37 +86,6 @@ module FastJsonapi
record_hash
end
# Cache options helper. Use it to adapt cache keys/rules.
#
# If a fieldset is specified, it modifies the namespace to include the
# fields from the fieldset.
#
# @param options [Hash] default cache options
# @param fieldset [Array, nil] passed fieldset values
# @param includes_list [Array, nil] passed included values
# @param params [Hash] the serializer params
#
# @return [Hash] processed options hash
# rubocop:disable Lint/UnusedMethodArgument
def record_cache_options(options, fieldset, includes_list, params)
return options unless fieldset
options = options ? options.dup : {}
options[:namespace] ||= 'jsonapi-serializer'
fieldset_key = fieldset.join('_')
# Use a fixed-length fieldset key if the current length is more than
# the length of a SHA1 digest
if fieldset_key.length > 40
fieldset_key = Digest::SHA1.hexdigest(fieldset_key)
end
options[:namespace] = "#{options[:namespace]}-fieldset:#{fieldset_key}"
options
end
# rubocop:enable Lint/UnusedMethodArgument
def id_from_record(record, params)
return FastJsonapi.call_proc(record_id, record, params) if record_id.is_a?(Proc)
return record.send(record_id) if record_id
@ -127,73 +94,58 @@ module FastJsonapi
record.id
end
# It chops out the root association (first part) from each include.
#
# It keeps an unique list and collects all of the rest of the include
# value to hand it off to the next related to include serializer.
#
# This method will turn that include array into a Hash that looks like:
#
# {
# authors: Set.new([
# 'books',
# 'books.genre',
# 'books.genre.books',
# 'books.genre.books.authors',
# 'books.genre.books.genre'
# ]),
# genre: Set.new(['books'])
# }
#
# Because the serializer only cares about the root associations
# included, it only needs the first segment of each include
# (for books, it's the "authors" and "genre") and it doesn't need to
# waste cycles parsing the rest of the include value. That will be done
# by the next serializer in line.
#
# @param includes_list [List] to be parsed
# @return [Hash]
def parse_includes_list(includes_list)
includes_list.each_with_object({}) do |include_item, include_sets|
include_base, include_remainder = include_item.to_s.split('.', 2)
include_sets[include_base.to_sym] ||= Set.new
include_sets[include_base.to_sym] << include_remainder if include_remainder
end
def parse_include_item(include_item)
return [include_item.to_sym] unless include_item.to_s.include?('.')
include_item.to_s.split('.').map!(&:to_sym)
end
def remaining_items(items)
return unless items.size > 1
[items[1..-1].join('.').to_sym]
end
# includes handler
def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {})
return unless includes_list.present?
return [] unless relationships_to_serialize
includes_list = parse_includes_list(includes_list)
includes_list.sort.each_with_object([]) do |include_item, included_records|
items = parse_include_item(include_item)
remaining_items = remaining_items(items)
includes_list.each_with_object([]) do |include_item, included_records|
relationship_item = relationships_to_serialize[include_item.first]
items.each do |item|
next unless relationships_to_serialize && relationships_to_serialize[item]
next unless relationship_item&.include_relationship?(record, params)
relationship_item = relationships_to_serialize[item]
next unless relationship_item.include_relationship?(record, params)
included_objects = Array(relationship_item.fetch_associated_object(record, params))
next if included_objects.empty?
relationship_type = relationship_item.relationship_type
static_serializer = relationship_item.static_serializer
static_record_type = relationship_item.static_record_type
included_objects = relationship_item.fetch_associated_object(record, params)
next if included_objects.blank?
included_objects.each do |inc_obj|
serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
record_type = static_record_type || serializer.record_type
included_objects = [included_objects] unless relationship_type == :has_many
if include_item.last.any?
serializer_records = serializer.get_included_records(inc_obj, include_item.last, known_included_objects, fieldsets, params)
included_records.concat(serializer_records) unless serializer_records.empty?
static_serializer = relationship_item.static_serializer
static_record_type = relationship_item.static_record_type
included_objects.each do |inc_obj|
serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
record_type = static_record_type || serializer.record_type
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, params)}"
next if known_included_objects.key?(code)
known_included_objects[code] = inc_obj
included_records << serializer.record_hash(inc_obj, fieldsets[record_type], includes_list, params)
end
code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}"
next if known_included_objects.include?(code)
known_included_objects << code
included_records << serializer.record_hash(inc_obj, fieldsets[record_type], includes_list, params)
end
end
end

View File

@ -1,3 +1,3 @@
module FastJsonapi
VERSION = JSONAPI::Serializer::VERSION
VERSION = '1.7.3'.freeze
end

View File

@ -1,6 +1,6 @@
<% module_namespacing do -%>
class <%= class_name %>Serializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
attributes <%= attributes_names.join(", ") %>
end
<% end -%>

View File

@ -1,12 +0,0 @@
require 'fast_jsonapi'
module JSONAPI
module Serializer
# TODO: Move and cleanup the old implementation...
def self.included(base)
base.class_eval do
include FastJsonapi::ObjectSerializer
end
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
module JSONAPI
module Serializer
class Error < StandardError; end
class UnsupportedIncludeError < Error
attr_reader :include_item, :klass
def initialize(include_item, klass)
super()
@include_item = include_item
@klass = klass
end
def message
"#{include_item} is not specified as a relationship on #{klass}"
end
end
end
end

View File

@ -1,28 +0,0 @@
require 'active_support'
require 'active_support/notifications'
module JSONAPI
module Serializer
# Support for instrumentation
module Instrumentation
# Performance instrumentation namespace
NOTIFICATION_NAMESPACE = 'render.jsonapi-serializer.'.freeze
# Patch methods to use instrumentation...
%w[
serializable_hash
get_included_records
relationships_hash
].each do |method_name|
define_method(method_name) do |*args|
ActiveSupport::Notifications.instrument(
NOTIFICATION_NAMESPACE + method_name,
{ name: self.class.name, serializer: self.class }
) do
super(*args)
end
end
end
end
end
end

View File

@ -1,5 +0,0 @@
module JSONAPI
module Serializer
VERSION = '2.2.0'.freeze
end
end

View File

@ -1,6 +1,3 @@
require 'active_support'
require 'active_support/cache'
class User
attr_accessor :uid, :first_name, :last_name, :email
@ -18,7 +15,7 @@ class NoSerializerUser < User
end
class UserSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
set_id :uid
attributes :first_name, :last_name, :email
@ -29,12 +26,3 @@ class UserSerializer
}
end
end
module Cached
class UserSerializer < ::UserSerializer
cache_options(
store: ActiveSupport::Cache::MemoryStore.new,
namespace: 'test'
)
end
end

View File

@ -1,6 +1,4 @@
require 'active_support'
require 'active_support/cache'
require 'jsonapi/serializer/instrumentation'
class Actor < User
attr_accessor :movies, :movie_ids
@ -35,7 +33,7 @@ class ActorSerializer < UserSerializer
end
class CamelCaseActorSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel
@ -72,9 +70,3 @@ module Cached
)
end
end
module Instrumented
class ActorSerializer < ::ActorSerializer
include ::JSONAPI::Serializer::Instrumentation
end
end

View File

@ -3,7 +3,6 @@ class Movie
:id,
:name,
:year,
:actor_or_user,
:actors,
:actor_ids,
:polymorphics,
@ -26,7 +25,7 @@ class Movie
@url ||= FFaker::Internet.http_url
return @url if obj.nil?
"#{@url}?#{obj.hash}"
@url + '?' + obj.hash.to_s
end
def owner=(ownr)
@ -44,30 +43,20 @@ class Movie
end
class MovieSerializer
include JSONAPI::Serializer
include FastJsonapi::ObjectSerializer
set_type :movie
attribute :released_in_year, &:year
attributes :name
attribute :release_year do |object, _params|
attribute :release_year do |object|
object.year
end
link :self, :url
belongs_to :owner, serializer: UserSerializer
belongs_to :actor_or_user,
id_method_name: :uid,
polymorphic: {
Actor => :actor,
User => :user
}
has_many(
:actors,
meta: proc { |record, _| { count: record.actors.length } },
links: {
actors_self: :url,
related: ->(obj) { obj.url(obj) }

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:actor) do
act = Actor.fake
act.movies = [Movie.fake]

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:actor) do
faked = Actor.fake
movie = Movie.fake
@ -25,56 +25,5 @@ RSpec.describe JSONAPI::Serializer do
cache_store.delete(actor.movies[0].owner, namespace: 'test')
).to be(false)
end
context 'without relationships' do
let(:user) { User.fake }
let(:serialized) { Cached::UserSerializer.new(user).serializable_hash.as_json }
it do
expect(serialized['data']).not_to have_key('relationships')
end
end
end
describe 'with caching and different fieldsets' do
context 'when fieldset is provided' do
it 'includes the fieldset in the namespace' do
expect(cache_store.delete(actor, namespace: 'test')).to be(false)
Cached::ActorSerializer.new(
[actor], fields: { actor: %i[first_name] }
).serializable_hash
# Expect cached keys to match the passed fieldset
expect(cache_store.read(actor, namespace: 'test-fieldset:first_name')[:attributes].keys).to eq(%i[first_name])
Cached::ActorSerializer.new(
[actor]
).serializable_hash
# Expect cached keys to match all valid actor fields (no fieldset)
expect(cache_store.read(actor, namespace: 'test')[:attributes].keys).to eq(%i[first_name last_name email])
expect(cache_store.delete(actor, namespace: 'test')).to be(true)
expect(cache_store.delete(actor, namespace: 'test-fieldset:first_name')).to be(true)
end
end
context 'when long fieldset is provided' do
let(:actor_keys) { %i[first_name last_name more_fields yet_more_fields so_very_many_fields] }
let(:digest_key) { Digest::SHA1.hexdigest(actor_keys.join('_')) }
it 'includes the hashed fieldset in the namespace' do
Cached::ActorSerializer.new(
[actor], fields: { actor: actor_keys }
).serializable_hash
expect(cache_store.read(actor, namespace: "test-fieldset:#{digest_key}")[:attributes].keys).to eq(
%i[first_name last_name]
)
expect(cache_store.delete(actor, namespace: "test-fieldset:#{digest_key}")).to be(true)
end
end
end
end

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:actor) { Actor.fake }
let(:params) { {} }
@ -18,7 +18,7 @@ RSpec.describe JSONAPI::Serializer do
it do
expect { ActorSerializer.new(actor, include: ['bad_include']) }
.to raise_error(
JSONAPI::Serializer::UnsupportedIncludeError, /bad_include is not specified as a relationship/
ArgumentError, /bad_include is not specified as a relationship/
)
end
end

View File

@ -1,29 +0,0 @@
require 'spec_helper'
# Needed to subscribe to `active_support/notifications`
require 'concurrent'
RSpec.describe JSONAPI::Serializer do
let(:serializer) do
Instrumented::ActorSerializer.new(Actor.fake)
end
it do
payload = event_name = nil
notification_name =
"#{::JSONAPI::Serializer::Instrumentation::NOTIFICATION_NAMESPACE}serializable_hash"
ActiveSupport::Notifications.subscribe(
notification_name
) do |ev_name, _s, _f, _i, ev_payload|
event_name = ev_name
payload = ev_payload
end
expect(serializer.serializable_hash).not_to be_nil
expect(event_name).to eq('render.jsonapi-serializer.serializable_hash')
expect(payload[:name]).to eq(serializer.class.name)
expect(payload[:serializer]).to eq(serializer.class)
end
end

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:actor) { Actor.fake }
let(:params) { {} }
let(:serialized) do

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:movie) do
faked = Movie.fake
faked.actors = [Actor.fake]

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:user) { User.fake }
let(:params) { {} }
let(:serialized) do

View File

@ -1,6 +1,6 @@
require 'spec_helper'
RSpec.describe JSONAPI::Serializer do
RSpec.describe FastJsonapi::ObjectSerializer do
let(:movie) do
mov = Movie.fake
mov.actors = rand(2..5).times.map { Actor.fake }
@ -8,7 +8,6 @@ RSpec.describe JSONAPI::Serializer do
poly_act = Actor.fake
poly_act.movies = [Movie.fake]
mov.polymorphics = [User.fake, poly_act]
mov.actor_or_user = Actor.fake
mov
end
let(:params) { {} }
@ -59,13 +58,6 @@ RSpec.describe JSONAPI::Serializer do
)
end
describe 'has relationship meta' do
it do
expect(serialized['data']['relationships']['actors'])
.to have_meta('count' => movie.actors.length)
end
end
context 'with include' do
let(:params) do
{ include: [:actors] }
@ -101,7 +93,7 @@ RSpec.describe JSONAPI::Serializer do
end
end
context 'with has_many polymorphic' do
context 'with polymorphic' do
let(:params) do
{ include: ['actors_and_users.played_movies'] }
end
@ -129,18 +121,6 @@ RSpec.describe JSONAPI::Serializer do
)
end
end
context 'with belongs_to polymorphic' do
let(:params) do
{ include: ['actor_or_user'] }
end
it do
expect(serialized['included']).to include(
have_type('actor').and(have_id(movie.actor_or_user.uid))
)
end
end
end
end
end

View File

@ -6,9 +6,8 @@ SimpleCov.start do
end
SimpleCov.minimum_coverage 90
require 'active_support'
require 'active_support/core_ext/object/json'
require 'jsonapi/serializer'
require 'fast_jsonapi'
require 'ffaker'
require 'rspec'
require 'jsonapi/rspec'