Compare commits
No commits in common. "master" and "v1.7.0" have entirely different histories.
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -1,19 +1,19 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
on: [push]
|
||||
|
||||
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 }}
|
||||
|
||||
@ -24,17 +24,17 @@ jobs:
|
||||
bundle install
|
||||
|
||||
- name: Runs code QA and tests
|
||||
run: bundle exec rake
|
||||
run: rspec
|
||||
|
||||
- name: Publish to Rubygems
|
||||
continue-on-error: true
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
- name: Publish to GPR
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
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
|
||||
gem build *.gemspec
|
||||
gem push *.gem
|
||||
printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
||||
gem build
|
||||
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
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -41,8 +41,5 @@ test.db
|
||||
/vendor
|
||||
|
||||
# Don't checkin Gemfile.lock
|
||||
# See: https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
||||
# See: http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
||||
Gemfile.lock
|
||||
|
||||
# Gem builds
|
||||
/*.gem
|
||||
|
102
.rubocop.yml
102
.rubocop.yml
@ -1,102 +0,0 @@
|
||||
require:
|
||||
- rubocop-performance
|
||||
- rubocop-rspec
|
||||
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
SuggestExtensions: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
|
||||
Style/SymbolProc:
|
||||
Exclude:
|
||||
- 'spec/fixtures/*.rb'
|
||||
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
- 'spec/fixtures/*.rb'
|
||||
|
||||
RSpec/FilePath:
|
||||
Enabled: false
|
||||
|
||||
RSpec/DescribedClass:
|
||||
Enabled: false
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Enabled: false
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Enabled: false
|
||||
|
||||
RSpec/NestedGroups:
|
||||
Enabled: false
|
||||
|
||||
Performance/TimesMap:
|
||||
Exclude:
|
||||
- 'spec/**/**.rb'
|
||||
|
||||
Gemspec/RequiredRubyVersion:
|
||||
Enabled: false
|
||||
|
||||
# TODO: Fix these...
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/GuardClause:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Style/ConditionalAssignment:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Lint/AssignmentInCondition:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Metrics:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
|
||||
Layout/LineLength:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
Naming/PredicateName:
|
||||
Exclude:
|
||||
- 'lib/**/**.rb'
|
||||
|
||||
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'
|
44
CHANGELOG.md
44
CHANGELOG.md
@ -4,50 +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)
|
||||
|
||||
## [1.7.1] - 2020-05-01
|
||||
### Fixed
|
||||
- ObjectSerializer#serialized_json accepts arguments for to_json (#80)
|
||||
|
||||
## [1.7.0] - 2020-04-29
|
||||
### Added
|
||||
- Serializer option support for procs (#32)
|
||||
|
@ -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,
|
||||
|
243
README.md
243
README.md
@ -1,29 +1,23 @@
|
||||
# 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).
|
||||
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 about _25 times_ faster than
|
||||
the ActiveModelSerializers on up to a current benchmark of 1000 records. Please
|
||||
read the performance article in the `docs` folder for any questions related to
|
||||
methodology.
|
||||
|
||||
We want to ensure that with every
|
||||
change on this library, serialization time stays significantly faster than
|
||||
the performance provided by the alternatives. Please read the performance
|
||||
article in the `docs` folder for any questions related to methodology.
|
||||
## Benchmark times for 250 records
|
||||
|
||||
```bash
|
||||
$ rspec
|
||||
Active Model Serializer serialized 250 records in 138.71 ms
|
||||
Fast JSON API serialized 250 records in 3.01 ms
|
||||
```
|
||||
|
||||
# Table of Contents
|
||||
|
||||
@ -44,9 +38,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 +54,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.6.0', git: 'https://github.com/fast-jsonapi/fast_jsonapi'
|
||||
```
|
||||
|
||||
Execute:
|
||||
@ -94,8 +85,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 +166,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 +186,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 +202,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 +216,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 +228,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 +237,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 +245,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 +302,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 +312,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 +367,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 +381,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 +401,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 +416,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 +435,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 +447,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 +461,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 +482,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 +495,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 +508,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 +528,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 +559,7 @@ module AvatarHelper
|
||||
end
|
||||
|
||||
class UserSerializer
|
||||
include JSONAPI::Serializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
include AvatarHelper # mixes in your helper method as class method
|
||||
|
||||
@ -641,7 +584,7 @@ module AvatarHelper
|
||||
end
|
||||
|
||||
class UserSerializer
|
||||
include JSONAPI::Serializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
extend AvatarHelper # mixes in your helper method as class method
|
||||
|
||||
@ -671,27 +614,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,59 +653,16 @@ 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:
|
||||
To run tests without the performance tests (for quicker test runs):
|
||||
|
||||
### [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'
|
||||
```bash
|
||||
rspec spec --tag ~performance:true
|
||||
```
|
||||
|
||||
### Replace all constant references
|
||||
To run tests only performance tests:
|
||||
|
||||
```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
|
||||
```bash
|
||||
rspec spec --tag performance:true
|
||||
```
|
||||
|
||||
## Contributing
|
||||
@ -763,4 +672,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.
|
||||
|
15
Rakefile
15
Rakefile
@ -1,15 +0,0 @@
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
require 'rubocop/rake_task'
|
||||
|
||||
desc('Codestyle check and linter')
|
||||
RuboCop::RakeTask.new('rubocop') do |task|
|
||||
task.fail_on_error = true
|
||||
task.patterns = [
|
||||
'lib/**/*.rb',
|
||||
'spec/**/*.rb'
|
||||
]
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task(default: [:rubocop, :spec])
|
@ -13,7 +13,7 @@ require 'oj'
|
||||
require 'fast_jsonapi'
|
||||
|
||||
class BaseSerializer
|
||||
include JSONAPI::Serializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
def to_json
|
||||
Oj.dump(serializable_hash)
|
||||
|
@ -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)
|
||||
|
||||
|
39
fast_jsonapi.gemspec
Normal file
39
fast_jsonapi.gemspec
Normal file
@ -0,0 +1,39 @@
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
|
||||
require "fast_jsonapi/version"
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "fast_jsonapi"
|
||||
gem.version = FastJsonapi::VERSION
|
||||
|
||||
gem.required_ruby_version = '>= 2.0.0' if gem.respond_to? :required_ruby_version=
|
||||
gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version=
|
||||
gem.metadata = { "allowed_push_host" => "https://rubygems.org" } if gem.respond_to? :metadata=
|
||||
gem.require_paths = ["lib"]
|
||||
gem.authors = ["Shishir Kakaraddi", "Srinivas Raghunathan", "Adam Gross"]
|
||||
gem.description = "JSON API(jsonapi.org) serializer that works with rails and can be used to serialize any kind of ruby objects"
|
||||
gem.email = ""
|
||||
gem.extra_rdoc_files = [
|
||||
"LICENSE.txt",
|
||||
"README.md"
|
||||
]
|
||||
gem.files = Dir["lib/**/*"]
|
||||
gem.homepage = "http://github.com/Netflix/fast_jsonapi"
|
||||
gem.licenses = ["Apache-2.0"]
|
||||
gem.rubygems_version = "2.5.1"
|
||||
gem.summary = "fast JSON API(jsonapi.org) serializer"
|
||||
|
||||
gem.add_runtime_dependency(%q<activesupport>, [">= 4.2"])
|
||||
gem.add_development_dependency(%q<activerecord>, [">= 4.2"])
|
||||
gem.add_development_dependency(%q<skylight>, ["~> 1.3"])
|
||||
gem.add_development_dependency(%q<rspec>, ["~> 3.5.0"])
|
||||
gem.add_development_dependency(%q<oj>, ["~> 3.3"])
|
||||
gem.add_development_dependency(%q<rspec-benchmark>, ["~> 0.3.0"])
|
||||
gem.add_development_dependency(%q<bundler>, [">= 1.0"])
|
||||
gem.add_development_dependency(%q<byebug>, [">= 0"])
|
||||
gem.add_development_dependency(%q<active_model_serializers>, ["~> 0.10.7"])
|
||||
gem.add_development_dependency(%q<sqlite3>, ["~> 1.3"])
|
||||
gem.add_development_dependency(%q<jsonapi-rb>, ["~> 0.5.0"])
|
||||
gem.add_development_dependency(%q<jsonapi-serializers>, ["~> 1.0.0"])
|
||||
end
|
@ -1,37 +0,0 @@
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
|
||||
require 'jsonapi/serializer/version'
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = 'jsonapi-serializer'
|
||||
gem.version = JSONAPI::Serializer::VERSION
|
||||
|
||||
gem.authors = ['JSON:API Serializer 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.licenses = ['Apache-2.0']
|
||||
gem.files = Dir['lib/**/*']
|
||||
gem.require_paths = ['lib']
|
||||
gem.extra_rdoc_files = ['LICENSE.txt', 'README.md']
|
||||
|
||||
gem.add_runtime_dependency('activesupport', '>= 4.2')
|
||||
|
||||
gem.add_development_dependency('activerecord')
|
||||
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('rake')
|
||||
gem.add_development_dependency('rspec')
|
||||
gem.add_development_dependency('rubocop')
|
||||
gem.add_development_dependency('rubocop-performance')
|
||||
gem.add_development_dependency('rubocop-rspec')
|
||||
gem.add_development_dependency('simplecov')
|
||||
gem.add_development_dependency('sqlite3')
|
||||
gem.metadata['rubygems_mfa_required'] = 'true'
|
||||
end
|
@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'jsonapi/serializer/errors'
|
||||
|
||||
module FastJsonapi
|
||||
require 'fast_jsonapi/object_serializer'
|
||||
if defined?(::Rails)
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
15
lib/fast_jsonapi/instrumentation/serializable_hash.rb
Normal file
15
lib/fast_jsonapi/instrumentation/serializable_hash.rb
Normal file
@ -0,0 +1,15 @@
|
||||
require 'active_support/notifications'
|
||||
|
||||
module FastJsonapi
|
||||
module ObjectSerializer
|
||||
|
||||
alias_method :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
|
15
lib/fast_jsonapi/instrumentation/serialized_json.rb
Normal file
15
lib/fast_jsonapi/instrumentation/serialized_json.rb
Normal file
@ -0,0 +1,15 @@
|
||||
require 'active_support/notifications'
|
||||
|
||||
module FastJsonapi
|
||||
module ObjectSerializer
|
||||
|
||||
alias_method :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
|
@ -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'
|
||||
|
@ -0,0 +1,7 @@
|
||||
require 'skylight'
|
||||
|
||||
SKYLIGHT_NORMALIZER_BASE_CLASS = begin
|
||||
::Skylight::Core::Normalizers::Normalizer
|
||||
rescue NameError
|
||||
::Skylight::Normalizers::Normalizer
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
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
|
@ -0,0 +1,22 @@
|
||||
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
|
@ -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,13 +36,11 @@ 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
|
||||
alias to_hash serializable_hash
|
||||
alias_method :to_hash, :serializable_hash
|
||||
|
||||
def hash_for_one_record
|
||||
serializable_hash = { data: nil }
|
||||
@ -73,6 +72,15 @@ module FastJsonapi
|
||||
serializable_hash
|
||||
end
|
||||
|
||||
def serialized_json
|
||||
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
|
||||
end
|
||||
alias_method :to_json, :serialized_json
|
||||
|
||||
private
|
||||
|
||||
def process_options(options)
|
||||
@ -81,12 +89,12 @@ 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]
|
||||
@params = options[:params] || {}
|
||||
raise ArgumentError, '`params` option passed to serializer must be a hash' unless @params.is_a?(Hash)
|
||||
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
|
||||
|
||||
if options[:include].present?
|
||||
@includes = options[:include].reject(&:blank?).map(&:to_sym)
|
||||
@ -106,15 +114,13 @@ module FastJsonapi
|
||||
end
|
||||
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?
|
||||
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
|
||||
resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
||||
def inherited(subclass)
|
||||
super(subclass)
|
||||
@ -134,7 +140,11 @@ 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
|
||||
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)
|
||||
@ -143,14 +153,13 @@ module FastJsonapi
|
||||
# ensure that the record type is correctly transformed
|
||||
if record_type
|
||||
set_type(record_type)
|
||||
# TODO: Remove dead code
|
||||
elsif reflected_record_type
|
||||
set_type(reflected_record_type)
|
||||
end
|
||||
end
|
||||
|
||||
def run_key_transform(input)
|
||||
if transform_method.present?
|
||||
if self.transform_method.present?
|
||||
input.to_s.send(*@transform_method).to_sym
|
||||
else
|
||||
input.to_sym
|
||||
@ -172,7 +181,7 @@ module FastJsonapi
|
||||
|
||||
def cache_options(cache_options)
|
||||
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
||||
unless cache_options.key?(:store)
|
||||
if !cache_options.key?(:store)
|
||||
# fall back to old, deprecated behaviour because no store was passed.
|
||||
# we assume the user explicitly wants new behaviour if he passed a
|
||||
# store because this is the new syntax.
|
||||
@ -202,7 +211,7 @@ module FastJsonapi
|
||||
def attributes(*attributes_list, &block)
|
||||
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
||||
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
||||
self.attributes_to_serialize = {} if attributes_to_serialize.nil?
|
||||
self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
|
||||
|
||||
# to support calling `attribute` with a lambda, e.g `attribute :key, ->(object) { ... }`
|
||||
block = attributes_list.pop if attributes_list.last.is_a?(Proc)
|
||||
@ -225,14 +234,12 @@ module FastJsonapi
|
||||
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
||||
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
||||
|
||||
# TODO: Remove this undocumented option.
|
||||
# Delegate the caching to the serializer exclusively.
|
||||
if relationship.cached
|
||||
cachable_relationships_to_serialize[relationship.name] = relationship
|
||||
if !relationship.cached
|
||||
self.uncachable_relationships_to_serialize[relationship.name] = relationship
|
||||
else
|
||||
uncachable_relationships_to_serialize[relationship.name] = relationship
|
||||
self.cachable_relationships_to_serialize[relationship.name] = relationship
|
||||
end
|
||||
relationships_to_serialize[relationship.name] = relationship
|
||||
self.relationships_to_serialize[relationship.name] = relationship
|
||||
end
|
||||
|
||||
def has_many(relationship_name, options = {}, &block)
|
||||
@ -258,9 +265,11 @@ module FastJsonapi
|
||||
name = base_key.to_sym
|
||||
if relationship_type == :has_many
|
||||
base_serialization_key = base_key.to_s.singularize
|
||||
base_key_sym = base_serialization_key.to_sym
|
||||
id_postfix = '_ids'
|
||||
else
|
||||
base_serialization_key = base_key
|
||||
base_key_sym = name
|
||||
id_postfix = '_id'
|
||||
end
|
||||
polymorphic = fetch_polymorphic_option(options)
|
||||
@ -285,7 +294,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,14 +309,14 @@ 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
|
||||
return serializer_class_name.constantize
|
||||
rescue NameError
|
||||
raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
|
||||
"Attempted to find '#{serializer_class_name}'. " \
|
||||
'Consider specifying the serializer directly through options[:serializer].'
|
||||
raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " +
|
||||
"Attempted to find '#{serializer_class_name}'. " +
|
||||
"Consider specifying the serializer directly through options[:serializer]."
|
||||
end
|
||||
end
|
||||
|
||||
@ -316,20 +324,19 @@ module FastJsonapi
|
||||
option = options[:polymorphic]
|
||||
return false unless option.present?
|
||||
return option if option.respond_to? :keys
|
||||
|
||||
{}
|
||||
end
|
||||
|
||||
# def link(link_name, link_method_name = nil, &block)
|
||||
def link(*params, &block)
|
||||
self.data_links = {} if data_links.nil?
|
||||
self.data_links = {} if self.data_links.nil?
|
||||
|
||||
options = params.last.is_a?(Hash) ? params.pop : {}
|
||||
link_name = params.first
|
||||
link_method_name = params[-1]
|
||||
key = run_key_transform(link_name)
|
||||
|
||||
data_links[key] = Link.new(
|
||||
self.data_links[key] = Link.new(
|
||||
key: key,
|
||||
method: block || link_method_name,
|
||||
options: options
|
||||
@ -339,11 +346,20 @@ 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
|
||||
|
||||
relationship_to_include.static_serializer # called for a side-effect to check for a known serializer class.
|
||||
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
|
||||
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
|
||||
|
@ -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 = {}
|
||||
@ -45,16 +43,15 @@ module FastJsonapi
|
||||
empty_case = relationship_type == :has_many ? [] : nil
|
||||
|
||||
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?
|
||||
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?
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_associated_object(record, params)
|
||||
return FastJsonapi.call_proc(object_block, record, params) unless object_block.nil?
|
||||
|
||||
record.send(object_method_name)
|
||||
end
|
||||
|
||||
@ -67,9 +64,8 @@ module FastJsonapi
|
||||
end
|
||||
|
||||
def serializer_for(record, serialization_params)
|
||||
# TODO: Remove this, dead code...
|
||||
if @static_serializer
|
||||
@static_serializer
|
||||
return @static_serializer
|
||||
|
||||
elsif polymorphic
|
||||
name = polymorphic[record.class] if polymorphic.is_a?(Hash)
|
||||
@ -83,7 +79,6 @@ module FastJsonapi
|
||||
serializer_for_name(record.class.name)
|
||||
|
||||
else
|
||||
# TODO: Remove this, dead code...
|
||||
raise "Unknown serializer for object #{record.inspect}"
|
||||
end
|
||||
end
|
||||
@ -107,11 +102,9 @@ module FastJsonapi
|
||||
|
||||
return unless associated_object = fetch_associated_object(record, params)
|
||||
|
||||
if associated_object.respond_to? :map
|
||||
return associated_object.map do |object|
|
||||
id_hash_from_record object, params
|
||||
end
|
||||
end
|
||||
return associated_object.map do |object|
|
||||
id_hash_from_record object, params
|
||||
end if associated_object.respond_to? :map
|
||||
|
||||
id_hash_from_record associated_object, params
|
||||
end
|
||||
@ -123,11 +116,10 @@ module FastJsonapi
|
||||
|
||||
def ids_hash(ids, record_type)
|
||||
return ids.map { |id| id_hash(id, record_type) } if ids.respond_to? :map
|
||||
|
||||
id_hash(ids, record_type) # ids variable is just a single id here
|
||||
end
|
||||
|
||||
def id_hash(id, record_type, default_return = false)
|
||||
def id_hash(id, record_type, default_return=false)
|
||||
if id.present?
|
||||
{ id: id.to_s, type: record_type }
|
||||
else
|
||||
@ -139,33 +131,24 @@ module FastJsonapi
|
||||
if object_block.present?
|
||||
object = FastJsonapi.call_proc(object_block, record, params)
|
||||
return object.map { |item| item.public_send(id_method_name) } if object.respond_to? :map
|
||||
|
||||
return object.try(id_method_name)
|
||||
end
|
||||
record.public_send(id_method_name)
|
||||
end
|
||||
|
||||
def add_links_hash(record, params, output_hash)
|
||||
output_hash[key][:links] = if links.is_a?(Symbol)
|
||||
record.public_send(links)
|
||||
else
|
||||
links.each_with_object({}) do |(key, method), 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
|
||||
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
|
||||
|
||||
def run_key_transform(input)
|
||||
if transform_method.present?
|
||||
input.to_s.send(*transform_method).to_sym
|
||||
if self.transform_method.present?
|
||||
input.to_s.send(*self.transform_method).to_sym
|
||||
else
|
||||
input.to_sym
|
||||
end
|
||||
@ -173,7 +156,6 @@ module FastJsonapi
|
||||
|
||||
def initialize_static_serializer
|
||||
return if @initialized_static_serializer
|
||||
|
||||
@static_serializer = compute_static_serializer
|
||||
@static_record_type = compute_static_record_type
|
||||
@initialized_static_serializer = true
|
||||
@ -217,20 +199,14 @@ module FastJsonapi
|
||||
def record_type_for(record, serialization_params)
|
||||
# if the record type is static, return it
|
||||
return @static_record_type if @static_record_type
|
||||
|
||||
# if not, use the record type of the serializer, and memoize the transformed version
|
||||
serializer = serializer_for(record, serialization_params)
|
||||
@record_types_for[serializer] ||= run_key_transform(serializer.record_type)
|
||||
end
|
||||
|
||||
def compute_static_record_type
|
||||
if polymorphic
|
||||
nil
|
||||
elsif record_type
|
||||
run_key_transform(record_type)
|
||||
elsif @static_serializer
|
||||
run_key_transform(@static_serializer.record_type)
|
||||
end
|
||||
return run_key_transform(record_type) if record_type
|
||||
return run_key_transform(@static_serializer.record_type) if @static_serializer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,10 +10,10 @@ module FastJsonapi
|
||||
|
||||
def serialize(record, serialization_params, output_hash)
|
||||
if conditionally_allowed?(record, serialization_params)
|
||||
if method.is_a?(Proc)
|
||||
output_hash[key] = FastJsonapi.call_proc(method, record, serialization_params)
|
||||
output_hash[key] = if method.is_a?(Proc)
|
||||
FastJsonapi.call_proc(method, record, serialization_params)
|
||||
else
|
||||
output_hash[key] = record.public_send(method)
|
||||
record.public_send(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,8 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support'
|
||||
require 'active_support/concern'
|
||||
require 'digest/sha1'
|
||||
|
||||
module FastJsonapi
|
||||
MandatoryField = Class.new(StandardError)
|
||||
@ -27,7 +25,7 @@ module FastJsonapi
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def id_hash(id, record_type, default_return = false)
|
||||
def id_hash(id, record_type, default_return=false)
|
||||
if id.present?
|
||||
{ id: id.to_s, type: record_type }
|
||||
else
|
||||
@ -68,132 +66,83 @@ 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?
|
||||
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
|
||||
record_hash
|
||||
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?
|
||||
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
|
||||
|
||||
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
|
||||
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
|
||||
raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
|
||||
|
||||
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]
|
||||
relationship_item = relationships_to_serialize[item]
|
||||
next unless relationship_item.include_relationship?(record, params)
|
||||
relationship_type = relationship_item.relationship_type
|
||||
|
||||
next unless relationship_item&.include_relationship?(record, params)
|
||||
included_objects = relationship_item.fetch_associated_object(record, params)
|
||||
next if included_objects.blank?
|
||||
included_objects = [included_objects] unless relationship_type == :has_many
|
||||
|
||||
included_objects = Array(relationship_item.fetch_associated_object(record, params))
|
||||
next if included_objects.empty?
|
||||
static_serializer = relationship_item.static_serializer
|
||||
static_record_type = relationship_item.static_record_type
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
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
|
||||
|
@ -1,3 +1,3 @@
|
||||
module FastJsonapi
|
||||
VERSION = JSONAPI::Serializer::VERSION
|
||||
VERSION = '1.7.0'
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ class SerializerGenerator < Rails::Generators::NamedBase
|
||||
|
||||
private
|
||||
|
||||
def attributes_names
|
||||
attributes.map { |a| a.name.to_sym.inspect }
|
||||
end
|
||||
def attributes_names
|
||||
attributes.map { |a| a.name.to_sym.inspect }
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,6 @@
|
||||
<% module_namespacing do -%>
|
||||
class <%= class_name %>Serializer
|
||||
include JSONAPI::Serializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes <%= attributes_names.join(", ") %>
|
||||
end
|
||||
<% end -%>
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
module JSONAPI
|
||||
module Serializer
|
||||
VERSION = '2.2.0'.freeze
|
||||
end
|
||||
end
|
40
spec/fixtures/_user.rb
vendored
40
spec/fixtures/_user.rb
vendored
@ -1,40 +0,0 @@
|
||||
require 'active_support'
|
||||
require 'active_support/cache'
|
||||
|
||||
class User
|
||||
attr_accessor :uid, :first_name, :last_name, :email
|
||||
|
||||
def self.fake(id = nil)
|
||||
faked = new
|
||||
faked.uid = id || SecureRandom.uuid
|
||||
faked.first_name = FFaker::Name.first_name
|
||||
faked.last_name = FFaker::Name.last_name
|
||||
faked.email = FFaker::Internet.email
|
||||
faked
|
||||
end
|
||||
end
|
||||
|
||||
class NoSerializerUser < User
|
||||
end
|
||||
|
||||
class UserSerializer
|
||||
include JSONAPI::Serializer
|
||||
|
||||
set_id :uid
|
||||
attributes :first_name, :last_name, :email
|
||||
|
||||
meta do |obj|
|
||||
{
|
||||
email_length: obj.email.size
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module Cached
|
||||
class UserSerializer < ::UserSerializer
|
||||
cache_options(
|
||||
store: ActiveSupport::Cache::MemoryStore.new,
|
||||
namespace: 'test'
|
||||
)
|
||||
end
|
||||
end
|
80
spec/fixtures/actor.rb
vendored
80
spec/fixtures/actor.rb
vendored
@ -1,80 +0,0 @@
|
||||
require 'active_support'
|
||||
require 'active_support/cache'
|
||||
require 'jsonapi/serializer/instrumentation'
|
||||
|
||||
class Actor < User
|
||||
attr_accessor :movies, :movie_ids
|
||||
|
||||
def self.fake(id = nil)
|
||||
faked = super(id)
|
||||
faked.movies = []
|
||||
faked.movie_ids = []
|
||||
faked
|
||||
end
|
||||
|
||||
def movie_urls
|
||||
{
|
||||
movie_url: movies[0]&.url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class ActorSerializer < UserSerializer
|
||||
set_type :actor
|
||||
|
||||
attribute :email, if: ->(_object, params) { params[:conditionals_off].nil? }
|
||||
|
||||
has_many(
|
||||
:played_movies,
|
||||
serializer: :movie,
|
||||
links: :movie_urls,
|
||||
if: ->(_object, params) { params[:conditionals_off].nil? }
|
||||
) do |object|
|
||||
object.movies
|
||||
end
|
||||
end
|
||||
|
||||
class CamelCaseActorSerializer
|
||||
include JSONAPI::Serializer
|
||||
|
||||
set_key_transform :camel
|
||||
|
||||
set_id :uid
|
||||
set_type :user_actor
|
||||
attributes :first_name
|
||||
|
||||
link :movie_url do |obj|
|
||||
obj.movie_urls.values[0]
|
||||
end
|
||||
|
||||
has_many(
|
||||
:played_movies,
|
||||
serializer: :movie
|
||||
) do |object|
|
||||
object.movies
|
||||
end
|
||||
end
|
||||
|
||||
class BadMovieSerializerActorSerializer < ActorSerializer
|
||||
has_many :played_movies, serializer: :bad, object_method_name: :movies
|
||||
end
|
||||
|
||||
module Cached
|
||||
class ActorSerializer < ::ActorSerializer
|
||||
# TODO: Fix this, the serializer gets cached on inherited classes...
|
||||
has_many :played_movies, serializer: :movie do |object|
|
||||
object.movies
|
||||
end
|
||||
|
||||
cache_options(
|
||||
store: ActiveSupport::Cache::MemoryStore.new,
|
||||
namespace: 'test'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
module Instrumented
|
||||
class ActorSerializer < ::ActorSerializer
|
||||
include ::JSONAPI::Serializer::Instrumentation
|
||||
end
|
||||
end
|
127
spec/fixtures/movie.rb
vendored
127
spec/fixtures/movie.rb
vendored
@ -1,127 +0,0 @@
|
||||
class Movie
|
||||
attr_accessor(
|
||||
:id,
|
||||
:name,
|
||||
:year,
|
||||
:actor_or_user,
|
||||
:actors,
|
||||
:actor_ids,
|
||||
:polymorphics,
|
||||
:owner,
|
||||
:owner_id
|
||||
)
|
||||
|
||||
def self.fake(id = nil)
|
||||
faked = new
|
||||
faked.id = id || SecureRandom.uuid
|
||||
faked.name = FFaker::Movie.title
|
||||
faked.year = FFaker::Vehicle.year
|
||||
faked.actors = []
|
||||
faked.actor_ids = []
|
||||
faked.polymorphics = []
|
||||
faked
|
||||
end
|
||||
|
||||
def url(obj = nil)
|
||||
@url ||= FFaker::Internet.http_url
|
||||
return @url if obj.nil?
|
||||
|
||||
"#{@url}?#{obj.hash}"
|
||||
end
|
||||
|
||||
def owner=(ownr)
|
||||
@owner = ownr
|
||||
@owner_id = ownr.uid
|
||||
end
|
||||
|
||||
def actors=(acts)
|
||||
@actors = acts
|
||||
@actor_ids = actors.map do |actor|
|
||||
actor.movies << self
|
||||
actor.uid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MovieSerializer
|
||||
include JSONAPI::Serializer
|
||||
|
||||
set_type :movie
|
||||
|
||||
attribute :released_in_year, &:year
|
||||
attributes :name
|
||||
attribute :release_year do |object, _params|
|
||||
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) }
|
||||
}
|
||||
)
|
||||
has_one(
|
||||
:creator,
|
||||
object_method_name: :owner,
|
||||
id_method_name: :uid,
|
||||
serializer: ->(object, _params) { UserSerializer if object.is_a?(User) }
|
||||
)
|
||||
has_many(
|
||||
:actors_and_users,
|
||||
id_method_name: :uid,
|
||||
polymorphic: {
|
||||
Actor => :actor,
|
||||
User => :user
|
||||
}
|
||||
) do |obj|
|
||||
obj.polymorphics
|
||||
end
|
||||
|
||||
has_many(
|
||||
:dynamic_actors_and_users,
|
||||
id_method_name: :uid,
|
||||
polymorphic: true
|
||||
) do |obj|
|
||||
obj.polymorphics
|
||||
end
|
||||
|
||||
has_many(
|
||||
:auto_detected_actors_and_users,
|
||||
id_method_name: :uid
|
||||
) do |obj|
|
||||
obj.polymorphics
|
||||
end
|
||||
end
|
||||
|
||||
module Cached
|
||||
class MovieSerializer < ::MovieSerializer
|
||||
cache_options(
|
||||
store: ActorSerializer.cache_store_instance,
|
||||
namespace: 'test'
|
||||
)
|
||||
|
||||
has_one(
|
||||
:creator,
|
||||
id_method_name: :uid,
|
||||
serializer: :actor,
|
||||
# TODO: Remove this undocumented option.
|
||||
# Delegate the caching to the serializer exclusively.
|
||||
cached: false
|
||||
) do |obj|
|
||||
obj.owner
|
||||
end
|
||||
end
|
||||
end
|
@ -1,63 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:actor) do
|
||||
act = Actor.fake
|
||||
act.movies = [Movie.fake]
|
||||
act
|
||||
end
|
||||
let(:params) { {} }
|
||||
let(:serialized) do
|
||||
ActorSerializer.new(actor, params).serializable_hash.as_json
|
||||
end
|
||||
|
||||
describe 'attributes' do
|
||||
it do
|
||||
expect(serialized['data']).to have_id(actor.uid)
|
||||
expect(serialized['data']).to have_type('actor')
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_jsonapi_attributes('first_name', 'last_name', 'email').exactly
|
||||
expect(serialized['data']).to have_attribute('first_name')
|
||||
.with_value(actor.first_name)
|
||||
expect(serialized['data']).to have_attribute('last_name')
|
||||
.with_value(actor.last_name)
|
||||
expect(serialized['data']).to have_attribute('email')
|
||||
.with_value(actor.email)
|
||||
end
|
||||
|
||||
context 'with nil identifier' do
|
||||
before { actor.uid = nil }
|
||||
|
||||
it { expect(serialized['data']).to have_id(nil) }
|
||||
end
|
||||
|
||||
context 'with `if` conditions' do
|
||||
let(:params) { { params: { conditionals_off: 'yes' } } }
|
||||
|
||||
it do
|
||||
expect(serialized['data']).not_to have_attribute('email')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with include and fields' do
|
||||
let(:params) do
|
||||
{
|
||||
include: [:played_movies],
|
||||
fields: { movie: [:release_year], actor: [:first_name] }
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized['data'])
|
||||
.to have_jsonapi_attributes(:first_name).exactly
|
||||
|
||||
expect(serialized['included']).to include(
|
||||
have_type('movie')
|
||||
.and(have_id(actor.movies[0].id))
|
||||
.and(have_jsonapi_attributes('release_year').exactly)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,80 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:actor) do
|
||||
faked = Actor.fake
|
||||
movie = Movie.fake
|
||||
movie.owner = User.fake
|
||||
movie.actors = [faked]
|
||||
faked.movies = [movie]
|
||||
faked
|
||||
end
|
||||
let(:cache_store) { Cached::ActorSerializer.cache_store_instance }
|
||||
|
||||
describe 'with caching' do
|
||||
it do
|
||||
expect(cache_store.delete(actor, namespace: 'test')).to be(false)
|
||||
|
||||
Cached::ActorSerializer.new(
|
||||
[actor, actor], include: ['played_movies', 'played_movies.owner']
|
||||
).serializable_hash
|
||||
|
||||
expect(cache_store.delete(actor, namespace: 'test')).to be(true)
|
||||
expect(cache_store.delete(actor.movies[0], namespace: 'test')).to be(true)
|
||||
expect(
|
||||
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
|
@ -1,25 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:actor) { Actor.fake }
|
||||
let(:params) { {} }
|
||||
|
||||
describe 'with errors' do
|
||||
it do
|
||||
expect do
|
||||
BadMovieSerializerActorSerializer.new(
|
||||
actor, include: ['played_movies']
|
||||
)
|
||||
end.to raise_error(
|
||||
NameError, /cannot resolve a serializer class for 'bad'/
|
||||
)
|
||||
end
|
||||
|
||||
it do
|
||||
expect { ActorSerializer.new(actor, include: ['bad_include']) }
|
||||
.to raise_error(
|
||||
JSONAPI::Serializer::UnsupportedIncludeError, /bad_include is not specified as a relationship/
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
@ -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
|
@ -1,19 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:actor) { Actor.fake }
|
||||
let(:params) { {} }
|
||||
let(:serialized) do
|
||||
CamelCaseActorSerializer.new(actor, params).serializable_hash.as_json
|
||||
end
|
||||
|
||||
describe 'camel case key tranformation' do
|
||||
it do
|
||||
expect(serialized['data']).to have_id(actor.uid)
|
||||
expect(serialized['data']).to have_type('UserActor')
|
||||
expect(serialized['data']).to have_attribute('FirstName')
|
||||
expect(serialized['data']).to have_relationship('PlayedMovies')
|
||||
expect(serialized['data']).to have_link('MovieUrl').with_value(nil)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,47 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:movie) do
|
||||
faked = Movie.fake
|
||||
faked.actors = [Actor.fake]
|
||||
faked
|
||||
end
|
||||
let(:params) { {} }
|
||||
let(:serialized) do
|
||||
MovieSerializer.new(movie, params).serializable_hash.as_json
|
||||
end
|
||||
|
||||
describe 'links' do
|
||||
it do
|
||||
expect(serialized['data']).to have_link('self').with_value(movie.url)
|
||||
expect(serialized['data']['relationships']['actors'])
|
||||
.to have_link('actors_self').with_value(movie.url)
|
||||
expect(serialized['data']['relationships']['actors'])
|
||||
.to have_link('related').with_value(movie.url(movie))
|
||||
end
|
||||
|
||||
context 'with included records' do
|
||||
let(:serialized) do
|
||||
ActorSerializer.new(movie.actors[0]).serializable_hash.as_json
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized['data']['relationships']['played_movies'])
|
||||
.to have_link('movie_url').with_value(movie.url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with root link' do
|
||||
let(:params) do
|
||||
{
|
||||
links: { 'root_link' => FFaker::Internet.http_url }
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized)
|
||||
.to have_link('root_link').with_value(params[:links]['root_link'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,25 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:user) { User.fake }
|
||||
let(:params) { {} }
|
||||
let(:serialized) do
|
||||
UserSerializer.new(user, params).serializable_hash.as_json
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized['data']).to have_meta('email_length' => user.email.size)
|
||||
end
|
||||
|
||||
context 'with root meta' do
|
||||
let(:params) do
|
||||
{
|
||||
meta: { 'code' => FFaker::Internet.password }
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized).to have_meta(params[:meta])
|
||||
end
|
||||
end
|
||||
end
|
@ -1,146 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JSONAPI::Serializer do
|
||||
let(:movie) do
|
||||
mov = Movie.fake
|
||||
mov.actors = rand(2..5).times.map { Actor.fake }
|
||||
mov.owner = User.fake
|
||||
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) { {} }
|
||||
let(:serialized) do
|
||||
MovieSerializer.new(movie, params).serializable_hash.as_json
|
||||
end
|
||||
|
||||
describe 'relationships' do
|
||||
it do
|
||||
actors_rel = movie.actors.map { |a| { 'id' => a.uid, 'type' => 'actor' } }
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('actors').with_data(actors_rel)
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('owner')
|
||||
.with_data('id' => movie.owner.uid, 'type' => 'user')
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('creator')
|
||||
.with_data('id' => movie.owner.uid, 'type' => 'user')
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('actors_and_users')
|
||||
.with_data(
|
||||
[
|
||||
{ 'id' => movie.polymorphics[0].uid, 'type' => 'user' },
|
||||
{ 'id' => movie.polymorphics[1].uid, 'type' => 'actor' }
|
||||
]
|
||||
)
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('dynamic_actors_and_users')
|
||||
.with_data(
|
||||
[
|
||||
{ 'id' => movie.polymorphics[0].uid, 'type' => 'user' },
|
||||
{ 'id' => movie.polymorphics[1].uid, 'type' => 'actor' }
|
||||
]
|
||||
)
|
||||
|
||||
expect(serialized['data'])
|
||||
.to have_relationship('auto_detected_actors_and_users')
|
||||
.with_data(
|
||||
[
|
||||
{ 'id' => movie.polymorphics[0].uid, 'type' => 'user' },
|
||||
{ 'id' => movie.polymorphics[1].uid, 'type' => 'actor' }
|
||||
]
|
||||
)
|
||||
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] }
|
||||
end
|
||||
|
||||
it do
|
||||
movie.actors.each do |actor|
|
||||
expect(serialized['included']).to include(
|
||||
have_type('actor')
|
||||
.and(have_id(actor.uid))
|
||||
.and(have_relationship('played_movies')
|
||||
.with_data([{ 'id' => actor.movies[0].id, 'type' => 'movie' }]))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with `if` conditions' do
|
||||
let(:params) do
|
||||
{
|
||||
include: ['actors'],
|
||||
params: { conditionals_off: 'yes' }
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
movie.actors.each do |actor|
|
||||
expect(serialized['included']).not_to include(
|
||||
have_type('actor')
|
||||
.and(have_id(actor.uid))
|
||||
.and(have_relationship('played_movies'))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with has_many polymorphic' do
|
||||
let(:params) do
|
||||
{ include: ['actors_and_users.played_movies'] }
|
||||
end
|
||||
|
||||
it do
|
||||
expect(serialized['included']).to include(
|
||||
have_type('user').and(have_id(movie.polymorphics[0].uid))
|
||||
)
|
||||
|
||||
expect(serialized['included']).to include(
|
||||
have_type('movie').and(have_id(movie.polymorphics[1].movies[0].id))
|
||||
)
|
||||
|
||||
expect(serialized['included']).to include(
|
||||
have_type('actor')
|
||||
.and(have_id(movie.polymorphics[1].uid))
|
||||
.and(
|
||||
have_relationship('played_movies').with_data(
|
||||
[{
|
||||
'id' => movie.polymorphics[1].movies[0].id,
|
||||
'type' => 'movie'
|
||||
}]
|
||||
)
|
||||
)
|
||||
)
|
||||
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
|
155
spec/lib/extensions/active_record_spec.rb
Normal file
155
spec/lib/extensions/active_record_spec.rb
Normal file
@ -0,0 +1,155 @@
|
||||
require 'spec_helper'
|
||||
require 'active_record'
|
||||
require 'sqlite3'
|
||||
|
||||
describe 'active record' do
|
||||
|
||||
# Setup DB
|
||||
before(:all) do
|
||||
@db_file = "test.db"
|
||||
|
||||
# Open a database
|
||||
db = SQLite3::Database.new @db_file
|
||||
|
||||
# Create tables
|
||||
db.execute_batch <<-SQL
|
||||
create table suppliers (
|
||||
name varchar(30),
|
||||
id int primary key
|
||||
);
|
||||
|
||||
create table accounts (
|
||||
name varchar(30),
|
||||
id int primary key,
|
||||
supplier_id int,
|
||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(id)
|
||||
);
|
||||
SQL
|
||||
|
||||
# Insert records
|
||||
@account_id = 2
|
||||
@supplier_id = 1
|
||||
@supplier_id_without_account = 3
|
||||
db.execute_batch <<-SQL
|
||||
insert into suppliers values ('Supplier1', #{@supplier_id}),
|
||||
('SupplierWithoutAccount', #{@supplier_id_without_account});
|
||||
insert into accounts values ('Dollar Account', #{@account_id}, #{@supplier_id});
|
||||
SQL
|
||||
end
|
||||
|
||||
# Setup Active Record
|
||||
before(:all) do
|
||||
class Supplier < ActiveRecord::Base
|
||||
has_one :account
|
||||
end
|
||||
|
||||
class Account < ActiveRecord::Base
|
||||
belongs_to :supplier
|
||||
end
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => 'sqlite3',
|
||||
:database => @db_file
|
||||
)
|
||||
end
|
||||
|
||||
context 'has one patch' do
|
||||
|
||||
it 'has account_id method for a supplier' do
|
||||
expect(Supplier.first.respond_to?(:account_id)).to be true
|
||||
expect(Supplier.first.account_id).to eq @account_id
|
||||
end
|
||||
|
||||
it 'has account_id method return nil if account not present' do
|
||||
expect(Supplier.find(@supplier_id_without_account).account_id).to eq nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Clean up DB
|
||||
after(:all) do
|
||||
File.delete(@db_file) if File.exist?(@db_file)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'active record has_one through' do
|
||||
# Setup DB
|
||||
before(:all) do
|
||||
@db_file = "test_two.db"
|
||||
|
||||
# Open a database
|
||||
db = SQLite3::Database.new @db_file
|
||||
|
||||
# Create tables
|
||||
db.execute_batch <<-SQL
|
||||
create table forests (
|
||||
id int primary key,
|
||||
name varchar(30)
|
||||
);
|
||||
|
||||
create table trees (
|
||||
id int primary key,
|
||||
forest_id int,
|
||||
name varchar(30),
|
||||
|
||||
FOREIGN KEY (forest_id) REFERENCES forests(id)
|
||||
);
|
||||
|
||||
create table fruits (
|
||||
id int primary key,
|
||||
tree_id int,
|
||||
name varchar(30),
|
||||
|
||||
FOREIGN KEY (tree_id) REFERENCES trees(id)
|
||||
);
|
||||
SQL
|
||||
|
||||
# Insert records
|
||||
db.execute_batch <<-SQL
|
||||
insert into forests values (1, 'sherwood');
|
||||
insert into trees values (2, 1,'pine');
|
||||
insert into fruits values (3, 2, 'pine nut');
|
||||
|
||||
insert into fruits(id,name) values (4,'apple');
|
||||
SQL
|
||||
end
|
||||
|
||||
# Setup Active Record
|
||||
before(:all) do
|
||||
class Forest < ActiveRecord::Base
|
||||
has_many :trees
|
||||
end
|
||||
|
||||
class Tree < ActiveRecord::Base
|
||||
belongs_to :forest
|
||||
end
|
||||
|
||||
class Fruit < ActiveRecord::Base
|
||||
belongs_to :tree
|
||||
has_one :forest, through: :tree
|
||||
end
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => 'sqlite3',
|
||||
:database => @db_file
|
||||
)
|
||||
end
|
||||
|
||||
context 'revenue' do
|
||||
it 'has an forest_id' do
|
||||
expect(Fruit.find(3).respond_to?(:forest_id)).to be true
|
||||
expect(Fruit.find(3).forest_id).to eq 1
|
||||
expect(Fruit.find(3).forest.name).to eq "sherwood"
|
||||
end
|
||||
|
||||
it 'has nil if tree id not available' do
|
||||
expect(Fruit.find(4).respond_to?(:tree_id)).to be true
|
||||
expect(Fruit.find(4).forest_id).to eq nil
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up DB
|
||||
after(:all) do
|
||||
File.delete(@db_file) if File.exist?(@db_file)
|
||||
end
|
||||
end
|
73
spec/lib/helpers_spec.rb
Normal file
73
spec/lib/helpers_spec.rb
Normal file
@ -0,0 +1,73 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi do
|
||||
describe '.call_proc' do
|
||||
context 'with a Proc' do
|
||||
context 'with no parameters' do
|
||||
let(:function) { proc { 42 } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(42)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single parameter' do
|
||||
let(:function) { proc { |a| 42 + a } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(43)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple parameters' do
|
||||
let(:function) { proc { |a, b| 42 + a + b } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(45)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default parameters' do
|
||||
let(:function) { proc { |a = 0, b = 0| 42 + a + b } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(45)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a lambda' do
|
||||
context 'with no parameters' do
|
||||
let(:function) { -> { 42 } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(42)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single parameter' do
|
||||
let(:function) { ->(a) { 42 + a } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(43)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple parameters' do
|
||||
let(:function) { ->(a, b) { 42 + a + b } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(45)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default parameters' do
|
||||
let(:function) { ->(a = 0, b = 0) { 42 + a + b } }
|
||||
|
||||
it 'calls the proc' do
|
||||
expect(FastJsonapi.call_proc(function, 1, 2)).to eq(45)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
57
spec/lib/instrumentation/as_notifications_negative_spec.rb
Normal file
57
spec/lib/instrumentation/as_notifications_negative_spec.rb
Normal file
@ -0,0 +1,57 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context 'instrument' do
|
||||
|
||||
before(:each) do
|
||||
options = {}
|
||||
options[:meta] = { total: 2 }
|
||||
options[:include] = [:actors]
|
||||
|
||||
movies = build_movies(2)
|
||||
@serializer = MovieSerializer.new(movies, options)
|
||||
end
|
||||
|
||||
context 'serializable_hash' do
|
||||
|
||||
it 'should send not notifications' do
|
||||
events = []
|
||||
|
||||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION) do |*args|
|
||||
events << ActiveSupport::Notifications::Event.new(*args)
|
||||
end
|
||||
|
||||
serialized_hash = @serializer.serializable_hash
|
||||
|
||||
expect(events.length).to eq(0)
|
||||
|
||||
expect(serialized_hash.key?(:data)).to eq(true)
|
||||
expect(serialized_hash.key?(:meta)).to eq(true)
|
||||
expect(serialized_hash.key?(:included)).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'serialized_json' do
|
||||
|
||||
it 'should send not notifications' do
|
||||
events = []
|
||||
|
||||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION) do |*args|
|
||||
events << ActiveSupport::Notifications::Event.new(*args)
|
||||
end
|
||||
|
||||
json = @serializer.serialized_json
|
||||
|
||||
expect(events.length).to eq(0)
|
||||
|
||||
expect(json.length).to be > 50
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
83
spec/lib/instrumentation/as_notifications_spec.rb
Normal file
83
spec/lib/instrumentation/as_notifications_spec.rb
Normal file
@ -0,0 +1,83 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context 'instrument' do
|
||||
|
||||
before(:all) do
|
||||
require 'fast_jsonapi/instrumentation'
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
[ :serialized_json, :serializable_hash ].each do |m|
|
||||
alias_command = "alias_method :#{m}, :#{m}_without_instrumentation"
|
||||
FastJsonapi::ObjectSerializer.class_eval(alias_command)
|
||||
|
||||
remove_command = "remove_method :#{m}_without_instrumentation"
|
||||
FastJsonapi::ObjectSerializer.class_eval(remove_command)
|
||||
end
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
options = {}
|
||||
options[:meta] = { total: 2 }
|
||||
options[:include] = [:actors]
|
||||
|
||||
movies = build_movies(2)
|
||||
@serializer = MovieSerializer.new(movies, options)
|
||||
end
|
||||
|
||||
context 'serializable_hash' do
|
||||
|
||||
it 'should send notifications' do
|
||||
events = []
|
||||
|
||||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION) do |*args|
|
||||
events << ActiveSupport::Notifications::Event.new(*args)
|
||||
end
|
||||
|
||||
serialized_hash = @serializer.serializable_hash
|
||||
|
||||
expect(events.length).to eq(1)
|
||||
|
||||
event = events.first
|
||||
|
||||
expect(event.duration).to be > 0
|
||||
expect(event.payload).to eq({ name: 'MovieSerializer' })
|
||||
expect(event.name).to eq(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION)
|
||||
|
||||
expect(serialized_hash.key?(:data)).to eq(true)
|
||||
expect(serialized_hash.key?(:meta)).to eq(true)
|
||||
expect(serialized_hash.key?(:included)).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'serialized_json' do
|
||||
|
||||
it 'should send notifications' do
|
||||
events = []
|
||||
|
||||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION) do |*args|
|
||||
events << ActiveSupport::Notifications::Event.new(*args)
|
||||
end
|
||||
|
||||
json = @serializer.serialized_json
|
||||
|
||||
expect(events.length).to eq(1)
|
||||
|
||||
event = events.first
|
||||
|
||||
expect(event.duration).to be > 0
|
||||
expect(event.payload).to eq({ name: 'MovieSerializer' })
|
||||
expect(event.name).to eq(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION)
|
||||
|
||||
expect(json.length).to be > 50
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
|
||||
context 'instrument' do
|
||||
context 'skylight' do
|
||||
# skip for normal runs because this could alter some
|
||||
# other test by insterting the instrumentation
|
||||
xit 'make sure requiring skylight normalizers works' do
|
||||
require 'fast_jsonapi/instrumentation/skylight'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
118
spec/lib/object_serializer_attribute_param_spec.rb
Normal file
118
spec/lib/object_serializer_attribute_param_spec.rb
Normal file
@ -0,0 +1,118 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context "params option" do
|
||||
let(:hash) { serializer.serializable_hash }
|
||||
|
||||
before(:context) do
|
||||
class Movie
|
||||
def viewed?(user)
|
||||
user.viewed.include?(id)
|
||||
end
|
||||
end
|
||||
|
||||
class MovieSerializer
|
||||
attribute :viewed do |movie, params|
|
||||
params[:user] ? movie.viewed?(params[:user]) : false
|
||||
end
|
||||
|
||||
attribute :no_param_attribute do |movie|
|
||||
"no-param-attribute"
|
||||
end
|
||||
end
|
||||
|
||||
User = Struct.new(:viewed)
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
Object.send(:remove_const, User) if Object.constants.include?(User)
|
||||
end
|
||||
|
||||
context "enforces a hash only params" do
|
||||
let(:params) { User.new([]) }
|
||||
|
||||
it "fails when creating a serializer with an object as params" do
|
||||
expect(-> { MovieSerializer.new(movie, {params: User.new([])}) }).to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "succeeds creating a serializer with a hash" do
|
||||
expect(-> { MovieSerializer.new(movie, {params: {current_user: User.new([])}}) }).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "passing params to the serializer" do
|
||||
let(:params) { {user: User.new([movie.id])} }
|
||||
let(:options_with_params) { {params: params} }
|
||||
|
||||
context "with a single record" do
|
||||
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
|
||||
|
||||
it "handles attributes that use params" do
|
||||
expect(hash[:data][:attributes][:viewed]).to eq(true)
|
||||
end
|
||||
|
||||
it "handles attributes that don't use params" do
|
||||
expect(hash[:data][:attributes][:no_param_attribute]).to eq("no-param-attribute")
|
||||
end
|
||||
end
|
||||
|
||||
context "with a list of records" do
|
||||
let(:movies) { build_movies(3) }
|
||||
let(:user) { User.new(movies.map { |m| [true, false].sample ? m.id : nil }.compact) }
|
||||
let(:params) { {user: user} }
|
||||
let(:serializer) { MovieSerializer.new(movies, options_with_params) }
|
||||
|
||||
it "has 3 items" do
|
||||
hash[:data].length == 3
|
||||
end
|
||||
|
||||
it "handles passing params to a list of resources" do
|
||||
param_attribute_values = hash[:data].map { |data| [data[:id], data[:attributes][:viewed]] }
|
||||
expected_values = movies.map { |m| [m.id.to_s, user.viewed.include?(m.id)] }
|
||||
|
||||
expect(param_attribute_values).to eq(expected_values)
|
||||
end
|
||||
|
||||
it "handles attributes without params" do
|
||||
no_param_attribute_values = hash[:data].map { |data| data[:attributes][:no_param_attribute] }
|
||||
expected_values = (1..3).map { "no-param-attribute" }
|
||||
|
||||
expect(no_param_attribute_values).to eq(expected_values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "without passing params to the serializer" do
|
||||
context "with a single movie" do
|
||||
let(:serializer) { MovieSerializer.new(movie) }
|
||||
|
||||
it "handles param attributes" do
|
||||
expect(hash[:data][:attributes][:viewed]).to eq(false)
|
||||
end
|
||||
|
||||
it "handles attributes that don't use params" do
|
||||
expect(hash[:data][:attributes][:no_param_attribute]).to eq("no-param-attribute")
|
||||
end
|
||||
end
|
||||
|
||||
context "with multiple movies" do
|
||||
let(:serializer) { MovieSerializer.new(build_movies(3)) }
|
||||
|
||||
it "handles attributes with params" do
|
||||
param_attribute_values = hash[:data].map { |data| data[:attributes][:viewed] }
|
||||
|
||||
expect(param_attribute_values).to eq([false, false, false])
|
||||
end
|
||||
|
||||
it "handles attributes that don't use params" do
|
||||
no_param_attribute_values = hash[:data].map { |data| data[:attributes][:no_param_attribute] }
|
||||
expected_attribute_values = (1..3).map { "no-param-attribute" }
|
||||
|
||||
expect(no_param_attribute_values).to eq(expected_attribute_values)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
107
spec/lib/object_serializer_caching_spec.rb
Normal file
107
spec/lib/object_serializer_caching_spec.rb
Normal file
@ -0,0 +1,107 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context 'when caching has_many' do
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
options = {}
|
||||
options[:meta] = { total: 2 }
|
||||
options[:links] = { self: 'self' }
|
||||
|
||||
options[:include] = [:actors]
|
||||
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
|
||||
expect(serializable_hash[:data][0][:attributes].length).to eq 2
|
||||
|
||||
expect(serializable_hash[:meta]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:links]).to be_instance_of(Hash)
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included][0]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:included].length).to eq 3
|
||||
|
||||
serializable_hash = CachingMovieSerializer.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:meta]).to be nil
|
||||
expect(serializable_hash[:links]).to be nil
|
||||
expect(serializable_hash[:included]).to be nil
|
||||
end
|
||||
|
||||
it 'uses cached values for the record' do
|
||||
previous_name = movie.name
|
||||
previous_actors = movie.actors
|
||||
CachingMovieSerializer.new(movie).serializable_hash
|
||||
|
||||
movie.name = 'should not match'
|
||||
allow(movie).to receive(:actor_ids).and_return([99])
|
||||
|
||||
expect(previous_name).not_to eq(movie.name)
|
||||
expect(previous_actors).not_to eq(movie.actors)
|
||||
serializable_hash = CachingMovieSerializer.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq(previous_name)
|
||||
expect(serializable_hash[:data][:relationships][:actors][:data].length).to eq movie.actors.length
|
||||
end
|
||||
|
||||
it 'uses cached values for has many as specified' do
|
||||
previous_name = movie.name
|
||||
previous_actors = movie.actors
|
||||
CachingMovieWithHasManySerializer.new(movie).serializable_hash
|
||||
|
||||
movie.name = 'should not match'
|
||||
allow(movie).to receive(:actor_ids).and_return([99])
|
||||
|
||||
expect(previous_name).not_to eq(movie.name)
|
||||
expect(previous_actors).not_to eq(movie.actors)
|
||||
serializable_hash = CachingMovieWithHasManySerializer.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq(previous_name)
|
||||
expect(serializable_hash[:data][:relationships][:actors][:data].length).to eq previous_actors.length
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
||||
context 'when using deprecated cache options' do
|
||||
let(:deprecated_caching_movie_serializer_class) do
|
||||
rails = OpenStruct.new
|
||||
rails.cache = ActiveSupport::Cache::MemoryStore.new
|
||||
stub_const('Rails', rails)
|
||||
|
||||
Class.new do
|
||||
def self.name
|
||||
'DeprecatedCachingMovieSerializer'
|
||||
end
|
||||
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options enabled: true
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses cached values for the record' do
|
||||
previous_name = movie.name
|
||||
previous_actors = movie.actors
|
||||
deprecated_caching_movie_serializer_class.new(movie).serializable_hash
|
||||
|
||||
movie.name = 'should not match'
|
||||
allow(movie).to receive(:actor_ids).and_return([99])
|
||||
|
||||
expect(previous_name).not_to eq(movie.name)
|
||||
expect(previous_actors).not_to eq(movie.actors)
|
||||
serializable_hash = deprecated_caching_movie_serializer_class.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq(previous_name)
|
||||
expect(serializable_hash[:data][:relationships][:actors][:data].length).to eq movie.actors.length
|
||||
end
|
||||
end
|
||||
end
|
690
spec/lib/object_serializer_class_methods_spec.rb
Normal file
690
spec/lib/object_serializer_class_methods_spec.rb
Normal file
@ -0,0 +1,690 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
|
||||
include_context 'movie class'
|
||||
|
||||
describe '#has_many' do
|
||||
subject(:relationship) { serializer.relationships_to_serialize[:roles] }
|
||||
|
||||
before do
|
||||
serializer.has_many *children
|
||||
end
|
||||
|
||||
after do
|
||||
serializer.relationships_to_serialize = {}
|
||||
end
|
||||
|
||||
context 'with namespace' do
|
||||
|
||||
before do
|
||||
class AppName::V1::RoleSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:serializer) { AppName::V1::MovieSerializer }
|
||||
let(:children) { [:roles] }
|
||||
let(:relationship_serializer) { AppName::V1::RoleSerializer }
|
||||
|
||||
context 'with overrides' do
|
||||
let(:children) { [:roles, id_method_name: :roles_only_ids, record_type: :super_role] }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :roles_only_ids, :super_role
|
||||
end
|
||||
|
||||
context 'without overrides' do
|
||||
let(:children) { [:roles] }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :role_ids, :role
|
||||
end
|
||||
end
|
||||
|
||||
context 'without namespace' do
|
||||
|
||||
before do
|
||||
class RoleSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:serializer) { MovieSerializer }
|
||||
let(:relationship_serializer) { RoleSerializer }
|
||||
|
||||
context 'with overrides' do
|
||||
let(:children) { [:roles, id_method_name: :roles_only_ids, record_type: :super_role] }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :roles_only_ids, :super_role
|
||||
end
|
||||
|
||||
context 'without overrides' do
|
||||
let(:children) { [:roles] }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :role_ids, :role
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_many with block' do
|
||||
before do
|
||||
MovieSerializer.has_many :awards do |movie|
|
||||
movie.actors.map(&:awards).flatten
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize.delete(:awards)
|
||||
end
|
||||
|
||||
context 'awards is not included' do
|
||||
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:data][:relationships][:awards][:data].length).to eq(6)
|
||||
expect(hash[:data][:relationships][:awards][:data][0]).to eq({ id: '9', type: :award })
|
||||
expect(hash[:data][:relationships][:awards][:data][-1]).to eq({ id: '28', type: :award })
|
||||
end
|
||||
end
|
||||
|
||||
context 'state is included' do
|
||||
subject(:hash) { MovieSerializer.new(movie, include: [:awards]).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:included].length).to eq 6
|
||||
expect(hash[:included][0][:id]).to eq '9'
|
||||
expect(hash[:included][0][:type]).to eq :award
|
||||
expect(hash[:included][0][:attributes]).to eq({ id: 9, title: 'Test Award 9' })
|
||||
expect(hash[:included][0][:relationships]).to eq({ actor: { data: { id: '1', type: :actor } } })
|
||||
expect(hash[:included][-1][:id]).to eq '28'
|
||||
expect(hash[:included][-1][:type]).to eq :award
|
||||
expect(hash[:included][-1][:attributes]).to eq({ id: 28, title: 'Test Award 28' })
|
||||
expect(hash[:included][-1][:relationships]).to eq({ actor: { data: { id: '3', type: :actor } } })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_many with block and id_method_name' do
|
||||
before do
|
||||
MovieSerializer.has_many(:awards, id_method_name: :imdb_award_id) do |movie|
|
||||
movie.actors.map(&:awards).flatten
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize.delete(:awards)
|
||||
end
|
||||
|
||||
context 'awards is not included' do
|
||||
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
it 'returns correct hash where id is obtained from the method specified via `id_method_name`' do
|
||||
expected_award_data = movie.actors.map(&:awards).flatten.map do |actor|
|
||||
{ id: actor.imdb_award_id.to_s, type: actor.class.name.downcase.to_sym }
|
||||
end
|
||||
serialized_award_data = hash[:data][:relationships][:awards][:data]
|
||||
|
||||
expect(serialized_award_data).to eq(expected_award_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_many with &:proc' do
|
||||
before do
|
||||
MovieSerializer.has_many :stars, &:actors
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize.delete(:stars)
|
||||
end
|
||||
|
||||
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:data][:relationships][:stars][:data].length).to eq(3)
|
||||
expect(hash[:data][:relationships][:stars][:data][0]).to eq({ id: '1', type: :actor })
|
||||
expect(hash[:data][:relationships][:stars][:data][1]).to eq({ id: '2', type: :actor })
|
||||
expect(hash[:data][:relationships][:stars][:data][2]).to eq({ id: '3', type: :actor })
|
||||
end
|
||||
end
|
||||
|
||||
describe '#belongs_to' do
|
||||
subject(:relationship) { MovieSerializer.relationships_to_serialize[:area] }
|
||||
|
||||
before do
|
||||
MovieSerializer.belongs_to *parent
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize = {}
|
||||
end
|
||||
|
||||
context 'with overrides' do
|
||||
|
||||
before do
|
||||
class MyAreaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:parent) { [:area, id_method_name: :blah_id, record_type: :awesome_area, serializer: :my_area] }
|
||||
let(:relationship_serializer) { MyAreaSerializer }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :blah_id, :awesome_area
|
||||
end
|
||||
|
||||
context 'without overrides' do
|
||||
|
||||
before do
|
||||
class AreaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:parent) { [:area] }
|
||||
let(:relationship_serializer) { AreaSerializer }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :area_id, :area
|
||||
end
|
||||
end
|
||||
|
||||
describe '#belongs_to with block' do
|
||||
before do
|
||||
ActorSerializer.belongs_to :state do |actor|
|
||||
actor.agency.state
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
ActorSerializer.relationships_to_serialize.delete(:actorc)
|
||||
end
|
||||
|
||||
context 'state is not included' do
|
||||
subject(:hash) { ActorSerializer.new(actor).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:data][:relationships][:state][:data]).to eq({ id: '1', type: :state })
|
||||
end
|
||||
end
|
||||
|
||||
context 'state is included' do
|
||||
subject(:hash) { ActorSerializer.new(actor, include: [:state]).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:included].length).to eq 1
|
||||
expect(hash[:included][0][:id]).to eq '1'
|
||||
expect(hash[:included][0][:type]).to eq :state
|
||||
expect(hash[:included][0][:attributes]).to eq({ id: 1, name: 'Test State 1' })
|
||||
expect(hash[:included][0][:relationships]).to eq({ agency: { data: [{ id: '432', type: :agency }] } })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#belongs_to with &:proc' do
|
||||
before do
|
||||
MovieSerializer.belongs_to :user, &:owner
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize.delete(:user)
|
||||
end
|
||||
|
||||
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:data][:relationships][:user][:data]).to eq({ id: '3', type: :owner })
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_one' do
|
||||
subject(:relationship) { MovieSerializer.relationships_to_serialize[:area] }
|
||||
|
||||
before do
|
||||
MovieSerializer.has_one *partner
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize = {}
|
||||
end
|
||||
|
||||
context 'with overrides' do
|
||||
|
||||
before do
|
||||
class MyAreaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:partner) { [:area, id_method_name: :blah_id, record_type: :awesome_area, serializer: :my_area] }
|
||||
let(:relationship_serializer) { MyAreaSerializer }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :blah_id, :awesome_area
|
||||
end
|
||||
|
||||
context 'without overrides' do
|
||||
|
||||
before do
|
||||
class AreaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
end
|
||||
|
||||
let(:partner) { [:area] }
|
||||
let(:relationship_serializer) { AreaSerializer }
|
||||
|
||||
it_behaves_like 'returning correct relationship hash', :area_id, :area
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_one with &:proc' do
|
||||
before do
|
||||
MovieSerializer.has_one :user, &:owner
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.relationships_to_serialize.delete(:user)
|
||||
end
|
||||
|
||||
subject(:hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
it 'returns correct hash' do
|
||||
expect(hash[:data][:relationships][:user][:data]).to eq({ id: '3', type: :owner })
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_id' do
|
||||
let(:params) { {} }
|
||||
subject(:serializable_hash) do
|
||||
MovieSerializer.new(resource, { params: params }).serializable_hash
|
||||
end
|
||||
|
||||
context 'method name' do
|
||||
before do
|
||||
MovieSerializer.set_id :owner_id
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.set_id nil
|
||||
end
|
||||
|
||||
context 'when one record is given' do
|
||||
let(:resource) { movie }
|
||||
|
||||
it 'returns correct hash which id equals owner_id' do
|
||||
expect(serializable_hash[:data][:id].to_i).to eq movie.owner_id
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an array of records is given' do
|
||||
let(:resource) { 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
|
||||
expect(serializable_hash[:data][1][:id].to_i).to eq movie.owner_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with block' do
|
||||
let(:params) { { prefix: 'movie' } }
|
||||
|
||||
before do
|
||||
MovieSerializer.set_id do |record, params|
|
||||
"#{params[:prefix]}-#{record.owner_id}"
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.set_id nil
|
||||
end
|
||||
|
||||
context 'when one record is given' do
|
||||
let(:resource) { movie }
|
||||
|
||||
it 'returns correct hash which id equals movie-id' do
|
||||
expect(serializable_hash[:data][:id]).to eq "movie-#{movie.owner_id}"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an array of records is given' do
|
||||
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}"
|
||||
expect(serializable_hash[:data][1][:id]).to eq "movie-#{movie.owner_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a lambda' do
|
||||
let(:params) { { prefix: 'movie' } }
|
||||
|
||||
before do
|
||||
MovieSerializer.set_id ->(record) { "#{params[:prefix]}-#{record.owner_id}" }
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.set_id nil
|
||||
end
|
||||
|
||||
let(:resource) { movie }
|
||||
|
||||
it 'returns correct hash which id equals movie-id' do
|
||||
expect(serializable_hash[:data][:id]).to eq "movie-#{movie.owner_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#use_hyphen' do
|
||||
subject { MovieSerializer.use_hyphen }
|
||||
|
||||
after do
|
||||
MovieSerializer.transform_method = nil
|
||||
end
|
||||
|
||||
it 'sets the correct transform_method when use_hyphen is used' do
|
||||
warning_message = "DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead\n"
|
||||
expect { subject }.to output(warning_message).to_stderr
|
||||
expect(MovieSerializer.instance_variable_get(:@transform_method)).to eq :dasherize
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attribute' do
|
||||
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
context 'with block' do
|
||||
before do
|
||||
movie.release_year = 2008
|
||||
MovieSerializer.attribute :title_with_year do |record|
|
||||
"#{record.name} (#{record.release_year})"
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.attributes_to_serialize.delete(:title_with_year)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq movie.name
|
||||
expect(serializable_hash[:data][:attributes][:title_with_year]).to eq "#{movie.name} (#{movie.release_year})"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with &:proc' do
|
||||
before do
|
||||
movie.release_year = 2008
|
||||
MovieSerializer.attribute :released_in_year, &:release_year
|
||||
MovieSerializer.attribute :name, &:local_name
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.attributes_to_serialize.delete(:released_in_year)
|
||||
MovieSerializer.attributes_to_serialize.delete(:name)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq "english #{movie.name}"
|
||||
expect(serializable_hash[:data][:attributes][:released_in_year]).to eq movie.release_year
|
||||
end
|
||||
end
|
||||
|
||||
context 'with lambda' do
|
||||
before do
|
||||
movie.release_year = 2008
|
||||
MovieSerializer.attribute :released_in_year, &:release_year
|
||||
MovieSerializer.attribute :name, ->(object) { object.local_name }
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.attributes_to_serialize.delete(:released_in_year)
|
||||
MovieSerializer.attributes_to_serialize.delete(:name)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:attributes][:name]).to eq "english #{movie.name}"
|
||||
expect(serializable_hash[:data][:attributes][:released_in_year]).to eq movie.release_year
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#meta' do
|
||||
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
context 'with block' do
|
||||
before do
|
||||
movie.release_year = 2008
|
||||
MovieSerializer.meta do |movie|
|
||||
{
|
||||
years_since_release: year_since_release_calculator(movie.release_year)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
movie.release_year = nil
|
||||
MovieSerializer.meta_to_serialize = nil
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:meta]).to eq ({ years_since_release: year_since_release_calculator(movie.release_year) })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with lambda' do
|
||||
before do
|
||||
movie.release_year = 2008
|
||||
MovieSerializer.meta ->(movie) { { years_since_release: year_since_release_calculator(movie.release_year) } }
|
||||
end
|
||||
|
||||
after do
|
||||
movie.release_year = nil
|
||||
MovieSerializer.meta_to_serialize = nil
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:meta]).to eq ({ years_since_release: year_since_release_calculator(movie.release_year) })
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def year_since_release_calculator(release_year)
|
||||
Date.current.year - release_year
|
||||
end
|
||||
end
|
||||
|
||||
describe '#link' do
|
||||
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
after do
|
||||
MovieSerializer.data_links = {}
|
||||
ActorSerializer.data_links = {}
|
||||
end
|
||||
|
||||
context 'with block calling instance method on serializer' do
|
||||
before do
|
||||
MovieSerializer.link(:self) do |movie_object|
|
||||
movie_object.url
|
||||
end
|
||||
end
|
||||
let(:url) { "http://movies.com/#{movie.id}" }
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:links][:self]).to eq url
|
||||
end
|
||||
end
|
||||
|
||||
context 'with block and param' do
|
||||
before do
|
||||
MovieSerializer.link(:public_url) do |movie_object|
|
||||
"http://movies.com/#{movie_object.id}"
|
||||
end
|
||||
end
|
||||
let(:url) { "http://movies.com/#{movie.id}" }
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:links][:public_url]).to eq url
|
||||
end
|
||||
end
|
||||
|
||||
context 'with method' do
|
||||
before do
|
||||
MovieSerializer.link(:object_id, :id)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:links][:object_id]).to eq movie.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with method and convention' do
|
||||
before do
|
||||
MovieSerializer.link(:url)
|
||||
end
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:data][:links][:url]).to eq movie.url
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inheriting from a parent serializer' do
|
||||
before do
|
||||
MovieSerializer.link(:url) do |movie_object|
|
||||
"http://movies.com/#{movie_object.id}"
|
||||
end
|
||||
end
|
||||
subject(:action_serializable_hash) { ActionMovieSerializer.new(movie).serializable_hash }
|
||||
subject(:horror_serializable_hash) { HorrorMovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
let(:url) { "http://movies.com/#{movie.id}" }
|
||||
|
||||
it 'returns the link for the correct sub-class' 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
|
||||
|
||||
describe 'optional links with a lambda' do
|
||||
subject(:downloadable_serializable_hash) { OptionalDownloadableMovieWithLambdaSerializer.new(movie).serializable_hash }
|
||||
|
||||
context 'when the link should be provided' do
|
||||
before { movie.release_year = 2001 }
|
||||
|
||||
it 'includes the link' do
|
||||
expect(downloadable_serializable_hash[:data][:links][:download]).to eq '/download/232'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the link should not be provided' do
|
||||
before { movie.release_year = 1970 }
|
||||
|
||||
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(build_movies(2), include: [:movie_type]).serializable_hash }
|
||||
|
||||
let(:movie_serializer_class) { "#{key_transform}_movie_serializer".classify.constantize }
|
||||
|
||||
before(:context) do
|
||||
[:dash, :camel, :camel_lower, :underscore].each do |key_transform|
|
||||
movie_serializer_name = "#{key_transform}_movie_serializer".classify
|
||||
movie_type_serializer_name = "#{key_transform}_movie_type_serializer".classify
|
||||
# https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name
|
||||
movie_serializer_class = Object.const_set(movie_serializer_name, Class.new)
|
||||
# https://rubymonk.com/learning/books/5-metaprogramming-ruby-ascent/chapters/24-eval/lessons/67-instance-eval
|
||||
movie_serializer_class.instance_eval do
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
set_key_transform key_transform
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type, serializer: "#{key_transform}_movie_type".to_sym
|
||||
end
|
||||
movie_type_serializer_class = Object.const_set(movie_type_serializer_name, Class.new)
|
||||
movie_type_serializer_class.instance_eval do
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_key_transform key_transform
|
||||
attributes :name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when key_transform is dash' do
|
||||
let(:key_transform) { :dash }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :'movie-type', :'dash-movie-type', :'release-year'
|
||||
end
|
||||
|
||||
context 'when key_transform is camel' do
|
||||
let(:key_transform) { :camel }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :MovieType, :CamelMovieType, :ReleaseYear
|
||||
end
|
||||
|
||||
context 'when key_transform is camel_lower' do
|
||||
let(:key_transform) { :camel_lower }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :movieType, :camelLowerMovieType, :releaseYear
|
||||
end
|
||||
|
||||
context 'when key_transform is underscore' do
|
||||
let(:key_transform) { :underscore }
|
||||
|
||||
it_behaves_like 'returning key transformed hash', :movie_type, :underscore_movie_type, :release_year
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_key_transform after #set_type' do
|
||||
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
|
||||
|
||||
before do
|
||||
MovieSerializer.set_type type_name
|
||||
MovieSerializer.set_key_transform :camel
|
||||
end
|
||||
|
||||
after do
|
||||
MovieSerializer.transform_method = nil
|
||||
MovieSerializer.set_type :movie
|
||||
end
|
||||
|
||||
context 'when sets singular type name' do
|
||||
let(:type_name) { :film }
|
||||
|
||||
it 'returns correct hash which type equals transformed set_type value' do
|
||||
expect(serializable_hash[:data][:type]).to eq :Film
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sets plural type name' do
|
||||
let(:type_name) { :films }
|
||||
|
||||
it 'returns correct hash which type equals transformed set_type value' do
|
||||
expect(serializable_hash[:data][:type]).to eq :Films
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
81
spec/lib/object_serializer_fields_spec.rb
Normal file
81
spec/lib/object_serializer_fields_spec.rb
Normal file
@ -0,0 +1,81 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
let(:fields) do
|
||||
{
|
||||
movie: %i[name actors advertising_campaign],
|
||||
actor: %i[name agency]
|
||||
}
|
||||
end
|
||||
|
||||
it 'only returns specified fields' do
|
||||
hash = MovieSerializer.new(movie, fields: fields).serializable_hash
|
||||
|
||||
expect(hash[:data][:attributes].keys.sort).to eq %i[name]
|
||||
end
|
||||
|
||||
it 'only returns specified relationships' do
|
||||
hash = MovieSerializer.new(movie, fields: fields).serializable_hash
|
||||
|
||||
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
|
||||
|
||||
expect(hash[:included].first[:attributes].keys.sort).to eq %i[name]
|
||||
end
|
||||
|
||||
it 'only returns specified relationships for included relationships' do
|
||||
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
|
||||
|
||||
expect(hash[:included].first[:relationships].keys.sort).to eq %i[agency]
|
||||
end
|
||||
|
||||
it 'returns all fields for included relationships when no explicit fields have been specified' do
|
||||
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
|
||||
|
||||
expect(hash[:included][3][:attributes].keys.sort).to eq %i[id name]
|
||||
end
|
||||
|
||||
it 'returns all fields for included relationships when no explicit fields have been specified' do
|
||||
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
|
||||
|
||||
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
|
189
spec/lib/object_serializer_inheritance_spec.rb
Normal file
189
spec/lib/object_serializer_inheritance_spec.rb
Normal file
@ -0,0 +1,189 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
|
||||
after(:all) do
|
||||
classes_to_remove = %i[
|
||||
User
|
||||
UserSerializer
|
||||
Country
|
||||
CountrySerializer
|
||||
Employee
|
||||
EmployeeSerializer
|
||||
Photo
|
||||
PhotoSerializer
|
||||
EmployeeAccount
|
||||
]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
class User
|
||||
attr_accessor :id, :first_name, :last_name, :uuid
|
||||
|
||||
attr_accessor :address_ids, :country_id
|
||||
|
||||
def photo
|
||||
p = Photo.new
|
||||
p.id = 1
|
||||
p.user_id = id
|
||||
p
|
||||
end
|
||||
|
||||
def photo_id
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
class UserSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :user
|
||||
set_id :uuid
|
||||
attributes :first_name, :last_name
|
||||
|
||||
attribute :full_name do |user, params|
|
||||
"#{user.first_name} #{user.last_name}"
|
||||
end
|
||||
|
||||
has_many :addresses, cached: true
|
||||
belongs_to :country
|
||||
has_one :photo
|
||||
end
|
||||
|
||||
class Address
|
||||
attr_accessor :street, :city, :state, :postal_code
|
||||
end
|
||||
|
||||
class AddressSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :street, :city, :state, :postal_code
|
||||
end
|
||||
|
||||
class Photo
|
||||
attr_accessor :id, :user_id
|
||||
end
|
||||
|
||||
class PhotoSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :name
|
||||
end
|
||||
|
||||
class Country
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
class CountrySerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :name
|
||||
end
|
||||
|
||||
class EmployeeAccount
|
||||
attr_accessor :id, :employee_id
|
||||
end
|
||||
|
||||
class EmployeeAccountSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
belongs_to :employee
|
||||
end
|
||||
|
||||
class Employee < User
|
||||
attr_accessor :id, :location, :compensation
|
||||
|
||||
def account
|
||||
a = EmployeeAccount.new
|
||||
a.id = 1
|
||||
a.employee_id = id
|
||||
a
|
||||
end
|
||||
|
||||
def account_id
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
class EmployeeSerializer < UserSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :location
|
||||
attributes :compensation
|
||||
|
||||
has_one :account, serializer: EmployeeAccountSerializer
|
||||
end
|
||||
|
||||
it 'sets the correct record type' do
|
||||
expect(EmployeeSerializer.reflected_record_type).to eq :employee
|
||||
expect(EmployeeSerializer.record_type).to eq :employee
|
||||
end
|
||||
|
||||
context 'when testing inheritance of attributes' do
|
||||
|
||||
it 'includes parent attributes' do
|
||||
subclass_attributes = EmployeeSerializer.attributes_to_serialize
|
||||
superclass_attributes = UserSerializer.attributes_to_serialize
|
||||
expect(subclass_attributes).to include(superclass_attributes)
|
||||
end
|
||||
|
||||
it 'returns inherited attribute with a block correctly' do
|
||||
e = Employee.new
|
||||
e.id = 1
|
||||
e.first_name = 'S'
|
||||
e.last_name = 'K'
|
||||
attributes_hash = EmployeeSerializer.new(e).serializable_hash[:data][:attributes]
|
||||
expect(attributes_hash).to include(full_name: 'S K')
|
||||
end
|
||||
|
||||
it 'includes child attributes' do
|
||||
expect(EmployeeSerializer.attributes_to_serialize[:location].method).to eq(:location)
|
||||
end
|
||||
|
||||
it 'doesnt change parent class attributes' do
|
||||
EmployeeSerializer
|
||||
expect(UserSerializer.attributes_to_serialize).not_to have_key(:location)
|
||||
end
|
||||
|
||||
it 'inherits the id source' do
|
||||
e = Employee.new
|
||||
e.id = 2
|
||||
e.uuid = SecureRandom.uuid
|
||||
id = EmployeeSerializer.new(e).serializable_hash[:data][:id]
|
||||
expect(id).to eq(e.uuid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when testing inheritance of relationship' do
|
||||
it 'includes parent relationships' do
|
||||
subclass_relationships = EmployeeSerializer.relationships_to_serialize
|
||||
superclass_relationships = UserSerializer.relationships_to_serialize
|
||||
expect(subclass_relationships).to include(superclass_relationships)
|
||||
end
|
||||
|
||||
it 'returns inherited relationship correctly' do
|
||||
e = Employee.new
|
||||
e.country_id = 1
|
||||
relationships_hash = EmployeeSerializer.new(e).serializable_hash[:data][:relationships][:country]
|
||||
expect(relationships_hash).to include(data: { id: "1", type: :country })
|
||||
end
|
||||
|
||||
it 'includes child relationships' do
|
||||
expect(EmployeeSerializer.relationships_to_serialize.keys).to include(:account)
|
||||
end
|
||||
|
||||
it 'doesnt change parent class attributes' do
|
||||
EmployeeSerializer
|
||||
expect(UserSerializer.relationships_to_serialize.keys).not_to include(:account)
|
||||
end
|
||||
|
||||
it 'includes parent cached relationships' do
|
||||
subclass_relationships = EmployeeSerializer.cachable_relationships_to_serialize
|
||||
superclass_relationships = UserSerializer.cachable_relationships_to_serialize
|
||||
expect(subclass_relationships).to include(superclass_relationships)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when test inheritence of other attributes' do
|
||||
it 'inherits the tranform method' do
|
||||
EmployeeSerializer
|
||||
expect(UserSerializer.transform_method).to eq EmployeeSerializer.transform_method
|
||||
end
|
||||
end
|
||||
end
|
217
spec/lib/object_serializer_performance_spec.rb
Normal file
217
spec/lib/object_serializer_performance_spec.rb
Normal file
@ -0,0 +1,217 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer, performance: true do
|
||||
include_context 'movie class'
|
||||
include_context 'ams movie class'
|
||||
include_context 'jsonapi movie class'
|
||||
include_context 'jsonapi-serializers movie class'
|
||||
|
||||
include_context 'group class'
|
||||
include_context 'ams group class'
|
||||
include_context 'jsonapi group class'
|
||||
include_context 'jsonapi-serializers group class'
|
||||
|
||||
before(:all) { GC.disable }
|
||||
after(:all) { GC.enable }
|
||||
|
||||
SERIALIZERS = {
|
||||
fast_jsonapi: {
|
||||
name: 'Fast Serializer',
|
||||
hash_method: :serializable_hash,
|
||||
json_method: :serialized_json
|
||||
},
|
||||
ams: {
|
||||
name: 'AMS serializer',
|
||||
speed_factor: 25,
|
||||
hash_method: :as_json
|
||||
},
|
||||
jsonapi: {
|
||||
name: 'jsonapi-rb serializer'
|
||||
},
|
||||
jsonapis: {
|
||||
name: 'jsonapi-serializers'
|
||||
}
|
||||
}
|
||||
|
||||
context 'when testing performance of serialization' do
|
||||
it 'should create a hash of 1000 records in less than 50 ms' do
|
||||
movies = 1000.times.map { |_i| movie }
|
||||
expect { MovieSerializer.new(movies).serializable_hash }.to perform_under(50).ms
|
||||
end
|
||||
|
||||
it 'should serialize 1000 records to jsonapi in less than 60 ms' do
|
||||
movies = 1000.times.map { |_i| movie }
|
||||
expect { MovieSerializer.new(movies).serialized_json }.to perform_under(60).ms
|
||||
end
|
||||
|
||||
it 'should create a hash of 1000 records with includes and meta in less than 75 ms' do
|
||||
count = 1000
|
||||
movies = count.times.map { |_i| movie }
|
||||
options = {}
|
||||
options[:meta] = { total: count }
|
||||
options[:include] = [:actors]
|
||||
expect { MovieSerializer.new(movies, options).serializable_hash }.to perform_under(75).ms
|
||||
end
|
||||
|
||||
it 'should serialize 1000 records to jsonapi with includes and meta in less than 75 ms' do
|
||||
count = 1000
|
||||
movies = count.times.map { |_i| movie }
|
||||
options = {}
|
||||
options[:meta] = { total: count }
|
||||
options[:include] = [:actors]
|
||||
expect { MovieSerializer.new(movies, options).serialized_json }.to perform_under(75).ms
|
||||
end
|
||||
end
|
||||
|
||||
def print_stats(message, count, data)
|
||||
puts
|
||||
puts message
|
||||
|
||||
name_length = SERIALIZERS.collect { |s| s[1].fetch(:name, s[0]).length }.max
|
||||
|
||||
puts format("%-#{name_length+1}s %-10s %-10s %s", 'Serializer', 'Records', 'Time', 'Speed Up')
|
||||
|
||||
report_format = "%-#{name_length+1}s %-10s %-10s"
|
||||
fast_jsonapi_time = data[:fast_jsonapi][:time]
|
||||
puts format(report_format, 'Fast serializer', count, fast_jsonapi_time.round(2).to_s + ' ms')
|
||||
|
||||
data.reject { |k,v| k == :fast_jsonapi }.each_pair do |k,v|
|
||||
t = v[:time]
|
||||
factor = t / fast_jsonapi_time
|
||||
|
||||
speed_factor = SERIALIZERS[k].fetch(:speed_factor, 1)
|
||||
result = factor >= speed_factor ? '✔' : '✘'
|
||||
|
||||
puts format("%-#{name_length+1}s %-10s %-10s %sx %s", SERIALIZERS[k][:name], count, t.round(2).to_s + ' ms', factor.round(2), result)
|
||||
end
|
||||
end
|
||||
|
||||
def run_hash_benchmark(message, movie_count, serializers)
|
||||
data = Hash[serializers.keys.collect { |k| [ k, { hash: nil, time: nil, speed_factor: nil }] }]
|
||||
|
||||
serializers.each_pair do |k,v|
|
||||
hash_method = SERIALIZERS[k].key?(:hash_method) ? SERIALIZERS[k][:hash_method] : :to_hash
|
||||
data[k][:time] = Benchmark.measure { data[k][:hash] = v.send(hash_method) }.real * 1000
|
||||
end
|
||||
|
||||
print_stats(message, movie_count, data)
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def run_json_benchmark(message, movie_count, serializers)
|
||||
data = Hash[serializers.keys.collect { |k| [ k, { json: nil, time: nil, speed_factor: nil }] }]
|
||||
|
||||
serializers.each_pair do |k,v|
|
||||
ams_json = nil
|
||||
json_method = SERIALIZERS[k].key?(:json_method) ? SERIALIZERS[k][:json_method] : :to_json
|
||||
data[k][:time] = Benchmark.measure { data[k][:json] = v.send(json_method) }.real * 1000
|
||||
end
|
||||
|
||||
print_stats(message, movie_count, data)
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
context 'when comparing with AMS 0.10.x' do
|
||||
[1, 25, 250, 1000].each do |movie_count|
|
||||
it "should serialize #{movie_count} records atleast #{SERIALIZERS[:ams][:speed_factor]} times faster than AMS" do
|
||||
ams_movies = build_ams_movies(movie_count)
|
||||
movies = build_movies(movie_count)
|
||||
jsonapi_movies = build_jsonapi_movies(movie_count)
|
||||
jsonapis_movies = build_js_movies(movie_count)
|
||||
|
||||
serializers = {
|
||||
fast_jsonapi: MovieSerializer.new(movies),
|
||||
ams: ActiveModelSerializers::SerializableResource.new(ams_movies),
|
||||
jsonapi: JSONAPISerializer.new(jsonapi_movies),
|
||||
jsonapis: JSONAPISSerializer.new(jsonapis_movies)
|
||||
}
|
||||
|
||||
message = "Serialize to JSON string #{movie_count} records"
|
||||
json_benchmarks = run_json_benchmark(message, movie_count, serializers)
|
||||
|
||||
message = "Serialize to Ruby Hash #{movie_count} records"
|
||||
hash_benchmarks = run_hash_benchmark(message, movie_count, serializers)
|
||||
|
||||
# json
|
||||
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
|
||||
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
|
||||
|
||||
# hash
|
||||
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]
|
||||
expect(hash_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when comparing with AMS 0.10.x and with includes and meta' do
|
||||
[1, 25, 250, 1000].each do |movie_count|
|
||||
it "should serialize #{movie_count} records atleast #{SERIALIZERS[:ams][:speed_factor]} times faster than AMS" do
|
||||
ams_movies = build_ams_movies(movie_count)
|
||||
movies = build_movies(movie_count)
|
||||
jsonapi_movies = build_jsonapi_movies(movie_count)
|
||||
jsonapis_movies = build_js_movies(movie_count)
|
||||
|
||||
options = {}
|
||||
options[:meta] = { total: movie_count }
|
||||
options[:include] = [:actors, :movie_type]
|
||||
|
||||
serializers = {
|
||||
fast_jsonapi: MovieSerializer.new(movies, options),
|
||||
ams: ActiveModelSerializers::SerializableResource.new(ams_movies, include: options[:include], meta: options[:meta]),
|
||||
jsonapi: JSONAPISerializer.new(jsonapi_movies, include: options[:include], meta: options[:meta]),
|
||||
jsonapis: JSONAPISSerializer.new(jsonapis_movies, include: options[:include].map { |i| i.to_s.dasherize }, meta: options[:meta])
|
||||
}
|
||||
|
||||
message = "Serialize to JSON string #{movie_count} with includes and meta"
|
||||
json_benchmarks = run_json_benchmark(message, movie_count, serializers)
|
||||
|
||||
message = "Serialize to Ruby Hash #{movie_count} with includes and meta"
|
||||
hash_benchmarks = run_hash_benchmark(message, movie_count, serializers)
|
||||
|
||||
# json
|
||||
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
|
||||
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
|
||||
|
||||
# hash
|
||||
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]
|
||||
expect(hash_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when comparing with AMS 0.10.x and with polymorphic has_many' do
|
||||
[1, 25, 250, 1000].each do |group_count|
|
||||
it "should serialize #{group_count} records at least #{SERIALIZERS[:ams][:speed_factor]} times faster than AMS" do
|
||||
ams_groups = build_ams_groups(group_count)
|
||||
groups = build_groups(group_count)
|
||||
jsonapi_groups = build_jsonapi_groups(group_count)
|
||||
jsonapis_groups = build_jsonapis_groups(group_count)
|
||||
|
||||
options = {}
|
||||
|
||||
serializers = {
|
||||
fast_jsonapi: GroupSerializer.new(groups, options),
|
||||
ams: ActiveModelSerializers::SerializableResource.new(ams_groups),
|
||||
jsonapi: JSONAPISerializerB.new(jsonapi_groups),
|
||||
jsonapis: JSONAPISSerializerB.new(jsonapis_groups)
|
||||
}
|
||||
|
||||
message = "Serialize to JSON string #{group_count} with polymorphic has_many"
|
||||
json_benchmarks = run_json_benchmark(message, group_count, serializers)
|
||||
|
||||
message = "Serialize to Ruby Hash #{group_count} with polymorphic has_many"
|
||||
hash_benchmarks = run_hash_benchmark(message, group_count, serializers)
|
||||
|
||||
# json
|
||||
expect(json_benchmarks[:fast_jsonapi][:json].length).to eq json_benchmarks[:ams][:json].length
|
||||
json_speed_up = json_benchmarks[:ams][:time] / json_benchmarks[:fast_jsonapi][:time]
|
||||
|
||||
# hash
|
||||
hash_speed_up = hash_benchmarks[:ams][:time] / hash_benchmarks[:fast_jsonapi][:time]
|
||||
expect(hash_speed_up).to be >= SERIALIZERS[:ams][:speed_factor]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
99
spec/lib/object_serializer_polymorphic_spec.rb
Normal file
99
spec/lib/object_serializer_polymorphic_spec.rb
Normal file
@ -0,0 +1,99 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
class List
|
||||
attr_accessor :id, :name, :items
|
||||
end
|
||||
|
||||
class ChecklistItem
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
class Car
|
||||
attr_accessor :id, :model, :year
|
||||
end
|
||||
|
||||
class Animal
|
||||
attr_accessor :id, :uuid, :species
|
||||
end
|
||||
|
||||
class ChecklistItemSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :checklist_item
|
||||
attributes :name
|
||||
set_key_transform :dash
|
||||
end
|
||||
|
||||
class CarSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :car
|
||||
attributes :model, :year
|
||||
set_key_transform :dash
|
||||
end
|
||||
|
||||
class AnimalSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :checklist_item
|
||||
attributes :uuid, :species
|
||||
end
|
||||
|
||||
class ListSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :list
|
||||
attributes :name
|
||||
set_key_transform :dash
|
||||
has_many :items, polymorphic: true
|
||||
end
|
||||
|
||||
class ZooSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :list
|
||||
attributes :name
|
||||
has_many :items, polymorphic: true, id_method_name: :uuid
|
||||
end
|
||||
|
||||
let(:car) do
|
||||
car = Car.new
|
||||
car.id = 1
|
||||
car.model = 'Toyota Corolla'
|
||||
car.year = 1987
|
||||
car
|
||||
end
|
||||
|
||||
let(:checklist_item) do
|
||||
checklist_item = ChecklistItem.new
|
||||
checklist_item.id = 2
|
||||
checklist_item.name = 'Do this action!'
|
||||
checklist_item
|
||||
end
|
||||
|
||||
let(:animal) do
|
||||
animal = Animal.new
|
||||
animal.id = 1
|
||||
animal.species = 'Mellivora capensis'
|
||||
animal.uuid = SecureRandom.uuid
|
||||
animal
|
||||
end
|
||||
|
||||
context 'when serializing id and type of polymorphic relationships' do
|
||||
it 'should return correct type when transform_method is specified' do
|
||||
list = List.new
|
||||
list.id = 1
|
||||
list.items = [checklist_item, car]
|
||||
list_hash = ListSerializer.new(list).to_hash
|
||||
record_type = list_hash[:data][:relationships][:items][:data][0][:type]
|
||||
expect(record_type).to eq 'checklist-item'.to_sym
|
||||
record_type = list_hash[:data][:relationships][:items][:data][1][:type]
|
||||
expect(record_type).to eq 'car'.to_sym
|
||||
end
|
||||
|
||||
it 'should use the correct id method on associated objects' do
|
||||
list = List.new
|
||||
list.id = 1
|
||||
list.items = [animal]
|
||||
list_hash = ZooSerializer.new(list).to_hash
|
||||
id = list_hash[:data][:relationships][:items][:data][0][:id]
|
||||
expect(id).to eq animal.uuid
|
||||
end
|
||||
end
|
||||
end
|
112
spec/lib/object_serializer_relationship_links_spec.rb
Normal file
112
spec/lib/object_serializer_relationship_links_spec.rb
Normal file
@ -0,0 +1,112 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context "params option" do
|
||||
let(:hash) { serializer.serializable_hash }
|
||||
|
||||
context "generating links for a serializer relationship" do
|
||||
let(:params) { { } }
|
||||
let(:options_with_params) { { params: params } }
|
||||
let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" }
|
||||
let(:related_url) { "http://movies.com/movies/#{movie.name.parameterize}/actors/" }
|
||||
|
||||
before(:context) do
|
||||
class MovieSerializer
|
||||
has_many :actors, lazy_load_data: false, links: {
|
||||
self: :actors_relationship_url,
|
||||
related: -> (object, params = {}) {
|
||||
"#{params.has_key?(:secure) ? "https" : "http"}://movies.com/movies/#{object.name.parameterize}/actors/"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context "with a single record" do
|
||||
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
|
||||
let(:links) { hash[:data][:relationships][:actors][:links] }
|
||||
|
||||
it "handles relationship links that call a method" do
|
||||
expect(links).to be_present
|
||||
expect(links[:self]).to eq(relationship_url)
|
||||
end
|
||||
|
||||
it "handles relationship links that call a proc" do
|
||||
expect(links).to be_present
|
||||
expect(links[:related]).to eq(related_url)
|
||||
end
|
||||
|
||||
context "with serializer params" do
|
||||
let(:params) { { secure: true } }
|
||||
let(:secure_related_url) { related_url.gsub("http", "https") }
|
||||
|
||||
it "passes the params to the link serializer correctly" do
|
||||
expect(links).to be_present
|
||||
expect(links[:related]).to eq(secure_related_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "lazy loading relationship data" 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) }
|
||||
let(:actor_hash) { hash[:data][:relationships][:actors] }
|
||||
|
||||
it "does not include the :data key" do
|
||||
expect(actor_hash).to be_present
|
||||
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
|
63
spec/lib/object_serializer_relationship_param_spec.rb
Normal file
63
spec/lib/object_serializer_relationship_param_spec.rb
Normal file
@ -0,0 +1,63 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context "params option" do
|
||||
let(:hash) { serializer.serializable_hash }
|
||||
|
||||
before(:context) do
|
||||
class MovieSerializer
|
||||
has_many :agencies do |movie, params|
|
||||
movie.actors.map(&:agency) if params[:authorized]
|
||||
end
|
||||
|
||||
belongs_to :primary_agency do |movie, params|
|
||||
movie.actors.map(&:agency)[0] if params[:authorized]
|
||||
end
|
||||
|
||||
belongs_to :secondary_agency do |movie|
|
||||
movie.actors.map(&:agency)[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "passing params to the serializer" do
|
||||
let(:params) { {authorized: true} }
|
||||
let(:options_with_params) { {params: params} }
|
||||
|
||||
context "with a single record" do
|
||||
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
|
||||
|
||||
it "handles relationships that use params" do
|
||||
ids = hash[:data][:relationships][:agencies][:data].map{|a| a[:id]}
|
||||
ids.map!(&:to_i)
|
||||
expect(ids).to eq [0,1,2]
|
||||
end
|
||||
|
||||
it "handles relationships that don't use params" do
|
||||
expect(hash[:data][:relationships][:secondary_agency][:data]).to include({id: 1.to_s})
|
||||
end
|
||||
end
|
||||
|
||||
context "with a list of records" do
|
||||
let(:movies) { build_movies(3) }
|
||||
let(:params) { {authorized: true} }
|
||||
let(:serializer) { MovieSerializer.new(movies, options_with_params) }
|
||||
|
||||
it "handles relationship params when passing params to a list of resources" do
|
||||
relationships_hashes = hash[:data].map{|a| a[:relationships][:agencies][:data]}.uniq.flatten
|
||||
expect(relationships_hashes.map{|a| a[:id].to_i}).to contain_exactly 0,1,2
|
||||
|
||||
uniq_count = hash[:data].map{|a| a[:relationships][:primary_agency] }.uniq.count
|
||||
expect(uniq_count).to eq 1
|
||||
end
|
||||
|
||||
it "handles relationships without params" do
|
||||
uniq_count = hash[:data].map{|a| a[:relationships][:secondary_agency] }.uniq.count
|
||||
expect(uniq_count).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
107
spec/lib/object_serializer_relationship_serializer_proc_spec.rb
Normal file
107
spec/lib/object_serializer_relationship_serializer_proc_spec.rb
Normal file
@ -0,0 +1,107 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
|
||||
class Person
|
||||
attr_accessor :id, :name, :assets
|
||||
end
|
||||
|
||||
class House
|
||||
attr_accessor :id, :address
|
||||
end
|
||||
|
||||
class Car
|
||||
attr_accessor :id, :model, :year
|
||||
end
|
||||
|
||||
class PersonSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :person
|
||||
attributes :name
|
||||
set_key_transform :dash
|
||||
|
||||
has_many :assets, serializer: -> (object) do
|
||||
if object.is_a?(House)
|
||||
HouseSerializer
|
||||
elsif object.is_a?(Car)
|
||||
CarSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HouseSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :house
|
||||
attributes :address
|
||||
set_key_transform :dash
|
||||
end
|
||||
|
||||
class CarSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :car
|
||||
attributes :model, :year
|
||||
set_key_transform :dash
|
||||
end
|
||||
|
||||
|
||||
let(:house) do
|
||||
house = House.new
|
||||
house.id = 123
|
||||
house.address = '1600 Pennsylvania Avenue'
|
||||
house
|
||||
end
|
||||
|
||||
let(:car) do
|
||||
car = Car.new
|
||||
car.id = 456
|
||||
car.model = 'Toyota Corolla'
|
||||
car.year = 1987
|
||||
car
|
||||
end
|
||||
|
||||
context 'when serializing a relationship with a serializer block' do
|
||||
it 'should output the correct JSON based on the proper serializer' do
|
||||
person = Person.new
|
||||
person.id = 1
|
||||
person.name = 'Bob'
|
||||
person.assets = [house, car]
|
||||
person_hash = PersonSerializer.new(person).to_hash
|
||||
|
||||
relationships = person_hash[:data][:relationships]
|
||||
house_relationship = relationships[:assets][:data][0]
|
||||
expect(house_relationship[:type].to_s).to eq 'house'
|
||||
expect(house_relationship[:id].to_s).to eq house.id.to_s
|
||||
car_relationship = relationships[:assets][:data][1]
|
||||
expect(car_relationship[:type].to_s).to eq 'car'
|
||||
expect(car_relationship[:id].to_s).to eq car.id.to_s
|
||||
|
||||
expect(person_hash[:data]).to_not have_key :included
|
||||
end
|
||||
|
||||
it 'should output the correct included records' do
|
||||
person = Person.new
|
||||
person.id = 1
|
||||
person.name = 'Bob'
|
||||
person.assets = [house, car]
|
||||
person_hash = PersonSerializer.new(person, { include: [ :assets ] }).to_hash
|
||||
|
||||
relationships = person_hash[:data][:relationships]
|
||||
house_relationship = relationships[:assets][:data][0]
|
||||
expect(house_relationship[:type].to_s).to eq 'house'
|
||||
expect(house_relationship[:id].to_s).to eq house.id.to_s
|
||||
car_relationship = relationships[:assets][:data][1]
|
||||
expect(car_relationship[:type].to_s).to eq 'car'
|
||||
expect(car_relationship[:id].to_s).to eq car.id.to_s
|
||||
|
||||
included = person_hash[:included]
|
||||
house_included = included[0]
|
||||
expect(house_included[:type].to_s).to eq 'house'
|
||||
expect(house_included[:id].to_s).to eq house.id.to_s
|
||||
expect(house_included[:attributes][:address]).to eq house.address
|
||||
car_included = included[1]
|
||||
expect(car_included[:type].to_s).to eq 'car'
|
||||
expect(car_included[:id].to_s).to eq car.id.to_s
|
||||
expect(car_included[:attributes][:model]).to eq car.model
|
||||
end
|
||||
end
|
||||
end
|
593
spec/lib/object_serializer_spec.rb
Normal file
593
spec/lib/object_serializer_spec.rb
Normal file
@ -0,0 +1,593 @@
|
||||
require 'spec_helper'
|
||||
|
||||
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(movies, options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data].length).to eq 2
|
||||
expect(serializable_hash[:data][0][:relationships].length).to eq 4
|
||||
expect(serializable_hash[:data][0][:attributes].length).to eq 2
|
||||
|
||||
expect(serializable_hash[:meta]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:links]).to be_instance_of(Hash)
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included][0]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:included].length).to eq 3
|
||||
|
||||
serializable_hash = MovieSerializer.new(movie).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:meta]).to be nil
|
||||
expect(serializable_hash[:links]).to be nil
|
||||
expect(serializable_hash[:included]).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct nested includes when serializable_hash is called' do
|
||||
# 3 actors, 3 agencies
|
||||
include_object_total = 6
|
||||
|
||||
options = {}
|
||||
options[:include] = [:actors, :'actors.agency']
|
||||
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included].length).to eq include_object_total
|
||||
(0..include_object_total-1).each do |include|
|
||||
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
|
||||
end
|
||||
|
||||
options[:include] = [:'actors.agency']
|
||||
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included].length).to eq include_object_total
|
||||
(0..include_object_total-1).each do |include|
|
||||
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns correct number of records when serialized_json is called for an array' do
|
||||
options = {}
|
||||
options[:meta] = { total: 2 }
|
||||
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)
|
||||
end
|
||||
|
||||
it 'returns correct id when serialized_json is called for a single object' do
|
||||
json = MovieSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['id']).to eq movie.id.to_s
|
||||
end
|
||||
|
||||
it 'returns correct json when serializing nil' do
|
||||
json = MovieSerializer.new(nil).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']).to eq nil
|
||||
end
|
||||
|
||||
it 'returns correct json when record id is nil' do
|
||||
movie.id = nil
|
||||
json = MovieSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['id']).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct json when has_many returns []' do
|
||||
movie.actor_ids = []
|
||||
json = MovieSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships']['actors']['data'].length).to eq 0
|
||||
end
|
||||
|
||||
it 'returns correct json when belongs_to returns nil' do
|
||||
movie.owner_id = nil
|
||||
json = MovieSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships']['owner']['data']).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct json when belongs_to returns nil and there is a block for the relationship' do
|
||||
movie.owner_id = nil
|
||||
json = MovieSerializer.new(movie, {include: [:owner]}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships']['owner']['data']).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct json when has_one returns nil' do
|
||||
supplier.account_id = nil
|
||||
json = SupplierSerializer.new(supplier).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships']['account']['data']).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct json when serializing []' do
|
||||
json = MovieSerializer.new([]).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']).to eq []
|
||||
end
|
||||
|
||||
describe '#as_json' do
|
||||
it 'returns a json hash' do
|
||||
json_hash = MovieSerializer.new(movie).as_json
|
||||
expect(json_hash['data']['id']).to eq movie.id.to_s
|
||||
end
|
||||
|
||||
it 'returns multiple records' do
|
||||
json_hash = MovieSerializer.new(movies).as_json
|
||||
expect(json_hash['data'].length).to eq 2
|
||||
end
|
||||
|
||||
it 'removes non-relevant attributes' do
|
||||
movie.director = 'steven spielberg'
|
||||
json_hash = MovieSerializer.new(movie).as_json
|
||||
expect(json_hash['data']['director']).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns errors when serializing with non-existent includes key' 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
|
||||
|
||||
it 'does not throw an error with non-empty string array includes key' do
|
||||
options = {}
|
||||
options[:include] = ['actors']
|
||||
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(movies, options).serializable_hash.keys).to eq [:data, :meta]
|
||||
options[:include] = [nil]
|
||||
expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta]
|
||||
end
|
||||
end
|
||||
|
||||
context 'id attribute is the same for actors and not a primary key' do
|
||||
before do
|
||||
ActorSerializer.set_id :email
|
||||
movie.actor_ids = [0, 0, 0]
|
||||
class << movie
|
||||
def actors
|
||||
super.each_with_index { |actor, i| actor.email = "actor#{i}@email.com" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after { ActorSerializer.set_id nil }
|
||||
|
||||
let(:options) { { include: ['actors'] } }
|
||||
subject { MovieSerializer.new(movie, options).serializable_hash }
|
||||
|
||||
it 'returns all actors in includes' do
|
||||
|
||||
expect(
|
||||
subject[:included].select { |i| i[:type] == :actor }.map { |i| i[:id] }
|
||||
).to eq(
|
||||
movie.actors.map(&:email)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'nested includes' do
|
||||
it 'has_many to belongs_to: returns correct nested includes when serializable_hash is called' do
|
||||
# 3 actors, 3 agencies
|
||||
include_object_total = 6
|
||||
|
||||
options = {}
|
||||
options[:include] = [:actors, :'actors.agency']
|
||||
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included].length).to eq include_object_total
|
||||
(0..include_object_total-1).each do |include|
|
||||
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
|
||||
end
|
||||
|
||||
options[:include] = [:'actors.agency']
|
||||
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included].length).to eq include_object_total
|
||||
(0..include_object_total-1).each do |include|
|
||||
expect(serializable_hash[:included][include]).to be_instance_of(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
it '`has_many` to `belongs_to` to `belongs_to` - returns correct nested includes when serializable_hash is called' do
|
||||
# 3 actors, 3 agencies, 1 state
|
||||
include_object_total = 7
|
||||
|
||||
options = {}
|
||||
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
|
||||
serializable_hash = MovieSerializer.new([movie], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included].length).to eq include_object_total
|
||||
|
||||
actors_serialized = serializable_hash[:included].find_all { |included| included[:type] == :actor }.map { |included| included[:id].to_i }
|
||||
agencies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :agency }.map { |included| included[:id].to_i }
|
||||
states_serialized = serializable_hash[:included].find_all { |included| included[:type] == :state }.map { |included| included[:id].to_i }
|
||||
|
||||
movie.actors.each do |actor|
|
||||
expect(actors_serialized).to include(actor.id)
|
||||
end
|
||||
|
||||
agencies = movie.actors.map(&:agency).uniq
|
||||
agencies.each do |agency|
|
||||
expect(agencies_serialized).to include(agency.id)
|
||||
end
|
||||
|
||||
states = agencies.map(&:state).uniq
|
||||
states.each do |state|
|
||||
expect(states_serialized).to include(state.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'has_many => has_one returns correct nested includes when serializable_hash is called' do
|
||||
options = {}
|
||||
options[:include] = [:movies, :'movies.advertising_campaign']
|
||||
serializable_hash = MovieTypeSerializer.new([movie_type], options).serializable_hash
|
||||
|
||||
movies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :movie }.map { |included| included[:id].to_i }
|
||||
advertising_campaigns_serialized = serializable_hash[:included].find_all { |included| included[:type] == :advertising_campaign }.map { |included| included[:id].to_i }
|
||||
|
||||
movies = movie_type.movies
|
||||
movies.each do |movie|
|
||||
expect(movies_serialized).to include(movie.id)
|
||||
end
|
||||
|
||||
advertising_campaigns = movies.map(&:advertising_campaign)
|
||||
advertising_campaigns.each do |advertising_campaign|
|
||||
expect(advertising_campaigns_serialized).to include(advertising_campaign.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'belongs_to: returns correct nested includes when nested attributes are nil when serializable_hash is called' do
|
||||
class Movie
|
||||
def advertising_campaign
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
options = {}
|
||||
options[:include] = [:movies, :'movies.advertising_campaign']
|
||||
|
||||
serializable_hash = MovieTypeSerializer.new([movie_type], options).serializable_hash
|
||||
|
||||
movies_serialized = serializable_hash[:included].find_all { |included| included[:type] == :movie }.map { |included| included[:id].to_i }
|
||||
|
||||
movies = movie_type.movies
|
||||
movies.each do |movie|
|
||||
expect(movies_serialized).to include(movie.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'polymorphic has_many: returns correct nested includes when serializable_hash is called' do
|
||||
options = {}
|
||||
options[:include] = [:groupees]
|
||||
|
||||
serializable_hash = GroupSerializer.new([group], options).serializable_hash
|
||||
|
||||
persons_serialized = serializable_hash[:included].find_all { |included| included[:type] == :person }.map { |included| included[:id].to_i }
|
||||
groups_serialized = serializable_hash[:included].find_all { |included| included[:type] == :group }.map { |included| included[:id].to_i }
|
||||
|
||||
persons = group.groupees.find_all { |groupee| groupee.is_a?(Person) }
|
||||
persons.each do |person|
|
||||
expect(persons_serialized).to include(person.id)
|
||||
end
|
||||
|
||||
groups = group.groupees.find_all { |groupee| groupee.is_a?(Group) }
|
||||
groups.each do |group|
|
||||
expect(groups_serialized).to include(group.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when testing included do block of object serializer' do
|
||||
it 'should set default_type based on serializer class name' do
|
||||
class BlahSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
expect(BlahSerializer.record_type).to be :blah
|
||||
end
|
||||
|
||||
it 'should set default_type for a multi word class name' do
|
||||
class BlahBlahSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
expect(BlahBlahSerializer.record_type).to be :blah_blah
|
||||
end
|
||||
|
||||
it 'should set default_type for a namespaced serializer' do
|
||||
module V1
|
||||
class BlahSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
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
|
||||
before do
|
||||
ActorSerializer.link(:self) do |actor_object|
|
||||
actor_object.url
|
||||
end
|
||||
end
|
||||
subject(:serializable_hash) do
|
||||
options = {}
|
||||
options[:include] = [:actors]
|
||||
MovieSerializer.new(movie, options).serializable_hash
|
||||
end
|
||||
let(:actor) { movie.actors.first }
|
||||
let(:url) { "http://movies.com/actors/#{actor.id}" }
|
||||
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
expect(serializable_hash[:included][0][:links][:self]).to eq url
|
||||
end
|
||||
end
|
||||
|
||||
context 'when serializing included, params should be available in any serializer' do
|
||||
subject(:serializable_hash) do
|
||||
options = {}
|
||||
options[:include] = [:"actors.awards"]
|
||||
options[:params] = { include_award_year: true }
|
||||
MovieSerializer.new(movie, options).serializable_hash
|
||||
end
|
||||
let(:actor) { movie.actors.first }
|
||||
let(:award) { actor.awards.first }
|
||||
let(:year) { award.year }
|
||||
|
||||
it 'passes params to deeply nested includes' do
|
||||
expect(year).to_not be_blank
|
||||
expect(serializable_hash[:included][0][:attributes][:year]).to eq year
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is_collection option present' do
|
||||
subject { MovieSerializer.new(resource, is_collection_options).serializable_hash }
|
||||
|
||||
context 'autodetect' do
|
||||
let(:is_collection_options) { {} }
|
||||
|
||||
context 'collection if no option present' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect(subject[:data]).to be_a(Array) }
|
||||
end
|
||||
|
||||
context 'single if no option present' do
|
||||
let(:resource) { movie }
|
||||
it { expect(subject[:data]).to be_a(Hash) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'force is_collection to true' do
|
||||
let(:is_collection_options) { { is_collection: true } }
|
||||
|
||||
context 'collection will pass' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect(subject[:data]).to be_a(Array) }
|
||||
end
|
||||
|
||||
context 'single will raise error' do
|
||||
let(:resource) { movie }
|
||||
it { expect { subject }.to raise_error(NoMethodError, /method(.*)each/) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'force is_collection to false' do
|
||||
let(:is_collection_options) { { is_collection: false } }
|
||||
|
||||
context 'collection will fail without id' do
|
||||
let(:resource) { [movie] }
|
||||
it { expect { subject }.to raise_error(FastJsonapi::MandatoryField, /id is a mandatory field/) }
|
||||
end
|
||||
|
||||
context 'single will pass' do
|
||||
let(:resource) { movie }
|
||||
it { expect(subject[:data]).to be_a(Hash) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional attributes are determined by record data' do
|
||||
it 'returns optional attribute when attribute is included' do
|
||||
movie.release_year = 2001
|
||||
json = MovieOptionalRecordDataSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes']['release_year']).to eq movie.release_year
|
||||
end
|
||||
|
||||
it "doesn't return optional attribute when attribute is not included" do
|
||||
movie.release_year = 1970
|
||||
json = MovieOptionalRecordDataSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes'].has_key?('release_year')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional attributes are determined by record data with a lambda' do
|
||||
it 'returns optional attribute when attribute is included' do
|
||||
movie.release_year = 2001
|
||||
json = MovieOptionalRecordDataWithLambdaSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes']['release_year']).to eq movie.release_year
|
||||
end
|
||||
|
||||
it "doesn't return optional attribute when attribute is not included" do
|
||||
movie.release_year = 1970
|
||||
json = MovieOptionalRecordDataWithLambdaSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes'].has_key?('release_year')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional attributes are determined by params data' do
|
||||
it 'returns optional attribute when attribute is included' do
|
||||
movie.director = 'steven spielberg'
|
||||
json = MovieOptionalParamsDataSerializer.new(movie, { params: { admin: true }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes']['director']).to eq 'steven spielberg'
|
||||
end
|
||||
|
||||
it "doesn't return optional attribute when attribute is not included" do
|
||||
movie.director = 'steven spielberg'
|
||||
json = MovieOptionalParamsDataSerializer.new(movie, { params: { admin: false }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['attributes'].has_key?('director')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional relationships are determined by record data' do
|
||||
it 'returns optional relationship when relationship is included' do
|
||||
json = MovieOptionalRelationshipSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_truthy
|
||||
end
|
||||
|
||||
context "when relationship is not included" do
|
||||
let(:json) {
|
||||
MovieOptionalRelationshipSerializer.new(movie, options).serialized_json
|
||||
}
|
||||
let(:options) {
|
||||
{}
|
||||
}
|
||||
let(:serializable_hash) {
|
||||
JSON.parse(json)
|
||||
}
|
||||
|
||||
it "doesn't return optional relationship" do
|
||||
movie.actor_ids = []
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_falsey
|
||||
end
|
||||
|
||||
it "doesn't include optional relationship" do
|
||||
movie.actor_ids = []
|
||||
options[:include] = [:actors]
|
||||
expect(serializable_hash['included']).to be_blank
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional relationships are determined by record data with a lambda' do
|
||||
it 'returns optional relationship when relationship is included' do
|
||||
json = MovieOptionalRelationshipWithLambdaSerializer.new(movie).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_truthy
|
||||
end
|
||||
|
||||
context "when relationship is not included" do
|
||||
let(:json) {
|
||||
MovieOptionalRelationshipWithLambdaSerializer.new(movie, options).serialized_json
|
||||
}
|
||||
let(:options) {
|
||||
{}
|
||||
}
|
||||
let(:serializable_hash) {
|
||||
JSON.parse(json)
|
||||
}
|
||||
|
||||
it "doesn't return optional relationship" do
|
||||
movie.actor_ids = []
|
||||
expect(serializable_hash['data']['relationships'].has_key?('actors')).to be_falsey
|
||||
end
|
||||
|
||||
it "doesn't include optional relationship" do
|
||||
movie.actor_ids = []
|
||||
options[:include] = [:actors]
|
||||
expect(serializable_hash['included']).to be_blank
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'when 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
|
||||
|
||||
context 'when optional relationships are determined by params data' do
|
||||
it 'returns optional relationship when relationship is included' do
|
||||
json = MovieOptionalRelationshipWithParamsSerializer.new(movie, { params: { admin: true }}).serialized_json
|
||||
serializable_hash = JSON.parse(json)
|
||||
expect(serializable_hash['data']['relationships'].has_key?('owner')).to be_truthy
|
||||
end
|
||||
|
||||
context "when relationship is not included" do
|
||||
let(:json) {
|
||||
MovieOptionalRelationshipWithParamsSerializer.new(movie, options).serialized_json
|
||||
}
|
||||
let(:options) {
|
||||
{ params: { admin: false }}
|
||||
}
|
||||
let(:serializable_hash) {
|
||||
JSON.parse(json)
|
||||
}
|
||||
|
||||
it "doesn't return optional relationship" do
|
||||
expect(serializable_hash['data']['relationships'].has_key?('owner')).to be_falsey
|
||||
end
|
||||
|
||||
it "doesn't include optional relationship" do
|
||||
options[:include] = [:owner]
|
||||
expect(serializable_hash['included']).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when attribute contents are determined by params data' do
|
||||
it 'does not throw an error with no params are passed' do
|
||||
expect { MovieOptionalAttributeContentsWithParamsSerializer.new(movie).serialized_json }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
41
spec/lib/object_serializer_struct_spec.rb
Normal file
41
spec/lib/object_serializer_struct_spec.rb
Normal file
@ -0,0 +1,41 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context 'movie class'
|
||||
|
||||
context 'when testing object serializer with ruby struct' 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_struct, movie_struct], options).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data].length).to eq 2
|
||||
expect(serializable_hash[:data][0][:relationships].length).to eq 4
|
||||
expect(serializable_hash[:data][0][:attributes].length).to eq 2
|
||||
|
||||
expect(serializable_hash[:meta]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:links]).to be_instance_of(Hash)
|
||||
|
||||
expect(serializable_hash[:included]).to be_instance_of(Array)
|
||||
expect(serializable_hash[:included][0]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:included].length).to eq 3
|
||||
|
||||
serializable_hash = MovieSerializer.new(movie_struct).serializable_hash
|
||||
|
||||
expect(serializable_hash[:data]).to be_instance_of(Hash)
|
||||
expect(serializable_hash[:meta]).to be nil
|
||||
expect(serializable_hash[:links]).to be nil
|
||||
expect(serializable_hash[:included]).to be nil
|
||||
expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s
|
||||
end
|
||||
|
||||
context 'struct without id' do
|
||||
it 'returns correct hash when serializable_hash is called' do
|
||||
serializer = MovieWithoutIdStructSerializer.new(movie_struct_without_id)
|
||||
expect { serializer.serializable_hash }.to raise_error(FastJsonapi::MandatoryField)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
72
spec/lib/serialization_core_spec.rb
Normal file
72
spec/lib/serialization_core_spec.rb
Normal file
@ -0,0 +1,72 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe FastJsonapi::ObjectSerializer do
|
||||
include_context "movie class"
|
||||
include_context 'group class'
|
||||
|
||||
context 'when testing class methods of serialization core' do
|
||||
it 'returns correct hash when id_hash is called' do
|
||||
inputs = [{id: 23, record_type: :movie}, {id: 'x', record_type: 'person'}]
|
||||
inputs.each do |hash|
|
||||
result_hash = MovieSerializer.send(:id_hash, hash[:id], hash[:record_type])
|
||||
expect(result_hash[:id]).to eq hash[:id].to_s
|
||||
expect(result_hash[:type]).to eq hash[:record_type]
|
||||
end
|
||||
|
||||
result_hash = MovieSerializer.send(:id_hash, nil, 'movie')
|
||||
expect(result_hash).to be nil
|
||||
end
|
||||
|
||||
it 'returns correct hash when attributes_hash is called' do
|
||||
attributes_hash = MovieSerializer.send(:attributes_hash, movie)
|
||||
attribute_names = attributes_hash.keys.sort
|
||||
expect(attribute_names).to eq MovieSerializer.attributes_to_serialize.keys.sort
|
||||
MovieSerializer.attributes_to_serialize.each do |key, attribute|
|
||||
value = attributes_hash[key]
|
||||
expect(value).to eq movie.send(attribute.method)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the correct empty result when relationships_hash is called' do
|
||||
movie.actor_ids = []
|
||||
movie.owner_id = nil
|
||||
relationships_hash = MovieSerializer.send(:relationships_hash, movie)
|
||||
expect(relationships_hash[:actors][:data]).to eq([])
|
||||
expect(relationships_hash[:owner][:data]).to eq(nil)
|
||||
end
|
||||
|
||||
it 'returns correct keys when relationships_hash is called' do
|
||||
relationships_hash = MovieSerializer.send(:relationships_hash, movie)
|
||||
relationship_names = relationships_hash.keys.sort
|
||||
relationships_hashes = MovieSerializer.relationships_to_serialize.values
|
||||
expected_names = relationships_hashes.map{|relationship| relationship.key}.sort
|
||||
expect(relationship_names).to eq expected_names
|
||||
end
|
||||
|
||||
it 'returns correct values when relationships_hash is called' do
|
||||
relationships_hash = MovieSerializer.relationships_hash(movie)
|
||||
actors_hash = movie.actor_ids.map { |id| {id: id.to_s, type: :actor} }
|
||||
owner_hash = {id: movie.owner_id.to_s, type: :user}
|
||||
expect(relationships_hash[:actors][:data]).to match_array actors_hash
|
||||
expect(relationships_hash[:owner][:data]).to eq owner_hash
|
||||
end
|
||||
|
||||
it 'returns correct hash when record_hash is called' do
|
||||
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?
|
||||
expect(record_hash).to have_key(:relationships) if MovieSerializer.relationships_to_serialize.present?
|
||||
end
|
||||
|
||||
it 'serializes known included records only once' do
|
||||
includes_list = [:actors]
|
||||
known_included_objects = {}
|
||||
included_records = []
|
||||
[movie, movie].each do |record|
|
||||
included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, {}, nil)
|
||||
end
|
||||
expect(included_records.size).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
155
spec/shared/contexts/ams_context.rb
Normal file
155
spec/shared/contexts/ams_context.rb
Normal file
@ -0,0 +1,155 @@
|
||||
RSpec.shared_context 'ams movie class' do
|
||||
before(:context) do
|
||||
# models
|
||||
class AMSModel < ActiveModelSerializers::Model
|
||||
derive_attributes_from_names_and_fix_accessors
|
||||
end
|
||||
|
||||
class AMSMovieType < AMSModel
|
||||
attributes :id, :name, :movies
|
||||
end
|
||||
class AMSMovie < AMSModel
|
||||
attributes :id, :name, :release_year, :actors, :owner, :movie_type, :advertising_campaign
|
||||
|
||||
def movie_type
|
||||
mt = AMSMovieType.new
|
||||
mt.id = 1
|
||||
mt.name = 'Episode'
|
||||
mt.movies = [self]
|
||||
mt
|
||||
end
|
||||
end
|
||||
|
||||
class AMSAdvertisingCampaign < AMSModel
|
||||
attributes :id, :name, :movie
|
||||
end
|
||||
|
||||
class AMSAward < AMSModel
|
||||
attributes :id, :title, :actor
|
||||
end
|
||||
|
||||
class AMSAgency < AMSModel
|
||||
attributes :id, :name, :actors
|
||||
end
|
||||
|
||||
class AMSActor < AMSModel
|
||||
attributes :id, :name, :email, :agency, :awards, :agency_id
|
||||
def agency
|
||||
AMSAgency.new.tap do |a|
|
||||
a.id = agency_id
|
||||
a.name = "Test Agency #{agency_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def award_ids
|
||||
[id * 9, id * 9 + 1]
|
||||
end
|
||||
|
||||
def awards
|
||||
award_ids.map do |i|
|
||||
AMSAward.new.tap do |a|
|
||||
a.id = i
|
||||
a.title = "Test Award #{i}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AMSUser < AMSModel
|
||||
attributes :id, :name
|
||||
end
|
||||
class AMSMovieType < AMSModel
|
||||
attributes :id, :name
|
||||
end
|
||||
# serializers
|
||||
class AMSAwardSerializer < ActiveModel::Serializer
|
||||
type 'award'
|
||||
attributes :id, :title
|
||||
belongs_to :actor
|
||||
end
|
||||
class AMSAgencySerializer < ActiveModel::Serializer
|
||||
type 'agency'
|
||||
attributes :id, :name
|
||||
belongs_to :state
|
||||
has_many :actors
|
||||
end
|
||||
class AMSActorSerializer < ActiveModel::Serializer
|
||||
type 'actor'
|
||||
attributes :name, :email
|
||||
belongs_to :agency, serializer: ::AMSAgencySerializer
|
||||
has_many :awards, serializer: ::AMSAwardSerializer
|
||||
end
|
||||
class AMSUserSerializer < ActiveModel::Serializer
|
||||
type 'user'
|
||||
attributes :name
|
||||
end
|
||||
class AMSMovieTypeSerializer < ActiveModel::Serializer
|
||||
type 'movie_type'
|
||||
attributes :name
|
||||
has_many :movies
|
||||
end
|
||||
class AMSAdvertisingCampaignSerializer < ActiveModel::Serializer
|
||||
type 'advertising_campaign'
|
||||
attributes :name
|
||||
end
|
||||
class AMSMovieSerializer < ActiveModel::Serializer
|
||||
type 'movie'
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
has_one :owner
|
||||
belongs_to :movie_type
|
||||
has_one :advertising_campaign
|
||||
end
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
classes_to_remove = %i[AMSMovie AMSMovieSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:ams_actors) do
|
||||
3.times.map do |i|
|
||||
a = AMSActor.new
|
||||
a.id = i + 1
|
||||
a.name = "Test #{a.id}"
|
||||
a.email = "test#{a.id}@test.com"
|
||||
a.agency_id = i
|
||||
a
|
||||
end
|
||||
end
|
||||
|
||||
let(:ams_user) do
|
||||
ams_user = AMSUser.new
|
||||
ams_user.id = 3
|
||||
ams_user
|
||||
end
|
||||
|
||||
let(:ams_movie_type) do
|
||||
ams_movie_type = AMSMovieType.new
|
||||
ams_movie_type.id = 1
|
||||
ams_movie_type.name = 'episode'
|
||||
ams_movie_type
|
||||
end
|
||||
|
||||
let(:ams_advertising_campaign) do
|
||||
campaign = AMSAdvertisingCampaign.new
|
||||
campaign.id = 1
|
||||
campaign.name = "Movie is incredible!!"
|
||||
campaign
|
||||
end
|
||||
|
||||
def build_ams_movies(count)
|
||||
count.times.map do |i|
|
||||
m = AMSMovie.new
|
||||
m.id = i + 1
|
||||
m.name = 'test movie'
|
||||
m.actors = ams_actors
|
||||
m.owner = ams_user
|
||||
m.movie_type = ams_movie_type
|
||||
m.advertising_campaign = ams_advertising_campaign
|
||||
m
|
||||
end
|
||||
end
|
||||
end
|
87
spec/shared/contexts/ams_group_context.rb
Normal file
87
spec/shared/contexts/ams_group_context.rb
Normal file
@ -0,0 +1,87 @@
|
||||
RSpec.shared_context 'ams group class' do
|
||||
before(:context) do
|
||||
# models
|
||||
class AMSPerson < ActiveModelSerializers::Model
|
||||
attr_accessor :id, :first_name, :last_name
|
||||
end
|
||||
|
||||
class AMSGroup < ActiveModelSerializers::Model
|
||||
attr_accessor :id, :name, :groupees
|
||||
end
|
||||
|
||||
# serializers
|
||||
class AMSPersonSerializer < ActiveModel::Serializer
|
||||
type 'person'
|
||||
attributes :first_name, :last_name
|
||||
end
|
||||
|
||||
class AMSGroupSerializer < ActiveModel::Serializer
|
||||
type 'group'
|
||||
attributes :name
|
||||
has_many :groupees
|
||||
end
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
classes_to_remove = %i[AMSPerson AMSGroup AMSPersonSerializer AMSGroupSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:ams_groups) do
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
3.times.map do |i|
|
||||
group = AMSGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = AMSPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = AMSGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
|
||||
let(:ams_person) do
|
||||
ams_person = AMSPerson.new
|
||||
ams_person.id = 3
|
||||
ams_person
|
||||
end
|
||||
|
||||
def build_ams_groups(count)
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
count.times.map do |i|
|
||||
group = AMSGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = AMSPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = AMSGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
97
spec/shared/contexts/group_context.rb
Normal file
97
spec/shared/contexts/group_context.rb
Normal file
@ -0,0 +1,97 @@
|
||||
RSpec.shared_context 'group class' do
|
||||
|
||||
# Person, Group Classes and serializers
|
||||
before(:context) do
|
||||
# models
|
||||
class Person
|
||||
attr_accessor :id, :first_name, :last_name
|
||||
end
|
||||
|
||||
class Group
|
||||
attr_accessor :id, :name, :groupees # Let's assume groupees can be Person or Group objects
|
||||
end
|
||||
|
||||
# serializers
|
||||
class PersonSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :person
|
||||
attributes :first_name, :last_name
|
||||
end
|
||||
|
||||
class GroupSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :group
|
||||
attributes :name
|
||||
has_many :groupees, polymorphic: true
|
||||
end
|
||||
end
|
||||
|
||||
# Person and Group struct
|
||||
before(:context) do
|
||||
PersonStruct = Struct.new(
|
||||
:id, :first_name, :last_name
|
||||
)
|
||||
|
||||
GroupStruct = Struct.new(
|
||||
:id, :name, :groupees, :groupee_ids
|
||||
)
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
classes_to_remove = %i[
|
||||
Person
|
||||
PersonSerializer
|
||||
Group
|
||||
GroupSerializer
|
||||
PersonStruct
|
||||
GroupStruct
|
||||
]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:group) do
|
||||
group = Group.new
|
||||
group.id = 1
|
||||
group.name = 'Group 1'
|
||||
|
||||
person = Person.new
|
||||
person.id = 1
|
||||
person.last_name = "Last Name 1"
|
||||
person.first_name = "First Name 1"
|
||||
|
||||
child_group = Group.new
|
||||
child_group.id = 2
|
||||
child_group.name = 'Group 2'
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
|
||||
def build_groups(count)
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
|
||||
count.times.map do |i|
|
||||
group = Group.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = Person.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = Group.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
123
spec/shared/contexts/js_context.rb
Normal file
123
spec/shared/contexts/js_context.rb
Normal file
@ -0,0 +1,123 @@
|
||||
RSpec.shared_context 'jsonapi-serializers movie class' do
|
||||
before(:context) do
|
||||
# models
|
||||
class JSMovie
|
||||
attr_accessor :id, :name, :release_year, :actors, :owner, :movie_type
|
||||
end
|
||||
|
||||
class JSActor
|
||||
attr_accessor :id, :name, :email
|
||||
end
|
||||
|
||||
class JSUser
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
class JSMovieType
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
# serializers
|
||||
class JSActorSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :name, :email
|
||||
|
||||
def type
|
||||
'actor'
|
||||
end
|
||||
end
|
||||
class JSUserSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :name
|
||||
|
||||
def type
|
||||
'user'
|
||||
end
|
||||
end
|
||||
class JSMovieTypeSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :name
|
||||
|
||||
def type
|
||||
'movie_type'
|
||||
end
|
||||
end
|
||||
class JSMovieSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
has_one :owner
|
||||
has_one :movie_type
|
||||
|
||||
def type
|
||||
'movie'
|
||||
end
|
||||
end
|
||||
|
||||
class JSONAPISSerializer
|
||||
def initialize(data, options = {})
|
||||
@options = options.merge(is_collection: true)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def to_json
|
||||
JSONAPI::Serializer.serialize(@data, @options).to_json
|
||||
end
|
||||
|
||||
def to_hash
|
||||
JSONAPI::Serializer.serialize(@data, @options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
classes_to_remove = %i[
|
||||
JSMovie
|
||||
JSActor
|
||||
JSUser
|
||||
JSMovieType
|
||||
JSONAPISSerializer
|
||||
JSActorSerializer
|
||||
JSUserSerializer
|
||||
JSMovieTypeSerializer
|
||||
JSMovieSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:js_actors) do
|
||||
3.times.map do |i|
|
||||
a = JSActor.new
|
||||
a.id = i + 1
|
||||
a.name = "Test #{a.id}"
|
||||
a.email = "test#{a.id}@test.com"
|
||||
a
|
||||
end
|
||||
end
|
||||
|
||||
let(:js_user) do
|
||||
ams_user = JSUser.new
|
||||
ams_user.id = 3
|
||||
ams_user
|
||||
end
|
||||
|
||||
let(:js_movie_type) do
|
||||
ams_movie_type = JSMovieType.new
|
||||
ams_movie_type.id = 1
|
||||
ams_movie_type.name = 'episode'
|
||||
ams_movie_type
|
||||
end
|
||||
|
||||
def build_js_movies(count)
|
||||
count.times.map do |i|
|
||||
m = JSMovie.new
|
||||
m.id = i + 1
|
||||
m.name = 'test movie'
|
||||
m.actors = js_actors
|
||||
m.owner = js_user
|
||||
m.movie_type = js_movie_type
|
||||
m
|
||||
end
|
||||
end
|
||||
end
|
116
spec/shared/contexts/js_group_context.rb
Normal file
116
spec/shared/contexts/js_group_context.rb
Normal file
@ -0,0 +1,116 @@
|
||||
RSpec.shared_context 'jsonapi-serializers group class' do
|
||||
|
||||
# Person, Group Classes and serializers
|
||||
before(:context) do
|
||||
# models
|
||||
class JSPerson
|
||||
attr_accessor :id, :first_name, :last_name
|
||||
end
|
||||
|
||||
class JSGroup
|
||||
attr_accessor :id, :name, :groupees # Let's assume groupees can be Person or Group objects
|
||||
end
|
||||
|
||||
# serializers
|
||||
class JSPersonSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :first_name, :last_name
|
||||
|
||||
def type
|
||||
'person'
|
||||
end
|
||||
end
|
||||
|
||||
class JSGroupSerializer
|
||||
include JSONAPI::Serializer
|
||||
attributes :name
|
||||
has_many :groupees
|
||||
|
||||
def type
|
||||
'group'
|
||||
end
|
||||
end
|
||||
|
||||
class JSONAPISSerializerB
|
||||
def initialize(data, options = {})
|
||||
@options = options.merge(is_collection: true)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def to_json
|
||||
JSON.fast_generate(to_hash)
|
||||
end
|
||||
|
||||
def to_hash
|
||||
JSONAPI::Serializer.serialize(@data, @options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after :context do
|
||||
classes_to_remove = %i[
|
||||
JSPerson
|
||||
JSGroup
|
||||
JSPersonSerializer
|
||||
JSGroupSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapi_groups) do
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
3.times.map do |i|
|
||||
group = JSGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = JSPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = JSGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapis_person) do
|
||||
person = JSPerson.new
|
||||
person.id = 3
|
||||
person
|
||||
end
|
||||
|
||||
def build_jsonapis_groups(count)
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
count.times.map do |i|
|
||||
group = JSGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = JSPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = JSGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
116
spec/shared/contexts/jsonapi_context.rb
Normal file
116
spec/shared/contexts/jsonapi_context.rb
Normal file
@ -0,0 +1,116 @@
|
||||
RSpec.shared_context 'jsonapi movie class' do
|
||||
before(:context) do
|
||||
# models
|
||||
class JSONAPIMovie
|
||||
attr_accessor :id, :name, :release_year, :actors, :owner, :movie_type
|
||||
end
|
||||
|
||||
class JSONAPIActor
|
||||
attr_accessor :id, :name, :email
|
||||
end
|
||||
|
||||
class JSONAPIUser
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
class JSONAPIMovieType
|
||||
attr_accessor :id, :name
|
||||
end
|
||||
|
||||
# serializers
|
||||
class JSONAPIMovieSerializer < JSONAPI::Serializable::Resource
|
||||
type 'movie'
|
||||
attributes :name, :release_year
|
||||
|
||||
has_many :actors
|
||||
has_one :owner
|
||||
belongs_to :movie_type
|
||||
end
|
||||
|
||||
class JSONAPIActorSerializer < JSONAPI::Serializable::Resource
|
||||
type 'actor'
|
||||
attributes :name, :email
|
||||
end
|
||||
|
||||
class JSONAPIUserSerializer < JSONAPI::Serializable::Resource
|
||||
type 'user'
|
||||
attributes :name
|
||||
end
|
||||
|
||||
class JSONAPIMovieTypeSerializer < JSONAPI::Serializable::Resource
|
||||
type 'movie_type'
|
||||
attributes :name
|
||||
end
|
||||
|
||||
class JSONAPISerializer
|
||||
def initialize(data, options = {})
|
||||
@serializer = JSONAPI::Serializable::Renderer.new
|
||||
@options = options.merge(class: {
|
||||
JSONAPIMovie: JSONAPIMovieSerializer,
|
||||
JSONAPIActor: JSONAPIActorSerializer,
|
||||
JSONAPIUser: JSONAPIUserSerializer,
|
||||
JSONAPIMovieType: JSONAPIMovieTypeSerializer
|
||||
})
|
||||
@data = data
|
||||
end
|
||||
|
||||
def to_json
|
||||
@serializer.render(@data, @options).to_json
|
||||
end
|
||||
|
||||
def to_hash
|
||||
@serializer.render(@data, @options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after :context do
|
||||
classes_to_remove = %i[
|
||||
JSONAPIMovie
|
||||
JSONAPIActor
|
||||
JSONAPIUser
|
||||
JSONAPIMovieType
|
||||
JSONAPIMovieSerializer
|
||||
JSONAPIActorSerializer
|
||||
JSONAPIUserSerializer
|
||||
JSONAPIMovieTypeSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapi_actors) do
|
||||
3.times.map do |i|
|
||||
j = JSONAPIActor.new
|
||||
j.id = i + 1
|
||||
j.name = "Test #{j.id}"
|
||||
j.email = "test#{j.id}@test.com"
|
||||
j
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapi_user) do
|
||||
jsonapi_user = JSONAPIUser.new
|
||||
jsonapi_user.id = 3
|
||||
jsonapi_user
|
||||
end
|
||||
|
||||
let(:jsonapi_movie_type) do
|
||||
jsonapi_movie_type = JSONAPIMovieType.new
|
||||
jsonapi_movie_type.id = 1
|
||||
jsonapi_movie_type.name = 'episode'
|
||||
jsonapi_movie_type
|
||||
end
|
||||
|
||||
def build_jsonapi_movies(count)
|
||||
count.times.map do |i|
|
||||
m = JSONAPIMovie.new
|
||||
m.id = i + 1
|
||||
m.name = 'test movie'
|
||||
m.actors = jsonapi_actors
|
||||
m.owner = jsonapi_user
|
||||
m.movie_type = jsonapi_movie_type
|
||||
m
|
||||
end
|
||||
end
|
||||
end
|
112
spec/shared/contexts/jsonapi_group_context.rb
Normal file
112
spec/shared/contexts/jsonapi_group_context.rb
Normal file
@ -0,0 +1,112 @@
|
||||
RSpec.shared_context 'jsonapi group class' do
|
||||
|
||||
# Person, Group Classes and serializers
|
||||
before(:context) do
|
||||
# models
|
||||
class JSONAPIPerson
|
||||
attr_accessor :id, :first_name, :last_name
|
||||
end
|
||||
|
||||
class JSONAPIGroup
|
||||
attr_accessor :id, :name, :groupees # Let's assume groupees can be Person or Group objects
|
||||
end
|
||||
|
||||
# serializers
|
||||
class JSONAPIPersonSerializer < JSONAPI::Serializable::Resource
|
||||
type 'person'
|
||||
attributes :first_name, :last_name
|
||||
end
|
||||
|
||||
class JSONAPIGroupSerializer < JSONAPI::Serializable::Resource
|
||||
type 'group'
|
||||
attributes :name
|
||||
has_many :groupees
|
||||
end
|
||||
|
||||
class JSONAPISerializerB
|
||||
def initialize(data, options = {})
|
||||
@serializer = JSONAPI::Serializable::Renderer.new
|
||||
@options = options.merge(class: {
|
||||
JSONAPIPerson: JSONAPIPersonSerializer,
|
||||
JSONAPIGroup: JSONAPIGroupSerializer
|
||||
})
|
||||
@data = data
|
||||
end
|
||||
|
||||
def to_json
|
||||
@serializer.render(@data, @options).to_json
|
||||
end
|
||||
|
||||
def to_hash
|
||||
@serializer.render(@data, @options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after :context do
|
||||
classes_to_remove = %i[
|
||||
JSONAPIPerson
|
||||
JSONAPIGroup
|
||||
JSONAPIPersonSerializer
|
||||
JSONAPIGroupSerializer]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapi_groups) do
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
3.times.map do |i|
|
||||
group = JSONAPIGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = JSONAPIPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = JSONAPIGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
|
||||
let(:jsonapi_person) do
|
||||
person = JSONAPIPerson.new
|
||||
person.id = 3
|
||||
person
|
||||
end
|
||||
|
||||
def build_jsonapi_groups(count)
|
||||
group_count = 0
|
||||
person_count = 0
|
||||
count.times.map do |i|
|
||||
group = JSONAPIGroup.new
|
||||
group.id = group_count + 1
|
||||
group.name = "Test Group #{group.id}"
|
||||
group_count = group.id
|
||||
|
||||
person = JSONAPIPerson.new
|
||||
person.id = person_count + 1
|
||||
person.last_name = "Last Name #{person.id}"
|
||||
person.first_name = "First Name #{person.id}"
|
||||
person_count = person.id
|
||||
|
||||
child_group = JSONAPIGroup.new
|
||||
child_group.id = group_count + 1
|
||||
child_group.name = "Test Group #{child_group.id}"
|
||||
group_count = child_group.id
|
||||
|
||||
group.groupees = [person, child_group]
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
511
spec/shared/contexts/movie_context.rb
Normal file
511
spec/shared/contexts/movie_context.rb
Normal file
@ -0,0 +1,511 @@
|
||||
RSpec.shared_context 'movie class' do
|
||||
|
||||
# Movie, Actor Classes and serializers
|
||||
before(:context) do
|
||||
# models
|
||||
class Movie
|
||||
attr_accessor :id,
|
||||
:name,
|
||||
:release_year,
|
||||
:director,
|
||||
:actor_ids,
|
||||
:owner_id,
|
||||
:movie_type_id
|
||||
|
||||
def actors
|
||||
actor_ids.map.with_index do |id, i|
|
||||
a = Actor.new
|
||||
a.id = id
|
||||
a.name = "Test #{a.id}"
|
||||
a.email = "test#{a.id}@test.com"
|
||||
a.agency_id = i
|
||||
a
|
||||
end
|
||||
end
|
||||
|
||||
def movie_type
|
||||
mt = MovieType.new
|
||||
mt.id = movie_type_id
|
||||
mt.name = 'Episode'
|
||||
mt.movie_ids = [id]
|
||||
mt
|
||||
end
|
||||
|
||||
def advertising_campaign_id
|
||||
1
|
||||
end
|
||||
|
||||
def advertising_campaign
|
||||
ac = AdvertisingCampaign.new
|
||||
ac.id = 1
|
||||
ac.movie_id = id
|
||||
ac.name = "Movie #{name} is incredible!!"
|
||||
ac
|
||||
end
|
||||
|
||||
def owner
|
||||
return unless owner_id
|
||||
ow = Owner.new
|
||||
ow.id = owner_id
|
||||
ow
|
||||
end
|
||||
|
||||
def cache_key
|
||||
"#{id}"
|
||||
end
|
||||
|
||||
def local_name(locale = :english)
|
||||
"#{locale} #{name}"
|
||||
end
|
||||
|
||||
def url
|
||||
"http://movies.com/#{id}"
|
||||
end
|
||||
|
||||
def actors_relationship_url
|
||||
"#{url}/relationships/actors"
|
||||
end
|
||||
end
|
||||
|
||||
class Actor
|
||||
attr_accessor :id, :name, :email, :agency_id
|
||||
|
||||
def agency
|
||||
Agency.new.tap do |a|
|
||||
a.id = agency_id
|
||||
a.name = "Test Agency #{agency_id}"
|
||||
a.state_id = 1
|
||||
end
|
||||
end
|
||||
|
||||
def awards
|
||||
award_ids.map do |i|
|
||||
Award.new.tap do |a|
|
||||
a.id = i
|
||||
a.title = "Test Award #{i}"
|
||||
a.actor_id = id
|
||||
a.imdb_award_id = i * 10
|
||||
a.year = 1990 + i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def award_ids
|
||||
[id * 9, id * 9 + 1]
|
||||
end
|
||||
|
||||
def url
|
||||
"http://movies.com/actors/#{id}"
|
||||
end
|
||||
end
|
||||
|
||||
class AdvertisingCampaign
|
||||
attr_accessor :id, :name, :movie_id
|
||||
end
|
||||
|
||||
class Agency
|
||||
attr_accessor :id, :name, :state_id
|
||||
|
||||
def state
|
||||
State.new.tap do |s|
|
||||
s.id = state_id
|
||||
s.name = "Test State #{state_id}"
|
||||
s.agency_ids = [id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Award
|
||||
attr_accessor :id, :title, :actor_id, :year, :imdb_award_id
|
||||
end
|
||||
|
||||
class State
|
||||
attr_accessor :id, :name, :agency_ids
|
||||
end
|
||||
|
||||
class MovieType
|
||||
attr_accessor :id, :name, :movie_ids
|
||||
|
||||
def movies
|
||||
movie_ids.map.with_index do |id, i|
|
||||
m = Movie.new
|
||||
m.id = 232
|
||||
m.name = 'test movie'
|
||||
m.actor_ids = [1, 2, 3]
|
||||
m.owner_id = 3
|
||||
m.movie_type_id = 1
|
||||
m
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Agency
|
||||
attr_accessor :id, :name, :actor_ids
|
||||
end
|
||||
|
||||
class Agency
|
||||
attr_accessor :id, :name, :actor_ids
|
||||
end
|
||||
|
||||
class Supplier
|
||||
attr_accessor :id, :account_id
|
||||
|
||||
def account
|
||||
if account_id
|
||||
a = Account.new
|
||||
a.id = account_id
|
||||
a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Account
|
||||
attr_accessor :id
|
||||
end
|
||||
|
||||
class Owner
|
||||
attr_accessor :id
|
||||
end
|
||||
|
||||
class OwnerSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
end
|
||||
|
||||
# serializers
|
||||
class MovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
# director attr is not mentioned intentionally
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
belongs_to :owner, record_type: :user do |object, params|
|
||||
object.owner
|
||||
end
|
||||
belongs_to :movie_type
|
||||
has_one :advertising_campaign
|
||||
end
|
||||
|
||||
class GenreMovieSerializer < MovieSerializer
|
||||
link(:something) { '/something/' }
|
||||
end
|
||||
|
||||
class ActionMovieSerializer < GenreMovieSerializer
|
||||
link(:url) { |object| "/action-movie/#{object.id}" }
|
||||
end
|
||||
|
||||
class HorrorMovieSerializer < GenreMovieSerializer
|
||||
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 OptionalDownloadableMovieWithLambdaSerializer < MovieSerializer
|
||||
link(:download, if: ->(record) { record.release_year >= 2000 }) do |movie|
|
||||
"/download/#{movie.id}"
|
||||
end
|
||||
end
|
||||
|
||||
class MovieWithoutIdStructSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :name, :release_year
|
||||
end
|
||||
|
||||
class CachingMovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
has_many :actors
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options store: ActiveSupport::Cache::MemoryStore.new, expires_in: 5.minutes
|
||||
end
|
||||
|
||||
class CachingMovieWithHasManySerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
has_many :actors, cached: true
|
||||
belongs_to :owner, record_type: :user
|
||||
belongs_to :movie_type
|
||||
|
||||
cache_options store: ActiveSupport::Cache::MemoryStore.new, namespace: 'fast-jsonapi'
|
||||
end
|
||||
|
||||
class ActorSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :actor
|
||||
attributes :name, :email
|
||||
belongs_to :agency
|
||||
has_many :awards
|
||||
belongs_to :agency
|
||||
end
|
||||
|
||||
class AgencySerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :name
|
||||
belongs_to :state
|
||||
has_many :actors
|
||||
end
|
||||
|
||||
class AwardSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :title
|
||||
attribute :year, if: Proc.new { |record, params|
|
||||
params[:include_award_year].present? ?
|
||||
params[:include_award_year] :
|
||||
false
|
||||
}
|
||||
belongs_to :actor
|
||||
end
|
||||
|
||||
class StateSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :name
|
||||
has_many :agency
|
||||
end
|
||||
|
||||
class AdvertisingCampaignSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :name
|
||||
belongs_to :movie
|
||||
end
|
||||
|
||||
class MovieTypeSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie_type
|
||||
attributes :name
|
||||
has_many :movies
|
||||
end
|
||||
|
||||
class MovieSerializerWithAttributeBlock
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
attribute :title_with_year do |record|
|
||||
"#{record.name} (#{record.release_year})"
|
||||
end
|
||||
end
|
||||
|
||||
class MovieSerializerWithAttributeBlock
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name, :release_year
|
||||
attribute :title_with_year do |record|
|
||||
"#{record.name} (#{record.release_year})"
|
||||
end
|
||||
end
|
||||
|
||||
class AgencySerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
attributes :id, :name
|
||||
has_many :actors
|
||||
end
|
||||
|
||||
class SupplierSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :supplier
|
||||
has_one :account
|
||||
end
|
||||
|
||||
class AccountSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :account
|
||||
belongs_to :supplier
|
||||
end
|
||||
|
||||
class MovieOptionalRecordDataSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :release_year, if: Proc.new { |record| record.release_year >= 2000 }
|
||||
end
|
||||
|
||||
class MovieOptionalRecordDataWithLambdaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :release_year, if: ->(record) { record.release_year >= 2000 }
|
||||
end
|
||||
|
||||
class MovieOptionalParamsDataSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :director, if: Proc.new { |record, params| params[:admin] == true }
|
||||
end
|
||||
|
||||
class MovieOptionalRelationshipSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
has_many :actors, if: Proc.new { |record| record.actors.any? }
|
||||
end
|
||||
|
||||
class MovieOptionalRelationshipWithLambdaSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
has_many :actors, if: ->(record) { record.actors.any? }
|
||||
end
|
||||
|
||||
class MovieOptionalRelationshipWithParamsSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
belongs_to :owner, record_type: :user, if: Proc.new { |record, params| params[:admin] == true }
|
||||
end
|
||||
|
||||
class MovieOptionalAttributeContentsWithParamsSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
set_type :movie
|
||||
attributes :name
|
||||
attribute :director do |record, params|
|
||||
data = {}
|
||||
data[:first_name] = 'steven'
|
||||
data[:last_name] = 'spielberg' if params[:admin]
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Namespaced MovieSerializer
|
||||
before(:context) do
|
||||
# namespaced model stub
|
||||
module AppName
|
||||
module V1
|
||||
class MovieSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
# to test if compute_serializer_name works
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Movie and Actor struct
|
||||
before(:context) do
|
||||
MovieStruct = Struct.new(
|
||||
:id,
|
||||
:name,
|
||||
:release_year,
|
||||
:actor_ids,
|
||||
:actors,
|
||||
:owner_id,
|
||||
:owner,
|
||||
:movie_type_id,
|
||||
:advertising_campaign_id
|
||||
)
|
||||
|
||||
ActorStruct = Struct.new(:id, :name, :email, :agency_id, :award_ids)
|
||||
MovieWithoutIdStruct = Struct.new(:name, :release_year)
|
||||
AgencyStruct = Struct.new(:id, :name, :actor_ids)
|
||||
end
|
||||
|
||||
after(:context) do
|
||||
classes_to_remove = %i[
|
||||
ActionMovieSerializer
|
||||
GenreMovieSerializer
|
||||
HorrorMovieSerializer
|
||||
OptionalDownloadableMovieSerializer
|
||||
OptionalDownloadableMovieWithLambdaSerializer
|
||||
Movie
|
||||
MovieSerializer
|
||||
Actor
|
||||
ActorSerializer
|
||||
MovieType
|
||||
MovieTypeSerializer
|
||||
AppName::V1::MovieSerializer
|
||||
MovieStruct
|
||||
ActorStruct
|
||||
MovieWithoutIdStruct
|
||||
HyphenMovieSerializer
|
||||
MovieWithoutIdStructSerializer
|
||||
Agency
|
||||
AgencyStruct
|
||||
AgencySerializer
|
||||
AdvertisingCampaign
|
||||
AdvertisingCampaignSerializer
|
||||
]
|
||||
classes_to_remove.each do |klass_name|
|
||||
Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:movie_struct) do
|
||||
|
||||
agency = AgencyStruct
|
||||
|
||||
actors = []
|
||||
|
||||
3.times.each do |id|
|
||||
actors << ActorStruct.new(id, id.to_s, id.to_s, id, [id])
|
||||
end
|
||||
|
||||
m = MovieStruct.new
|
||||
m[:id] = 23
|
||||
m[:name] = 'struct movie'
|
||||
m[:release_year] = 1987
|
||||
m[:actor_ids] = [1,2,3]
|
||||
m[:owner_id] = 3
|
||||
m[:movie_type_id] = 2
|
||||
m[:actors] = actors
|
||||
m
|
||||
end
|
||||
|
||||
let(:movie_struct_without_id) do
|
||||
MovieWithoutIdStruct.new('struct without id', 2018)
|
||||
end
|
||||
|
||||
let(:movie) do
|
||||
m = Movie.new
|
||||
m.id = 232
|
||||
m.name = 'test movie'
|
||||
m.actor_ids = [1, 2, 3]
|
||||
m.owner_id = 3
|
||||
m.movie_type_id = 1
|
||||
m
|
||||
end
|
||||
|
||||
let(:actor) do
|
||||
Actor.new.tap do |a|
|
||||
a.id = 234
|
||||
a.name = 'test actor'
|
||||
a.email = 'test@test.com'
|
||||
a.agency_id = 432
|
||||
end
|
||||
end
|
||||
|
||||
let(:movie_type) do
|
||||
movie
|
||||
|
||||
mt = MovieType.new
|
||||
mt.id = movie.movie_type_id
|
||||
mt.name = 'Foreign Thriller'
|
||||
mt.movie_ids = [movie.id]
|
||||
mt
|
||||
end
|
||||
|
||||
let(:supplier) do
|
||||
s = Supplier.new
|
||||
s.id = 1
|
||||
s.account_id = 1
|
||||
s
|
||||
end
|
||||
|
||||
def build_movies(count)
|
||||
count.times.map do |i|
|
||||
m = Movie.new
|
||||
m.id = i + 1
|
||||
m.name = 'test movie'
|
||||
m.actor_ids = [1, 2, 3]
|
||||
m.owner_id = 3
|
||||
m.movie_type_id = 1
|
||||
m
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,18 @@
|
||||
RSpec.shared_examples 'returning correct relationship hash' do |id_method_name, record_type|
|
||||
it 'returns correct relationship hash' do
|
||||
expect(relationship).to be_instance_of(FastJsonapi::Relationship)
|
||||
# expect(relationship.keys).to all(be_instance_of(Symbol))
|
||||
expect(relationship.static_serializer).to be relationship_serializer
|
||||
expect(relationship.id_method_name).to be id_method_name
|
||||
expect(relationship.static_record_type).to be record_type
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'returning key transformed hash' do |relationship_name, resource_type, release_year|
|
||||
it 'returns correctly transformed hash' do
|
||||
expect(hash[:data][0][:attributes]).to have_key(release_year)
|
||||
expect(hash[:data][0][:relationships]).to have_key(relationship_name)
|
||||
expect(hash[:data][0][:relationships][relationship_name][:data][:type]).to eq(resource_type)
|
||||
expect(hash[:included][0][:type]).to eq(resource_type)
|
||||
end
|
||||
end
|
@ -1,30 +1,21 @@
|
||||
require 'simplecov'
|
||||
|
||||
SimpleCov.start do
|
||||
add_group 'Lib', 'lib'
|
||||
add_group 'Tests', 'spec'
|
||||
end
|
||||
SimpleCov.minimum_coverage 90
|
||||
|
||||
require 'active_support'
|
||||
require 'active_support/core_ext/object/json'
|
||||
require 'jsonapi/serializer'
|
||||
require 'ffaker'
|
||||
require 'rspec'
|
||||
require 'jsonapi/rspec'
|
||||
require 'active_record'
|
||||
require 'fast_jsonapi'
|
||||
require 'rspec-benchmark'
|
||||
require 'byebug'
|
||||
require 'securerandom'
|
||||
require 'active_model_serializers'
|
||||
require 'oj'
|
||||
require 'jsonapi/serializable'
|
||||
require 'jsonapi-serializers'
|
||||
|
||||
Dir[File.expand_path('spec/fixtures/*.rb')].sort.each { |f| require f }
|
||||
Dir[File.dirname(__FILE__) + '/shared/contexts/*.rb'].each {|file| require file }
|
||||
Dir[File.dirname(__FILE__) + '/shared/examples/*.rb'].each {|file| require file }
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include JSONAPI::RSpec
|
||||
|
||||
config.mock_with :rspec
|
||||
config.filter_run_when_matching :focus
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
config.include RSpec::Benchmark::Matchers
|
||||
config.filter_run_excluding performance: ENV['BENCHMARK'].blank?
|
||||
end
|
||||
|
||||
Oj.optimize_rails
|
||||
ActiveModel::Serializer.config.adapter = :json_api
|
||||
ActiveModel::Serializer.config.key_transform = :underscore
|
||||
ActiveModelSerializers.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new('/dev/null'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user