Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7db80f673d | ||
|
bcee2b597b | ||
|
44cf8495ea | ||
|
37235057df | ||
|
c21f9def8f | ||
|
8b74954478 | ||
|
ef93f7f358 | ||
|
c5eb1ce27c | ||
|
a25d415b4d | ||
|
c3376037e7 | ||
|
963cd77900 | ||
|
98dd59884c | ||
|
b7e8a30833 | ||
|
f4ed4f0440 | ||
|
88061bcfac | ||
|
f8255771dc | ||
|
1bcf8d2cb5 | ||
|
0b819e09b9 | ||
|
72f3ae64dd | ||
|
eae66a9610 | ||
|
5538555537 | ||
|
4d1a764abc | ||
|
c32358ecf5 | ||
|
f56a354860 | ||
|
8401d16c2e | ||
|
1ce4677a22 | ||
|
2a946c7723 | ||
|
51b319def5 | ||
|
593e8ea4e6 | ||
|
e2ac01f98a | ||
|
a4bd5a1edc | ||
|
f62a5bf162 | ||
|
430a5ca2ac | ||
|
84cd54bd0e | ||
|
0e051fcad2 | ||
|
eab3dbdb27 | ||
|
dd7f5ba415 | ||
|
6db86e0f4c | ||
|
95ce09d526 | ||
|
77d622e4a8 | ||
|
3ff69ccce1 | ||
|
1819629408 | ||
|
2de80d4889 | ||
|
9d08d2abed | ||
|
7e2289006c | ||
|
1d2eab2510 | ||
|
ac34aa22f7 | ||
|
b64a0ceeed | ||
|
c533634293 | ||
|
73814d1be5 | ||
|
713e0ef58d | ||
|
a529d2808a | ||
|
12e2987420 | ||
|
c32aacd8a6 | ||
|
529f51b04e | ||
|
100711f678 | ||
|
2a791bd90c | ||
|
ab9db291d4 | ||
|
c31bcdc379 | ||
|
7c31594740 | ||
|
07d46a9aac |
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -1,19 +1,19 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [push]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
ruby: [2.4, 2.5, 2.6, 2.7]
|
ruby: [2.4, 2.7, '3.0', 3.1, truffleruby-head]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
|
||||||
- name: Sets up the Ruby version
|
- name: Sets up the Ruby version
|
||||||
uses: actions/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby }}
|
ruby-version: ${{ matrix.ruby }}
|
||||||
|
|
||||||
@ -24,17 +24,17 @@ jobs:
|
|||||||
bundle install
|
bundle install
|
||||||
|
|
||||||
- name: Runs code QA and tests
|
- name: Runs code QA and tests
|
||||||
run: rspec
|
run: bundle exec rake
|
||||||
|
|
||||||
- name: Publish to GPR
|
- name: Publish to Rubygems
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
continue-on-error: true
|
||||||
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $HOME/.gem
|
mkdir -p $HOME/.gem
|
||||||
touch $HOME/.gem/credentials
|
touch $HOME/.gem/credentials
|
||||||
chmod 0600 $HOME/.gem/credentials
|
chmod 0600 $HOME/.gem/credentials
|
||||||
printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
printf -- "---\n:rubygems_api_key: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
||||||
gem build
|
gem build *.gemspec
|
||||||
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
gem push *.gem
|
||||||
env:
|
env:
|
||||||
GEM_HOST_API_KEY: ${{secrets.GPR_AUTH_TOKEN}}
|
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
|
||||||
OWNER: fast_jsonapi
|
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -41,5 +41,8 @@ test.db
|
|||||||
/vendor
|
/vendor
|
||||||
|
|
||||||
# Don't checkin Gemfile.lock
|
# Don't checkin Gemfile.lock
|
||||||
# See: http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
# See: https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
|
||||||
|
# Gem builds
|
||||||
|
/*.gem
|
||||||
|
102
.rubocop.yml
Normal file
102
.rubocop.yml
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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,6 +4,50 @@ 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/),
|
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).
|
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
|
## [1.7.0] - 2020-04-29
|
||||||
### Added
|
### Added
|
||||||
- Serializer option support for procs (#32)
|
- Serializer option support for procs (#32)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
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 not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
243
README.md
243
README.md
@ -1,23 +1,29 @@
|
|||||||
# Fast JSON API
|
# JSON:API Serialization Library
|
||||||
|
|
||||||
A lightning fast [JSON:API](http://jsonapi.org/) serializer for Ruby Objects.
|
## :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!
|
||||||
|
|
||||||
# Performance Comparison
|
# Performance Comparison
|
||||||
|
|
||||||
We compare serialization times with Active Model Serializer as part of RSpec
|
We compare serialization times with `ActiveModelSerializer` and alternative
|
||||||
performance tests included on this library. We want to ensure that with every
|
implementations as part of performance tests available at
|
||||||
change on this library, serialization time is about _25 times_ faster than
|
[jsonapi-serializer/comparisons](https://github.com/jsonapi-serializer/comparisons).
|
||||||
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.
|
|
||||||
|
|
||||||
## Benchmark times for 250 records
|
We want to ensure that with every
|
||||||
|
change on this library, serialization time stays significantly faster than
|
||||||
```bash
|
the performance provided by the alternatives. Please read the performance
|
||||||
$ rspec
|
article in the `docs` folder for any questions related to methodology.
|
||||||
Active Model Serializer serialized 250 records in 138.71 ms
|
|
||||||
Fast JSON API serialized 250 records in 3.01 ms
|
|
||||||
```
|
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
|
|
||||||
@ -38,6 +44,9 @@ Fast JSON API serialized 250 records in 3.01 ms
|
|||||||
* [Specifying a Relationship Serializer](#specifying-a-relationship-serializer)
|
* [Specifying a Relationship Serializer](#specifying-a-relationship-serializer)
|
||||||
* [Sparse Fieldsets](#sparse-fieldsets)
|
* [Sparse Fieldsets](#sparse-fieldsets)
|
||||||
* [Using helper methods](#using-helper-methods)
|
* [Using helper methods](#using-helper-methods)
|
||||||
|
* [Performance Instrumentation](#performance-instrumentation)
|
||||||
|
* [Deserialization](#deserialization)
|
||||||
|
* [Migrating from Netflix/fast_jsonapi](#migrating-from-netflixfast_jsonapi)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
|
|
||||||
|
|
||||||
@ -54,7 +63,7 @@ Fast JSON API serialized 250 records in 3.01 ms
|
|||||||
Add this line to your application's Gemfile:
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
gem 'fast_jsonapi', '~> 1.6.0', git: 'https://github.com/fast-jsonapi/fast_jsonapi'
|
gem 'jsonapi-serializer'
|
||||||
```
|
```
|
||||||
|
|
||||||
Execute:
|
Execute:
|
||||||
@ -85,7 +94,8 @@ end
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
set_type :movie # optional
|
set_type :movie # optional
|
||||||
set_id :owner_id # optional
|
set_id :owner_id # optional
|
||||||
attributes :name, :year
|
attributes :name, :year
|
||||||
@ -166,12 +176,16 @@ 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
|
### 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
|
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
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
# Available options :camel, :camel_lower, :dash, :underscore(default)
|
# Available options :camel, :camel_lower, :dash, :underscore(default)
|
||||||
set_key_transform :camel
|
set_key_transform :camel
|
||||||
end
|
end
|
||||||
@ -186,13 +200,13 @@ set_key_transform :underscore # "some_key" => "some_key"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Attributes
|
### Attributes
|
||||||
Attributes are defined in FastJsonapi using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute.
|
Attributes are defined 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:
|
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
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attribute :name
|
attribute :name
|
||||||
end
|
end
|
||||||
@ -202,7 +216,7 @@ Custom attributes that must be serialized but do not exist on the model can be d
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attributes :name, :year
|
attributes :name, :year
|
||||||
|
|
||||||
@ -216,7 +230,7 @@ The block syntax can also be used to override the property on the object:
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attribute :name do |object|
|
attribute :name do |object|
|
||||||
"#{object.name} Part 2"
|
"#{object.name} Part 2"
|
||||||
@ -228,7 +242,7 @@ Attributes can also use a different name by passing the original method or acces
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attributes :name
|
attributes :name
|
||||||
|
|
||||||
@ -237,7 +251,7 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Links Per Object
|
### Links Per Object
|
||||||
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.
|
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.
|
||||||
|
|
||||||
You can configure the method to use on the object for example a link with key `self` will get set to the value returned by a method called `url` on the movie object.
|
You can 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.
|
||||||
|
|
||||||
@ -245,29 +259,29 @@ You can also use a block to define a url as shown in `custom_url`. You can acces
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
link :public_url
|
link :public_url
|
||||||
|
|
||||||
link :self, :url
|
link :self, :url
|
||||||
|
|
||||||
link :custom_url do |object|
|
link :custom_url do |object|
|
||||||
"http://movies.com/#{object.name}-(#{object.year})"
|
"https://movies.com/#{object.name}-(#{object.year})"
|
||||||
end
|
end
|
||||||
|
|
||||||
link :personalized_url do |object, params|
|
link :personalized_url do |object, params|
|
||||||
"http://movies.com/#{object.name}-#{params[:user].reference_code}"
|
"https://movies.com/#{object.name}-#{params[:user].reference_code}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Links on a Relationship
|
#### Links on a Relationship
|
||||||
|
|
||||||
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))
|
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))
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
has_many :actors, links: {
|
has_many :actors, links: {
|
||||||
self: :url,
|
self: :url,
|
||||||
@ -302,7 +316,7 @@ For every resource in the collection, you can include a meta object containing n
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
meta do |movie|
|
meta do |movie|
|
||||||
{
|
{
|
||||||
@ -312,6 +326,23 @@ class MovieSerializer
|
|||||||
end
|
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
|
### Compound Document
|
||||||
|
|
||||||
Support for top-level and nested included associations through `options[:include]`.
|
Support for top-level and nested included associations through `options[:include]`.
|
||||||
@ -367,10 +398,10 @@ To enable caching, use `cache_options store: <cache_store>`:
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
# use rails cache with a separate namespace and fixed expiry
|
# use rails cache with a separate namespace and fixed expiry
|
||||||
cache_options store: Rails.cache, namespace: 'fast-jsonapi', expires_in: 1.hour
|
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -381,12 +412,31 @@ end
|
|||||||
- `options` is everything that was passed to `cache_options` except `store`, so it can be everyhing the cache store supports
|
- `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
|
- `&block` should be executed to fetch new data if cache is empty
|
||||||
|
|
||||||
So for the example above, FastJsonapi will call the cache instance like this:
|
So for the example above it will call the cache instance like this:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Rails.cache.fetch(record, namespace: 'fast-jsonapi, expires_in: 1.hour) { ... }
|
Rails.cache.fetch(record, namespace: 'jsonapi-serializer', 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
|
### Params
|
||||||
|
|
||||||
In some cases, attribute values might require more information than what is
|
In some cases, attribute values might require more information than what is
|
||||||
@ -401,7 +451,7 @@ parameter.
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
set_id do |movie, params|
|
set_id do |movie, params|
|
||||||
# in here, params is a hash containing the `:admin` key
|
# in here, params is a hash containing the `:admin` key
|
||||||
@ -416,7 +466,7 @@ class MovieSerializer
|
|||||||
|
|
||||||
belongs_to :primary_agent do |movie, params|
|
belongs_to :primary_agent do |movie, params|
|
||||||
# in here, params is a hash containing the `:current_user` key
|
# in here, params is a hash containing the `:current_user` key
|
||||||
params[:current_user].is_employee? ? true : false
|
params[:current_user]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -435,7 +485,7 @@ Conditional attributes can be defined by passing a Proc to the `if` key on the `
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attributes :name, :year
|
attributes :name, :year
|
||||||
attribute :release_year, if: Proc.new { |record|
|
attribute :release_year, if: Proc.new { |record|
|
||||||
@ -447,6 +497,13 @@ class MovieSerializer
|
|||||||
# The director will be serialized only if the :admin key of params is true
|
# The director will be serialized only if the :admin key of params is true
|
||||||
params && params[:admin] == 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
|
end
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
@ -461,7 +518,7 @@ Conditional relationships can be defined by passing a Proc to the `if` key. Retu
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
# Actors will only be serialized if the record has any associated actors
|
# Actors will only be serialized if the record has any associated actors
|
||||||
has_many :actors, if: Proc.new { |record| record.actors.any? }
|
has_many :actors, if: Proc.new { |record| record.actors.any? }
|
||||||
@ -482,7 +539,7 @@ In many cases, the relationship can automatically detect the serializer to use.
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
# resolves to StudioSerializer
|
# resolves to StudioSerializer
|
||||||
belongs_to :studio
|
belongs_to :studio
|
||||||
@ -495,7 +552,7 @@ At other times, such as when a property name differs from the class name, you ma
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
# resolves to MovieStudioSerializer
|
# resolves to MovieStudioSerializer
|
||||||
belongs_to :studio, serializer: :movie_studio
|
belongs_to :studio, serializer: :movie_studio
|
||||||
@ -508,7 +565,7 @@ For more advanced cases, such as polymorphic relationships and Single Table Inhe
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
has_many :actors, serializer: Proc.new do |record, params|
|
has_many :actors, serializer: Proc.new do |record, params|
|
||||||
if record.comedian?
|
if record.comedian?
|
||||||
@ -528,7 +585,7 @@ Attributes and relationships can be selectively returned per record type by usin
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class MovieSerializer
|
class MovieSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
attributes :name, :year
|
attributes :name, :year
|
||||||
end
|
end
|
||||||
@ -559,7 +616,7 @@ module AvatarHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
class UserSerializer
|
class UserSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
include AvatarHelper # mixes in your helper method as class method
|
include AvatarHelper # mixes in your helper method as class method
|
||||||
|
|
||||||
@ -584,7 +641,7 @@ module AvatarHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
class UserSerializer
|
class UserSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
extend AvatarHelper # mixes in your helper method as class method
|
extend AvatarHelper # mixes in your helper method as class method
|
||||||
|
|
||||||
@ -614,36 +671,27 @@ 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 | 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 }`
|
polymorphic | Sets custom record types for each object class in a polymorphic association | `has_many :targets, polymorphic: { Person => :person, Group => :group }`
|
||||||
|
|
||||||
### Instrumentation
|
### Performance Instrumentation
|
||||||
|
|
||||||
`fast_jsonapi` also has builtin [Skylight](https://www.skylight.io/) integration. To enable, add the following to an initializer:
|
Performance instrumentation is available by using the
|
||||||
|
`active_support/notifications`.
|
||||||
|
|
||||||
|
To enable it, include the module in your serializer class:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require 'fast_jsonapi/instrumentation/skylight'
|
require 'jsonapi/serializer'
|
||||||
|
require 'jsonapi/serializer/instrumentation'
|
||||||
|
|
||||||
|
class MovieSerializer
|
||||||
|
include JSONAPI::Serializer
|
||||||
|
include JSONAPI::Serializer::Instrumentation
|
||||||
|
|
||||||
|
# ...
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
[Skylight](https://www.skylight.io/) integration is also available and
|
||||||
|
supported by us, follow the Skylight documentation to enable it.
|
||||||
```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
|
### Running Tests
|
||||||
The project has and requires unit tests, functional tests and performance
|
The project has and requires unit tests, functional tests and performance
|
||||||
@ -653,16 +701,59 @@ tests. To run tests use the following command:
|
|||||||
rspec
|
rspec
|
||||||
```
|
```
|
||||||
|
|
||||||
To run tests without the performance tests (for quicker test runs):
|
## Deserialization
|
||||||
|
We currently do not support deserialization, but we recommend to use any of the next gems:
|
||||||
|
|
||||||
```bash
|
### [JSONAPI.rb](https://github.com/stas/jsonapi.rb)
|
||||||
rspec spec --tag ~performance:true
|
|
||||||
|
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'
|
||||||
```
|
```
|
||||||
|
|
||||||
To run tests only performance tests:
|
### Replace all constant references
|
||||||
|
|
||||||
```bash
|
```diff
|
||||||
rspec spec --tag performance:true
|
class MovieSerializer
|
||||||
|
- include FastJsonapi::ObjectSerializer
|
||||||
|
+ include JSONAPI::Serializer
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace removed methods
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- json_string = MovieSerializer.new(movie).serialized_json
|
||||||
|
+ json_string = MovieSerializer.new(movie).serializable_hash.to_json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace require references
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- require 'fast_jsonapi'
|
||||||
|
+ require 'jsonapi/serializer'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update your cache options
|
||||||
|
|
||||||
|
See [docs](https://github.com/jsonapi-serializer/jsonapi-serializer#caching).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- cache_options enabled: true, cache_length: 12.hours
|
||||||
|
+ cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
@ -672,4 +763,4 @@ pull request creation processes.
|
|||||||
|
|
||||||
This project is intended to be a safe, welcoming space for collaboration, and
|
This project is intended to be a safe, welcoming space for collaboration, and
|
||||||
contributors are expected to adhere to the
|
contributors are expected to adhere to the
|
||||||
[Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
[Contributor Covenant](https://contributor-covenant.org) code of conduct.
|
||||||
|
15
Rakefile
Normal file
15
Rakefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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'
|
require 'fast_jsonapi'
|
||||||
|
|
||||||
class BaseSerializer
|
class BaseSerializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
|
|
||||||
def to_json
|
def to_json
|
||||||
Oj.dump(serializable_hash)
|
Oj.dump(serializable_hash)
|
||||||
|
@ -23,7 +23,7 @@ cases.
|
|||||||
|
|
||||||
We came up with patterns that we can rely upon such as:
|
We came up with patterns that we can rely upon such as:
|
||||||
|
|
||||||
* We always use [JSON:API](http://jsonapi.org/) for our APIs
|
* We always use [JSON:API](https://jsonapi.org/) for our APIs
|
||||||
* We almost always serialize a homogenous list of objects (Example: An array of
|
* We almost always serialize a homogenous list of objects (Example: An array of
|
||||||
movies)
|
movies)
|
||||||
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
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
|
|
37
jsonapi-serializer.gemspec
Normal file
37
jsonapi-serializer.gemspec
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'jsonapi/serializer/errors'
|
||||||
|
|
||||||
module FastJsonapi
|
module FastJsonapi
|
||||||
require 'fast_jsonapi/object_serializer'
|
require 'fast_jsonapi/object_serializer'
|
||||||
if defined?(::Rails)
|
if defined?(::Rails)
|
||||||
|
@ -6,7 +6,16 @@ module FastJsonapi
|
|||||||
# @param [Array<Object>] *params any number of parameters to be passed to the Proc
|
# @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
|
# @return [Object] the result of the Proc call with the supplied parameters
|
||||||
def call_proc(proc, *params)
|
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))
|
proc.call(*params.take(proc.parameters.length))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
require 'fast_jsonapi/instrumentation/serializable_hash'
|
require 'jsonapi/serializer/instrumentation'
|
||||||
require 'fast_jsonapi/instrumentation/serialized_json'
|
|
||||||
|
warn(
|
||||||
|
'DEPRECATION: Performance instrumentation is no longer automatic. See: ' \
|
||||||
|
'https://github.com/jsonapi-serializer/jsonapi-serializer' \
|
||||||
|
'#performance-instrumentation'
|
||||||
|
)
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
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
|
|
@ -1,15 +0,0 @@
|
|||||||
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,2 +1,3 @@
|
|||||||
require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash'
|
require 'skylight'
|
||||||
require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json'
|
|
||||||
|
warn('DEPRECATION: Skylight support was moved into the `skylight` gem.')
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
require 'skylight'
|
|
||||||
|
|
||||||
SKYLIGHT_NORMALIZER_BASE_CLASS = begin
|
|
||||||
::Skylight::Core::Normalizers::Normalizer
|
|
||||||
rescue NameError
|
|
||||||
::Skylight::Normalizers::Normalizer
|
|
||||||
end
|
|
@ -1,22 +0,0 @@
|
|||||||
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
|
|
@ -1,22 +0,0 @@
|
|||||||
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,5 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'active_support'
|
||||||
require 'active_support/time'
|
require 'active_support/time'
|
||||||
require 'active_support/concern'
|
require 'active_support/concern'
|
||||||
require 'active_support/inflector'
|
require 'active_support/inflector'
|
||||||
@ -15,8 +16,6 @@ module FastJsonapi
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include SerializationCore
|
include SerializationCore
|
||||||
|
|
||||||
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
|
|
||||||
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
|
|
||||||
TRANSFORMS_MAPPING = {
|
TRANSFORMS_MAPPING = {
|
||||||
camel: :camelize,
|
camel: :camelize,
|
||||||
camel_lower: [:camelize, :lower],
|
camel_lower: [:camelize, :lower],
|
||||||
@ -36,11 +35,13 @@ module FastJsonapi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
return hash_for_collection if is_collection?(@resource, @is_collection)
|
if self.class.is_collection?(@resource, @is_collection)
|
||||||
|
return hash_for_collection
|
||||||
|
end
|
||||||
|
|
||||||
hash_for_one_record
|
hash_for_one_record
|
||||||
end
|
end
|
||||||
alias_method :to_hash, :serializable_hash
|
alias to_hash serializable_hash
|
||||||
|
|
||||||
def hash_for_one_record
|
def hash_for_one_record
|
||||||
serializable_hash = { data: nil }
|
serializable_hash = { data: nil }
|
||||||
@ -72,15 +73,6 @@ module FastJsonapi
|
|||||||
serializable_hash
|
serializable_hash
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def process_options(options)
|
def process_options(options)
|
||||||
@ -89,12 +81,12 @@ module FastJsonapi
|
|||||||
|
|
||||||
return if options.blank?
|
return if options.blank?
|
||||||
|
|
||||||
@known_included_objects = {}
|
@known_included_objects = Set.new
|
||||||
@meta = options[:meta]
|
@meta = options[:meta]
|
||||||
@links = options[:links]
|
@links = options[:links]
|
||||||
@is_collection = options[:is_collection]
|
@is_collection = options[:is_collection]
|
||||||
@params = options[:params] || {}
|
@params = options[:params] || {}
|
||||||
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
|
raise ArgumentError, '`params` option passed to serializer must be a hash' unless @params.is_a?(Hash)
|
||||||
|
|
||||||
if options[:include].present?
|
if options[:include].present?
|
||||||
@includes = options[:include].reject(&:blank?).map(&:to_sym)
|
@includes = options[:include].reject(&:blank?).map(&:to_sym)
|
||||||
@ -114,14 +106,16 @@ module FastJsonapi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
# Detects a collection/enumerable
|
||||||
|
#
|
||||||
|
# @return [TrueClass] on a successful detection
|
||||||
def is_collection?(resource, force_is_collection = nil)
|
def is_collection?(resource, force_is_collection = nil)
|
||||||
return force_is_collection unless force_is_collection.nil?
|
return force_is_collection unless force_is_collection.nil?
|
||||||
|
|
||||||
resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
|
resource.is_a?(Enumerable) && !resource.respond_to?(:each_pair)
|
||||||
end
|
end
|
||||||
|
|
||||||
class_methods do
|
|
||||||
|
|
||||||
def inherited(subclass)
|
def inherited(subclass)
|
||||||
super(subclass)
|
super(subclass)
|
||||||
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
|
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
|
||||||
@ -140,11 +134,7 @@ module FastJsonapi
|
|||||||
def reflected_record_type
|
def reflected_record_type
|
||||||
return @reflected_record_type if defined?(@reflected_record_type)
|
return @reflected_record_type if defined?(@reflected_record_type)
|
||||||
|
|
||||||
@reflected_record_type ||= begin
|
@reflected_record_type ||= (name.split('::').last.chomp('Serializer').underscore.to_sym if name&.end_with?('Serializer'))
|
||||||
if self.name && self.name.end_with?('Serializer')
|
|
||||||
self.name.split('::').last.chomp('Serializer').underscore.to_sym
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_key_transform(transform_name)
|
def set_key_transform(transform_name)
|
||||||
@ -153,13 +143,14 @@ module FastJsonapi
|
|||||||
# ensure that the record type is correctly transformed
|
# ensure that the record type is correctly transformed
|
||||||
if record_type
|
if record_type
|
||||||
set_type(record_type)
|
set_type(record_type)
|
||||||
|
# TODO: Remove dead code
|
||||||
elsif reflected_record_type
|
elsif reflected_record_type
|
||||||
set_type(reflected_record_type)
|
set_type(reflected_record_type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_key_transform(input)
|
def run_key_transform(input)
|
||||||
if self.transform_method.present?
|
if transform_method.present?
|
||||||
input.to_s.send(*@transform_method).to_sym
|
input.to_s.send(*@transform_method).to_sym
|
||||||
else
|
else
|
||||||
input.to_sym
|
input.to_sym
|
||||||
@ -181,7 +172,7 @@ module FastJsonapi
|
|||||||
|
|
||||||
def cache_options(cache_options)
|
def cache_options(cache_options)
|
||||||
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
||||||
if !cache_options.key?(:store)
|
unless cache_options.key?(:store)
|
||||||
# fall back to old, deprecated behaviour because no store was passed.
|
# fall back to old, deprecated behaviour because no store was passed.
|
||||||
# we assume the user explicitly wants new behaviour if he passed a
|
# we assume the user explicitly wants new behaviour if he passed a
|
||||||
# store because this is the new syntax.
|
# store because this is the new syntax.
|
||||||
@ -211,7 +202,7 @@ module FastJsonapi
|
|||||||
def attributes(*attributes_list, &block)
|
def attributes(*attributes_list, &block)
|
||||||
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
||||||
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
||||||
self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
|
self.attributes_to_serialize = {} if attributes_to_serialize.nil?
|
||||||
|
|
||||||
# to support calling `attribute` with a lambda, e.g `attribute :key, ->(object) { ... }`
|
# to support calling `attribute` with a lambda, e.g `attribute :key, ->(object) { ... }`
|
||||||
block = attributes_list.pop if attributes_list.last.is_a?(Proc)
|
block = attributes_list.pop if attributes_list.last.is_a?(Proc)
|
||||||
@ -234,12 +225,14 @@ module FastJsonapi
|
|||||||
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
||||||
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
||||||
|
|
||||||
if !relationship.cached
|
# TODO: Remove this undocumented option.
|
||||||
self.uncachable_relationships_to_serialize[relationship.name] = relationship
|
# Delegate the caching to the serializer exclusively.
|
||||||
|
if relationship.cached
|
||||||
|
cachable_relationships_to_serialize[relationship.name] = relationship
|
||||||
else
|
else
|
||||||
self.cachable_relationships_to_serialize[relationship.name] = relationship
|
uncachable_relationships_to_serialize[relationship.name] = relationship
|
||||||
end
|
end
|
||||||
self.relationships_to_serialize[relationship.name] = relationship
|
relationships_to_serialize[relationship.name] = relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_many(relationship_name, options = {}, &block)
|
def has_many(relationship_name, options = {}, &block)
|
||||||
@ -265,11 +258,9 @@ module FastJsonapi
|
|||||||
name = base_key.to_sym
|
name = base_key.to_sym
|
||||||
if relationship_type == :has_many
|
if relationship_type == :has_many
|
||||||
base_serialization_key = base_key.to_s.singularize
|
base_serialization_key = base_key.to_s.singularize
|
||||||
base_key_sym = base_serialization_key.to_sym
|
|
||||||
id_postfix = '_ids'
|
id_postfix = '_ids'
|
||||||
else
|
else
|
||||||
base_serialization_key = base_key
|
base_serialization_key = base_key
|
||||||
base_key_sym = name
|
|
||||||
id_postfix = '_id'
|
id_postfix = '_id'
|
||||||
end
|
end
|
||||||
polymorphic = fetch_polymorphic_option(options)
|
polymorphic = fetch_polymorphic_option(options)
|
||||||
@ -294,6 +285,7 @@ module FastJsonapi
|
|||||||
polymorphic: polymorphic,
|
polymorphic: polymorphic,
|
||||||
conditional_proc: options[:if],
|
conditional_proc: options[:if],
|
||||||
transform_method: @transform_method,
|
transform_method: @transform_method,
|
||||||
|
meta: options[:meta],
|
||||||
links: options[:links],
|
links: options[:links],
|
||||||
lazy_load_data: options[:lazy_load_data]
|
lazy_load_data: options[:lazy_load_data]
|
||||||
)
|
)
|
||||||
@ -309,14 +301,14 @@ module FastJsonapi
|
|||||||
|
|
||||||
def serializer_for(name)
|
def serializer_for(name)
|
||||||
namespace = self.name.gsub(/()?\w+Serializer$/, '')
|
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
|
serializer_class_name = namespace + serializer_name
|
||||||
begin
|
begin
|
||||||
return serializer_class_name.constantize
|
serializer_class_name.constantize
|
||||||
rescue NameError
|
rescue NameError
|
||||||
raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " +
|
raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
|
||||||
"Attempted to find '#{serializer_class_name}'. " +
|
"Attempted to find '#{serializer_class_name}'. " \
|
||||||
"Consider specifying the serializer directly through options[:serializer]."
|
'Consider specifying the serializer directly through options[:serializer].'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -324,19 +316,20 @@ module FastJsonapi
|
|||||||
option = options[:polymorphic]
|
option = options[:polymorphic]
|
||||||
return false unless option.present?
|
return false unless option.present?
|
||||||
return option if option.respond_to? :keys
|
return option if option.respond_to? :keys
|
||||||
|
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|
||||||
# def link(link_name, link_method_name = nil, &block)
|
# def link(link_name, link_method_name = nil, &block)
|
||||||
def link(*params, &block)
|
def link(*params, &block)
|
||||||
self.data_links = {} if self.data_links.nil?
|
self.data_links = {} if data_links.nil?
|
||||||
|
|
||||||
options = params.last.is_a?(Hash) ? params.pop : {}
|
options = params.last.is_a?(Hash) ? params.pop : {}
|
||||||
link_name = params.first
|
link_name = params.first
|
||||||
link_method_name = params[-1]
|
link_method_name = params[-1]
|
||||||
key = run_key_transform(link_name)
|
key = run_key_transform(link_name)
|
||||||
|
|
||||||
self.data_links[key] = Link.new(
|
data_links[key] = Link.new(
|
||||||
key: key,
|
key: key,
|
||||||
method: block || link_method_name,
|
method: block || link_method_name,
|
||||||
options: options
|
options: options
|
||||||
@ -346,20 +339,11 @@ module FastJsonapi
|
|||||||
def validate_includes!(includes)
|
def validate_includes!(includes)
|
||||||
return if includes.blank?
|
return if includes.blank?
|
||||||
|
|
||||||
includes.each do |include_item|
|
parse_includes_list(includes).each_key do |include_item|
|
||||||
klass = self
|
relationship_to_include = relationships_to_serialize[include_item]
|
||||||
parse_include_item(include_item).each do |parsed_include|
|
raise(JSONAPI::Serializer::UnsupportedIncludeError.new(include_item, name)) unless relationship_to_include
|
||||||
relationships_to_serialize = klass.relationships_to_serialize || {}
|
|
||||||
relationship_to_include = relationships_to_serialize[parsed_include]
|
relationship_to_include.static_serializer # called for a side-effect to check for a known serializer class.
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module FastJsonapi
|
module FastJsonapi
|
||||||
class Relationship
|
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, :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, :meta, :lazy_load_data
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
owner:,
|
owner:,
|
||||||
@ -12,11 +12,12 @@ module FastJsonapi
|
|||||||
object_block:,
|
object_block:,
|
||||||
serializer:,
|
serializer:,
|
||||||
relationship_type:,
|
relationship_type:,
|
||||||
cached: false,
|
|
||||||
polymorphic:,
|
polymorphic:,
|
||||||
conditional_proc:,
|
conditional_proc:,
|
||||||
transform_method:,
|
transform_method:,
|
||||||
links:,
|
links:,
|
||||||
|
meta:,
|
||||||
|
cached: false,
|
||||||
lazy_load_data: false
|
lazy_load_data: false
|
||||||
)
|
)
|
||||||
@owner = owner
|
@owner = owner
|
||||||
@ -33,6 +34,7 @@ module FastJsonapi
|
|||||||
@conditional_proc = conditional_proc
|
@conditional_proc = conditional_proc
|
||||||
@transform_method = transform_method
|
@transform_method = transform_method
|
||||||
@links = links || {}
|
@links = links || {}
|
||||||
|
@meta = meta || {}
|
||||||
@lazy_load_data = lazy_load_data
|
@lazy_load_data = lazy_load_data
|
||||||
@record_types_for = {}
|
@record_types_for = {}
|
||||||
@serializers_for_name = {}
|
@serializers_for_name = {}
|
||||||
@ -43,15 +45,16 @@ module FastJsonapi
|
|||||||
empty_case = relationship_type == :has_many ? [] : nil
|
empty_case = relationship_type == :has_many ? [] : nil
|
||||||
|
|
||||||
output_hash[key] = {}
|
output_hash[key] = {}
|
||||||
unless (lazy_load_data && !included)
|
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case unless lazy_load_data && !included
|
||||||
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case
|
|
||||||
end
|
add_meta_hash(record, serialization_params, output_hash) if meta.present?
|
||||||
add_links_hash(record, serialization_params, output_hash) if links.present?
|
add_links_hash(record, serialization_params, output_hash) if links.present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_associated_object(record, params)
|
def fetch_associated_object(record, params)
|
||||||
return FastJsonapi.call_proc(object_block, record, params) unless object_block.nil?
|
return FastJsonapi.call_proc(object_block, record, params) unless object_block.nil?
|
||||||
|
|
||||||
record.send(object_method_name)
|
record.send(object_method_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -64,8 +67,9 @@ module FastJsonapi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def serializer_for(record, serialization_params)
|
def serializer_for(record, serialization_params)
|
||||||
|
# TODO: Remove this, dead code...
|
||||||
if @static_serializer
|
if @static_serializer
|
||||||
return @static_serializer
|
@static_serializer
|
||||||
|
|
||||||
elsif polymorphic
|
elsif polymorphic
|
||||||
name = polymorphic[record.class] if polymorphic.is_a?(Hash)
|
name = polymorphic[record.class] if polymorphic.is_a?(Hash)
|
||||||
@ -79,6 +83,7 @@ module FastJsonapi
|
|||||||
serializer_for_name(record.class.name)
|
serializer_for_name(record.class.name)
|
||||||
|
|
||||||
else
|
else
|
||||||
|
# TODO: Remove this, dead code...
|
||||||
raise "Unknown serializer for object #{record.inspect}"
|
raise "Unknown serializer for object #{record.inspect}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -102,9 +107,11 @@ module FastJsonapi
|
|||||||
|
|
||||||
return unless associated_object = fetch_associated_object(record, params)
|
return unless associated_object = fetch_associated_object(record, params)
|
||||||
|
|
||||||
|
if associated_object.respond_to? :map
|
||||||
return associated_object.map do |object|
|
return associated_object.map do |object|
|
||||||
id_hash_from_record object, params
|
id_hash_from_record object, params
|
||||||
end if associated_object.respond_to? :map
|
end
|
||||||
|
end
|
||||||
|
|
||||||
id_hash_from_record associated_object, params
|
id_hash_from_record associated_object, params
|
||||||
end
|
end
|
||||||
@ -116,6 +123,7 @@ module FastJsonapi
|
|||||||
|
|
||||||
def ids_hash(ids, record_type)
|
def ids_hash(ids, record_type)
|
||||||
return ids.map { |id| id_hash(id, record_type) } if ids.respond_to? :map
|
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
|
id_hash(ids, record_type) # ids variable is just a single id here
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -131,24 +139,33 @@ module FastJsonapi
|
|||||||
if object_block.present?
|
if object_block.present?
|
||||||
object = FastJsonapi.call_proc(object_block, record, params)
|
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.map { |item| item.public_send(id_method_name) } if object.respond_to? :map
|
||||||
|
|
||||||
return object.try(id_method_name)
|
return object.try(id_method_name)
|
||||||
end
|
end
|
||||||
record.public_send(id_method_name)
|
record.public_send(id_method_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_links_hash(record, params, output_hash)
|
def add_links_hash(record, params, output_hash)
|
||||||
if links.is_a?(Symbol)
|
output_hash[key][:links] = if links.is_a?(Symbol)
|
||||||
output_hash[key][:links] = record.public_send(links)
|
record.public_send(links)
|
||||||
else
|
else
|
||||||
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
|
links.each_with_object({}) do |(key, method), hash|
|
||||||
Link.new(key: key, method: method).serialize(record, params, hash)\
|
Link.new(key: key, method: method).serialize(record, params, hash)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_meta_hash(record, params, output_hash)
|
||||||
|
output_hash[key][:meta] = if meta.is_a?(Proc)
|
||||||
|
FastJsonapi.call_proc(meta, record, params)
|
||||||
|
else
|
||||||
|
meta
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def run_key_transform(input)
|
def run_key_transform(input)
|
||||||
if self.transform_method.present?
|
if transform_method.present?
|
||||||
input.to_s.send(*self.transform_method).to_sym
|
input.to_s.send(*transform_method).to_sym
|
||||||
else
|
else
|
||||||
input.to_sym
|
input.to_sym
|
||||||
end
|
end
|
||||||
@ -156,6 +173,7 @@ module FastJsonapi
|
|||||||
|
|
||||||
def initialize_static_serializer
|
def initialize_static_serializer
|
||||||
return if @initialized_static_serializer
|
return if @initialized_static_serializer
|
||||||
|
|
||||||
@static_serializer = compute_static_serializer
|
@static_serializer = compute_static_serializer
|
||||||
@static_record_type = compute_static_record_type
|
@static_record_type = compute_static_record_type
|
||||||
@initialized_static_serializer = true
|
@initialized_static_serializer = true
|
||||||
@ -199,14 +217,20 @@ module FastJsonapi
|
|||||||
def record_type_for(record, serialization_params)
|
def record_type_for(record, serialization_params)
|
||||||
# if the record type is static, return it
|
# if the record type is static, return it
|
||||||
return @static_record_type if @static_record_type
|
return @static_record_type if @static_record_type
|
||||||
|
|
||||||
# if not, use the record type of the serializer, and memoize the transformed version
|
# if not, use the record type of the serializer, and memoize the transformed version
|
||||||
serializer = serializer_for(record, serialization_params)
|
serializer = serializer_for(record, serialization_params)
|
||||||
@record_types_for[serializer] ||= run_key_transform(serializer.record_type)
|
@record_types_for[serializer] ||= run_key_transform(serializer.record_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
def compute_static_record_type
|
def compute_static_record_type
|
||||||
return run_key_transform(record_type) if record_type
|
if polymorphic
|
||||||
return run_key_transform(@static_serializer.record_type) if @static_serializer
|
nil
|
||||||
|
elsif record_type
|
||||||
|
run_key_transform(record_type)
|
||||||
|
elsif @static_serializer
|
||||||
|
run_key_transform(@static_serializer.record_type)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,10 +10,10 @@ module FastJsonapi
|
|||||||
|
|
||||||
def serialize(record, serialization_params, output_hash)
|
def serialize(record, serialization_params, output_hash)
|
||||||
if conditionally_allowed?(record, serialization_params)
|
if conditionally_allowed?(record, serialization_params)
|
||||||
output_hash[key] = if method.is_a?(Proc)
|
if method.is_a?(Proc)
|
||||||
FastJsonapi.call_proc(method, record, serialization_params)
|
output_hash[key] = FastJsonapi.call_proc(method, record, serialization_params)
|
||||||
else
|
else
|
||||||
record.public_send(method)
|
output_hash[key] = record.public_send(method)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'active_support'
|
||||||
require 'active_support/concern'
|
require 'active_support/concern'
|
||||||
|
require 'digest/sha1'
|
||||||
|
|
||||||
module FastJsonapi
|
module FastJsonapi
|
||||||
MandatoryField = Class.new(StandardError)
|
MandatoryField = Class.new(StandardError)
|
||||||
@ -66,63 +68,113 @@ module FastJsonapi
|
|||||||
|
|
||||||
def record_hash(record, fieldset, includes_list, params = {})
|
def record_hash(record, fieldset, includes_list, params = {})
|
||||||
if cache_store_instance
|
if cache_store_instance
|
||||||
record_hash = cache_store_instance.fetch(record, **cache_store_options) do
|
cache_opts = record_cache_options(cache_store_options, fieldset, includes_list, params)
|
||||||
|
record_hash = cache_store_instance.fetch(record, **cache_opts) do
|
||||||
temp_hash = id_hash(id_from_record(record, params), record_type, true)
|
temp_hash = id_hash(id_from_record(record, params), record_type, true)
|
||||||
temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
|
temp_hash[: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[: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[:links] = links_hash(record, params) if data_links.present?
|
||||||
temp_hash
|
temp_hash
|
||||||
end
|
end
|
||||||
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, 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
|
else
|
||||||
record_hash = id_hash(id_from_record(record, params), record_type, true)
|
record_hash = id_hash(id_from_record(record, params), record_type, true)
|
||||||
record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
|
record_hash[: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[: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[:links] = links_hash(record, params) if data_links.present?
|
||||||
|
end
|
||||||
|
|
||||||
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
|
record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
|
||||||
record_hash
|
record_hash
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
|
options[:namespace] = "#{options[:namespace]}-fieldset:#{fieldset_key}"
|
||||||
|
options
|
||||||
|
end
|
||||||
|
# rubocop:enable Lint/UnusedMethodArgument
|
||||||
|
|
||||||
def id_from_record(record, params)
|
def id_from_record(record, params)
|
||||||
return FastJsonapi.call_proc(record_id, record, params) if record_id.is_a?(Proc)
|
return FastJsonapi.call_proc(record_id, record, params) if record_id.is_a?(Proc)
|
||||||
return record.send(record_id) if record_id
|
return record.send(record_id) if record_id
|
||||||
raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
|
raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
|
||||||
|
|
||||||
record.id
|
record.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_include_item(include_item)
|
# It chops out the root association (first part) from each include.
|
||||||
return [include_item.to_sym] unless include_item.to_s.include?('.')
|
#
|
||||||
|
# It keeps an unique list and collects all of the rest of the include
|
||||||
include_item.to_s.split('.').map!(&:to_sym)
|
# 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
|
end
|
||||||
|
|
||||||
def remaining_items(items)
|
|
||||||
return unless items.size > 1
|
|
||||||
|
|
||||||
[items[1..-1].join('.').to_sym]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# includes handler
|
# includes handler
|
||||||
def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {})
|
def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {})
|
||||||
return unless includes_list.present?
|
return unless includes_list.present?
|
||||||
|
return [] unless relationships_to_serialize
|
||||||
|
|
||||||
includes_list.sort.each_with_object([]) do |include_item, included_records|
|
includes_list = parse_includes_list(includes_list)
|
||||||
items = parse_include_item(include_item)
|
|
||||||
remaining_items = remaining_items(items)
|
|
||||||
|
|
||||||
items.each do |item|
|
includes_list.each_with_object([]) do |include_item, included_records|
|
||||||
next unless relationships_to_serialize && relationships_to_serialize[item]
|
relationship_item = relationships_to_serialize[include_item.first]
|
||||||
relationship_item = relationships_to_serialize[item]
|
|
||||||
next unless relationship_item.include_relationship?(record, params)
|
|
||||||
relationship_type = relationship_item.relationship_type
|
|
||||||
|
|
||||||
included_objects = relationship_item.fetch_associated_object(record, params)
|
next unless relationship_item&.include_relationship?(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_serializer = relationship_item.static_serializer
|
||||||
static_record_type = relationship_item.static_record_type
|
static_record_type = relationship_item.static_record_type
|
||||||
@ -131,15 +183,15 @@ module FastJsonapi
|
|||||||
serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
|
serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
|
||||||
record_type = static_record_type || serializer.record_type
|
record_type = static_record_type || serializer.record_type
|
||||||
|
|
||||||
if remaining_items.present?
|
if include_item.last.any?
|
||||||
serializer_records = serializer.get_included_records(inc_obj, remaining_items, known_included_objects, fieldsets, params)
|
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?
|
included_records.concat(serializer_records) unless serializer_records.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}"
|
code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}"
|
||||||
next if known_included_objects.key?(code)
|
next if known_included_objects.include?(code)
|
||||||
|
|
||||||
known_included_objects[code] = inc_obj
|
known_included_objects << code
|
||||||
|
|
||||||
included_records << serializer.record_hash(inc_obj, fieldsets[record_type], includes_list, params)
|
included_records << serializer.record_hash(inc_obj, fieldsets[record_type], includes_list, params)
|
||||||
end
|
end
|
||||||
@ -148,4 +200,3 @@ module FastJsonapi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
module FastJsonapi
|
module FastJsonapi
|
||||||
VERSION = '1.7.0'
|
VERSION = JSONAPI::Serializer::VERSION
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<% module_namespacing do -%>
|
<% module_namespacing do -%>
|
||||||
class <%= class_name %>Serializer
|
class <%= class_name %>Serializer
|
||||||
include FastJsonapi::ObjectSerializer
|
include JSONAPI::Serializer
|
||||||
attributes <%= attributes_names.join(", ") %>
|
attributes <%= attributes_names.join(", ") %>
|
||||||
end
|
end
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
12
lib/jsonapi/serializer.rb
Normal file
12
lib/jsonapi/serializer.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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
|
21
lib/jsonapi/serializer/errors.rb
Normal file
21
lib/jsonapi/serializer/errors.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
28
lib/jsonapi/serializer/instrumentation.rb
Normal file
28
lib/jsonapi/serializer/instrumentation.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
|
5
lib/jsonapi/serializer/version.rb
Normal file
5
lib/jsonapi/serializer/version.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module JSONAPI
|
||||||
|
module Serializer
|
||||||
|
VERSION = '2.2.0'.freeze
|
||||||
|
end
|
||||||
|
end
|
40
spec/fixtures/_user.rb
vendored
Normal file
40
spec/fixtures/_user.rb
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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
Normal file
80
spec/fixtures/actor.rb
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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
Normal file
127
spec/fixtures/movie.rb
vendored
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
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
|
63
spec/integration/attributes_fields_spec.rb
Normal file
63
spec/integration/attributes_fields_spec.rb
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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
|
80
spec/integration/caching_spec.rb
Normal file
80
spec/integration/caching_spec.rb
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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
|
25
spec/integration/errors_spec.rb
Normal file
25
spec/integration/errors_spec.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
29
spec/integration/instrumentation_spec.rb
Normal file
29
spec/integration/instrumentation_spec.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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
|
19
spec/integration/key_transform_spec.rb
Normal file
19
spec/integration/key_transform_spec.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
|
47
spec/integration/links_spec.rb
Normal file
47
spec/integration/links_spec.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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
|
25
spec/integration/meta_spec.rb
Normal file
25
spec/integration/meta_spec.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
146
spec/integration/relationships_spec.rb
Normal file
146
spec/integration/relationships_spec.rb
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
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
|
@ -1,155 +0,0 @@
|
|||||||
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
|
|
@ -1,73 +0,0 @@
|
|||||||
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
|
|
@ -1,57 +0,0 @@
|
|||||||
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
|
|
@ -1,83 +0,0 @@
|
|||||||
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
|
|
@ -1,14 +0,0 @@
|
|||||||
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
|
|
@ -1,118 +0,0 @@
|
|||||||
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
|
|
@ -1,107 +0,0 @@
|
|||||||
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
|
|
@ -1,690 +0,0 @@
|
|||||||
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
|
|
@ -1,81 +0,0 @@
|
|||||||
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
|
|
@ -1,189 +0,0 @@
|
|||||||
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
|
|
@ -1,217 +0,0 @@
|
|||||||
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
|
|
@ -1,99 +0,0 @@
|
|||||||
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
|
|
@ -1,112 +0,0 @@
|
|||||||
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
|
|
@ -1,63 +0,0 @@
|
|||||||
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
|
|
@ -1,107 +0,0 @@
|
|||||||
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
|
|
@ -1,593 +0,0 @@
|
|||||||
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
|
|
@ -1,41 +0,0 @@
|
|||||||
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
|
|
@ -1,72 +0,0 @@
|
|||||||
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
|
|
@ -1,155 +0,0 @@
|
|||||||
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
|
|
@ -1,87 +0,0 @@
|
|||||||
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
|
|
@ -1,97 +0,0 @@
|
|||||||
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
|
|
@ -1,123 +0,0 @@
|
|||||||
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
|
|
@ -1,116 +0,0 @@
|
|||||||
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
|
|
@ -1,116 +0,0 @@
|
|||||||
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
|
|
@ -1,112 +0,0 @@
|
|||||||
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
|
|
@ -1,511 +0,0 @@
|
|||||||
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
|
|
@ -1,18 +0,0 @@
|
|||||||
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,21 +1,30 @@
|
|||||||
require 'active_record'
|
require 'simplecov'
|
||||||
require 'fast_jsonapi'
|
|
||||||
require 'rspec-benchmark'
|
|
||||||
require 'byebug'
|
|
||||||
require 'active_model_serializers'
|
|
||||||
require 'oj'
|
|
||||||
require 'jsonapi/serializable'
|
|
||||||
require 'jsonapi-serializers'
|
|
||||||
|
|
||||||
Dir[File.dirname(__FILE__) + '/shared/contexts/*.rb'].each {|file| require file }
|
SimpleCov.start do
|
||||||
Dir[File.dirname(__FILE__) + '/shared/examples/*.rb'].each {|file| require file }
|
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 'byebug'
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
|
Dir[File.expand_path('spec/fixtures/*.rb')].sort.each { |f| require f }
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.include RSpec::Benchmark::Matchers
|
config.include JSONAPI::RSpec
|
||||||
config.filter_run_excluding performance: ENV['BENCHMARK'].blank?
|
|
||||||
end
|
|
||||||
|
|
||||||
Oj.optimize_rails
|
config.mock_with :rspec
|
||||||
ActiveModel::Serializer.config.adapter = :json_api
|
config.filter_run_when_matching :focus
|
||||||
ActiveModel::Serializer.config.key_transform = :underscore
|
config.disable_monkey_patching!
|
||||||
ActiveModelSerializers.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new('/dev/null'))
|
|
||||||
|
config.expect_with :rspec do |c|
|
||||||
|
c.syntax = :expect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user