Compare commits

...

37 Commits

Author SHA1 Message Date
Clemens Kofler
7db80f673d Clarify README wording related to contributions 2022-04-04 18:52:58 +01:00
Peter Goldstein
bcee2b597b Add Ruby 3.1 to CI
Also fixes the unquoted 3.0, which needs to be quoted to avoid truncation
Addresses a few Rubocop issues and an active_support require issue on Rails 7 w/Zeitwerk
2022-02-02 21:40:34 +00:00
Igor Victor
44cf8495ea Update ci.yml 2021-04-12 11:50:08 +01:00
Stas SUȘCOV
37235057df
Version bump. 2021-03-11 18:35:11 +00:00
Stas SUȘCOV
c21f9def8f Updated the changelog. 2021-03-11 18:33:35 +00:00
Stas SUȘCOV
8b74954478 Added ruby v3 to CI matrix. 2021-03-11 18:33:35 +00:00
Stas SUȘCOV
ef93f7f358 Make rubocop happy. 2021-03-11 18:33:35 +00:00
HubertVonHerbert
c5eb1ce27c
Fix Ruby3 compatibility issue with Procs (#160)
* Fix Ruby3 compatibility issue with Procs

* Fix rubocop complaints

* Remove ruby 3 from CI actions

* Simplify check for &:foo procs
2021-03-11 18:29:59 +00:00
Ryan Romanchuk
a25d415b4d Fix most likely copy/paste error
this lambda yielding a bool for `belongs_to` might confuse readers or conflate the :if block

which would probably look something like this 

```ruby
belongs_to :primary_agent, if: proc { |movie, params| params[:current_user].present? } do |movie, params|
    # in here, params is a hash containing the `:current_user` key
    params[:current_user]
  end
```
2021-02-14 22:17:08 +00:00
Stas SUȘCOV
c3376037e7 Let everyone know there's a WIP branch for v3. 2021-01-11 11:11:24 +00:00
Yaroslav Kasperovych
963cd77900 Fix require clause in fastjson migration guide
Seems like the require clause should be different as it produces an error if used as it is right now.
2020-11-06 10:15:26 +00:00
Tony Dehnke
98dd59884c Fix namespace name per comment in #139 2020-10-26 12:01:52 +00:00
Guillaume Briday
b7e8a30833 Fix typo in readme 2020-10-25 18:13:08 +00:00
Guillaume Briday
f4ed4f0440 Adding migration section 2020-10-25 17:23:20 +00:00
Jorge Garcia
88061bcfac Add deserialization options on Readme 2020-10-08 10:21:20 +01:00
nattfodd
f8255771dc Raise FastJsonapi scoped error in case of unsupported include 2020-09-18 13:12:41 +03:00
Donatas Povilaitis
1bcf8d2cb5
Do not add empty relationships key (#116) 2020-09-01 20:44:08 +01:00
Stas SUȘCOV
0b819e09b9
Version bump. 2020-08-30 16:21:07 +01:00
Stas SUȘCOV
72f3ae64dd Remove skylight and old instrumentation support. 2020-08-30 15:57:07 +01:00
Stas SUȘCOV
eae66a9610 Added new instrumentation support. 2020-08-30 15:57:07 +01:00
Stas SUȘCOV
5538555537 Detect enumerables strictly. Closes #112 2020-08-27 11:51:38 +01:00
Stas SUȘCOV
4d1a764abc Updated cache options helper.
Allow extra arguments to improve the API interface.
2020-08-27 11:51:15 +01:00
Kapil Sachdev
c32358ecf5 fix: Rename FastJsonapi::ObjectSerializer to JSONAPI::Serializer
As the project has been renamed, its better to reflect it in the source 
code as well.
JSONAPI::Serializer is evaluated from FastJsonapi::ObjectSerializer so 
this change  probably will go unnoticed in gem usage.
2020-08-27 10:57:15 +01:00
Kapil Sachdev
f56a354860
Update links to use https (#114) 2020-08-25 18:50:24 +01:00
Matthew Newell
8401d16c2e Fix cache keys to prevent fieldset caching errors
This change alters the cache namespace prior to retrieving cached record
data to ensure that different fieldsets are given different cache keys.

Previously, all cache keys for the same record would be specified
identically, leading to a situation where the fieldset would be ignored
if record caching is enabled.

Fixes #90.
2020-08-11 12:54:57 +01:00
Stas SUȘCOV
1ce4677a22
Docs for how the new parsing of includes works. 2020-08-07 12:45:13 +01:00
Nathaniel Bibler
2a946c7723 Use Ruby's safe navigation for Rubocop 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
51b319def5 Remove unused variable 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
593e8ea4e6 Reduce relationship lookups 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
e2ac01f98a Use a Set for managing uniqueness behavior 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
a4bd5a1edc Remove exponential increase in nested includes 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
f62a5bf162 Remove a looped conditional and #blank? use 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
430a5ca2ac Remove unnecessary sort 2020-08-07 12:36:29 +01:00
Nathaniel Bibler
84cd54bd0e Relocate highly-repetitive relationship check 2020-08-07 12:36:29 +01:00
David Angulo
0e051fcad2 Add example for custom attribute with condtion 2020-08-07 12:35:11 +01:00
Semyon Pupkov
eab3dbdb27 Use jsonapi-rspec version 2020-07-10 09:14:11 +01:00
mperice
dd7f5ba415
Add optional meta field to relationships (#99) (#100)
* Add optional meta field to relationships (#99)

* Fix Rubocop's warnings.

* Minor syntax corrections.
2020-07-07 15:22:25 +01:00
38 changed files with 519 additions and 211 deletions

View File

@ -4,16 +4,16 @@ 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 }}

2
.gitignore vendored
View File

@ -41,7 +41,7 @@ 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 builds

View File

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

View File

@ -5,6 +5,38 @@ 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] ## [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 ### Changed
- Remove `ObjectSerializer#serialized_json` (#91) - Remove `ObjectSerializer#serialized_json` (#91)

View File

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

View File

@ -1,6 +1,6 @@
Apache License 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,

160
README.md
View File

@ -1,6 +1,12 @@
# JSON:API Serialization Library # JSON:API Serialization Library
A 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 Previously this project was called **fast_jsonapi**, we forked the project
and renamed it to **jsonapi/serializer** in order to keep it alive. and renamed it to **jsonapi/serializer** in order to keep it alive.
@ -38,6 +44,9 @@ article in the `docs` folder for any questions related to methodology.
* [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)
@ -257,18 +266,18 @@ class MovieSerializer
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
@ -317,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]`.
@ -392,6 +418,25 @@ So for the example above it will call the cache instance like this:
Rails.cache.fetch(record, namespace: 'jsonapi-serializer', 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
@ -421,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
@ -452,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
# ... # ...
@ -619,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
@ -658,6 +701,61 @@ tests. To run tests use the following command:
rspec rspec
``` ```
## Deserialization
We currently do not support deserialization, but we recommend to use any of the next gems:
### [JSONAPI.rb](https://github.com/stas/jsonapi.rb)
This gem provides the next features alongside deserialization:
- Collection meta
- Error handling
- Includes and sparse fields
- Filtering and sorting
- Pagination
## Migrating from Netflix/fast_jsonapi
If you come from [Netflix/fast_jsonapi](https://github.com/Netflix/fast_jsonapi), here is the instructions to switch.
### Modify your Gemfile
```diff
- gem 'fast_jsonapi'
+ gem 'jsonapi-serializer'
```
### Replace all constant references
```diff
class MovieSerializer
- include FastJsonapi::ObjectSerializer
+ include JSONAPI::Serializer
end
```
### Replace removed methods
```diff
- json_string = MovieSerializer.new(movie).serialized_json
+ json_string = MovieSerializer.new(movie).serializable_hash.to_json
```
### Replace require references
```diff
- require 'fast_jsonapi'
+ require 'jsonapi/serializer'
```
### Update your cache options
See [docs](https://github.com/jsonapi-serializer/jsonapi-serializer#caching).
```diff
- cache_options enabled: true, cache_length: 12.hours
+ cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
```
## Contributing ## Contributing
Please follow the instructions we provide as part of the issue and Please follow the instructions we provide as part of the issue and
@ -665,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.

View File

@ -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)

View File

@ -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)

View File

@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
gem.summary = 'Fast JSON:API serialization library' gem.summary = 'Fast JSON:API serialization library'
gem.description = 'Fast, simple and easy to use '\ gem.description = 'Fast, simple and easy to use '\
'JSON:API serialization library (also known as fast_jsonapi).' 'JSON:API serialization library (also known as fast_jsonapi).'
gem.homepage = 'http://github.com/jsonapi-serializer/jsonapi-serializer' gem.homepage = 'https://github.com/jsonapi-serializer/jsonapi-serializer'
gem.licenses = ['Apache-2.0'] gem.licenses = ['Apache-2.0']
gem.files = Dir['lib/**/*'] gem.files = Dir['lib/**/*']
gem.require_paths = ['lib'] gem.require_paths = ['lib']
@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency('bundler') gem.add_development_dependency('bundler')
gem.add_development_dependency('byebug') gem.add_development_dependency('byebug')
gem.add_development_dependency('ffaker') gem.add_development_dependency('ffaker')
gem.add_development_dependency('jsonapi-rspec') gem.add_development_dependency('jsonapi-rspec', '>= 0.0.5')
gem.add_development_dependency('rake') gem.add_development_dependency('rake')
gem.add_development_dependency('rspec') gem.add_development_dependency('rspec')
gem.add_development_dependency('rubocop') gem.add_development_dependency('rubocop')
@ -33,4 +33,5 @@ Gem::Specification.new do |gem|
gem.add_development_dependency('rubocop-rspec') gem.add_development_dependency('rubocop-rspec')
gem.add_development_dependency('simplecov') gem.add_development_dependency('simplecov')
gem.add_development_dependency('sqlite3') gem.add_development_dependency('sqlite3')
gem.metadata['rubygems_mfa_required'] = 'true'
end end

View File

@ -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)

View File

@ -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

View File

@ -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'
)

View File

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

View File

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

View File

@ -1,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.')

View File

@ -1,7 +0,0 @@
require 'skylight'
SKYLIGHT_NORMALIZER_BASE_CLASS = begin
::Skylight::Core::Normalizers::Normalizer
rescue NameError
::Skylight::Normalizers::Normalizer
end

View File

@ -1,20 +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

View File

@ -1,20 +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

View File

@ -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,7 +35,9 @@ 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
@ -80,7 +81,7 @@ 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]
@ -105,13 +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?
@ -130,9 +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'))
name.split('::').last.chomp('Serializer').underscore.to_sym if name&.end_with?('Serializer')
end
end end
def set_key_transform(transform_name) def set_key_transform(transform_name)
@ -225,10 +227,10 @@ module FastJsonapi
# TODO: Remove this undocumented option. # TODO: Remove this undocumented option.
# Delegate the caching to the serializer exclusively. # Delegate the caching to the serializer exclusively.
if !relationship.cached if relationship.cached
uncachable_relationships_to_serialize[relationship.name] = relationship
else
cachable_relationships_to_serialize[relationship.name] = relationship cachable_relationships_to_serialize[relationship.name] = relationship
else
uncachable_relationships_to_serialize[relationship.name] = relationship
end end
relationships_to_serialize[relationship.name] = relationship relationships_to_serialize[relationship.name] = relationship
end end
@ -283,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]
) )
@ -298,7 +301,7 @@ 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
serializer_class_name.constantize serializer_class_name.constantize
@ -336,21 +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]
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
if relationship_to_include.static_serializer relationship_to_include.static_serializer # called for a side-effect to check for a known serializer class.
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

View File

@ -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 = {}
@ -44,6 +46,8 @@ module FastJsonapi
output_hash[key] = {} output_hash[key] = {}
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 unless lazy_load_data && !included
add_meta_hash(record, serialization_params, output_hash) if meta.present?
add_links_hash(record, serialization_params, output_hash) if links.present? add_links_hash(record, serialization_params, output_hash) if links.present?
end end
end end
@ -146,11 +150,19 @@ module FastJsonapi
record.public_send(links) record.public_send(links)
else else
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 transform_method.present? if transform_method.present?
input.to_s.send(*transform_method).to_sym input.to_s.send(*transform_method).to_sym

View File

@ -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,15 +68,15 @@ 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?
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?
@ -86,6 +88,37 @@ module FastJsonapi
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
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
@ -94,38 +127,54 @@ module FastJsonapi
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)
next unless relationship_item.include_relationship?(record, params)
relationship_type = relationship_item.relationship_type included_objects = Array(relationship_item.fetch_associated_object(record, params))
next if included_objects.empty?
included_objects = relationship_item.fetch_associated_object(record, params)
next if included_objects.blank?
included_objects = [included_objects] unless relationship_type == :has_many
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
@ -134,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
@ -151,4 +200,3 @@ module FastJsonapi
end end
end end
end end
end

View File

@ -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 -%>

View 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

View 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

View File

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

View File

@ -1,3 +1,6 @@
require 'active_support'
require 'active_support/cache'
class User class User
attr_accessor :uid, :first_name, :last_name, :email attr_accessor :uid, :first_name, :last_name, :email
@ -26,3 +29,12 @@ class UserSerializer
} }
end end
end end
module Cached
class UserSerializer < ::UserSerializer
cache_options(
store: ActiveSupport::Cache::MemoryStore.new,
namespace: 'test'
)
end
end

View File

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

View File

@ -26,7 +26,7 @@ class Movie
@url ||= FFaker::Internet.http_url @url ||= FFaker::Internet.http_url
return @url if obj.nil? return @url if obj.nil?
@url + '?' + obj.hash.to_s "#{@url}?#{obj.hash}"
end end
def owner=(ownr) def owner=(ownr)
@ -48,8 +48,9 @@ class MovieSerializer
set_type :movie set_type :movie
attribute :released_in_year, &:year
attributes :name attributes :name
attribute :release_year do |object| attribute :release_year do |object, _params|
object.year object.year
end end
@ -66,6 +67,7 @@ class MovieSerializer
has_many( has_many(
:actors, :actors,
meta: proc { |record, _| { count: record.actors.length } },
links: { links: {
actors_self: :url, actors_self: :url,
related: ->(obj) { obj.url(obj) } related: ->(obj) { obj.url(obj) }

View File

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

View File

@ -1,6 +1,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe FastJsonapi::ObjectSerializer do RSpec.describe JSONAPI::Serializer do
let(:actor) do let(:actor) do
faked = Actor.fake faked = Actor.fake
movie = Movie.fake movie = Movie.fake
@ -25,5 +25,56 @@ RSpec.describe FastJsonapi::ObjectSerializer do
cache_store.delete(actor.movies[0].owner, namespace: 'test') cache_store.delete(actor.movies[0].owner, namespace: 'test')
).to be(false) ).to be(false)
end 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
end end

View File

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

View 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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe FastJsonapi::ObjectSerializer do RSpec.describe JSONAPI::Serializer do
let(:movie) do let(:movie) do
mov = Movie.fake mov = Movie.fake
mov.actors = rand(2..5).times.map { Actor.fake } mov.actors = rand(2..5).times.map { Actor.fake }
@ -59,6 +59,13 @@ RSpec.describe FastJsonapi::ObjectSerializer do
) )
end 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 context 'with include' do
let(:params) do let(:params) do
{ include: [:actors] } { include: [:actors] }

View File

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