mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-24 14:56:50 +00:00
Merge branch 'master' into 0-10-stable
This commit is contained in:
commit
b48aeeef1e
@ -1,10 +1,13 @@
|
|||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.1
|
TargetRubyVersion: 2.1
|
||||||
Exclude:
|
Exclude:
|
||||||
- config/initializers/forbidden_yaml.rb
|
|
||||||
- !ruby/regexp /(vendor|bundle|bin|db|tmp)\/.*/
|
- !ruby/regexp /(vendor|bundle|bin|db|tmp)\/.*/
|
||||||
DisplayCopNames: true
|
DisplayCopNames: true
|
||||||
DisplayStyleGuide: true
|
DisplayStyleGuide: true
|
||||||
|
# https://github.com/bbatsov/rubocop/blob/master/manual/caching.md
|
||||||
|
# https://github.com/bbatsov/rubocop/blob/e8680418b351491e111a18cf5b453fc07a3c5239/config/default.yml#L60-L77
|
||||||
|
UseCache: true
|
||||||
|
CacheRootDirectory: tmp
|
||||||
|
|
||||||
Rails:
|
Rails:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,6 +1,6 @@
|
|||||||
## 0.10.x
|
## 0.10.x
|
||||||
|
|
||||||
### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...master)
|
### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.6...master)
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
@ -14,6 +14,20 @@ Fixes:
|
|||||||
|
|
||||||
Misc:
|
Misc:
|
||||||
|
|
||||||
|
### [v0.10.6 (2017-05-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...v0.10.6)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- [#1857](https://github.com/rails-api/active_model_serializers/pull/1857) JSON:API does not load belongs_to relation to get identifier id. (@bf4)
|
||||||
|
- [#2119](https://github.com/rails-api/active_model_serializers/pull/2119) JSON:API returns null resource object identifier when 'id' is null. (@bf4)
|
||||||
|
- [#2093](https://github.com/rails-api/active_model_serializers/pull/2093) undef problematic Serializer methods: display, select. (@bf4)
|
||||||
|
|
||||||
|
Misc:
|
||||||
|
|
||||||
|
- [#2104](https://github.com/rails-api/active_model_serializers/pull/2104) Documentation for serializers and rendering. (@cassidycodes)
|
||||||
|
- [#2081](https://github.com/rails-api/active_model_serializers/pull/2081) Documentation for `include` option in adapters. (@charlie-wasp)
|
||||||
|
- [#2120](https://github.com/rails-api/active_model_serializers/pull/2120) Documentation for association options: foreign_key, type, class_name, namespace. (@bf4)
|
||||||
|
|
||||||
### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5)
|
### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5)
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
@ -81,7 +95,7 @@ Misc:
|
|||||||
|
|
||||||
- [#1878](https://github.com/rails-api/active_model_serializers/pull/1878) Cache key generation for serializers now uses `ActiveSupport::Cache.expand_cache_key` instead of `Array#join` by default and is also overridable. This change should be backward-compatible. (@markiz)
|
- [#1878](https://github.com/rails-api/active_model_serializers/pull/1878) Cache key generation for serializers now uses `ActiveSupport::Cache.expand_cache_key` instead of `Array#join` by default and is also overridable. This change should be backward-compatible. (@markiz)
|
||||||
|
|
||||||
- [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@ScottKbka)
|
- [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@cassidycodes)
|
||||||
- [#1909](https://github.com/rails-api/active_model_serializers/pull/1909) Add documentation for relationship links. (@vasilakisfil, @NullVoxPopuli)
|
- [#1909](https://github.com/rails-api/active_model_serializers/pull/1909) Add documentation for relationship links. (@vasilakisfil, @NullVoxPopuli)
|
||||||
- [#1959](https://github.com/rails-api/active_model_serializers/pull/1959) Add documentation for root. (@shunsuke227ono)
|
- [#1959](https://github.com/rails-api/active_model_serializers/pull/1959) Add documentation for root. (@shunsuke227ono)
|
||||||
- [#1967](https://github.com/rails-api/active_model_serializers/pull/1967) Improve type method documentation. (@yukideluxe)
|
- [#1967](https://github.com/rails-api/active_model_serializers/pull/1967) Improve type method documentation. (@yukideluxe)
|
||||||
|
|||||||
@ -90,8 +90,8 @@ reading documentation for our `master`, which may include features that have not
|
|||||||
been released yet. Please see below for the documentation relevant to you.
|
been released yet. Please see below for the documentation relevant to you.
|
||||||
|
|
||||||
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
|
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
|
||||||
- [0.10.4 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.4)
|
- [0.10.6 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.6)
|
||||||
- [](http://www.rubydoc.info/gems/active_model_serializers/0.10.4)
|
- [](http://www.rubydoc.info/gems/active_model_serializers/0.10.6)
|
||||||
- [Guides](docs)
|
- [Guides](docs)
|
||||||
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
|
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
|
||||||
- [](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
|
- [](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
|
||||||
|
|||||||
31
Rakefile
31
Rakefile
@ -7,6 +7,7 @@ begin
|
|||||||
require 'simplecov'
|
require 'simplecov'
|
||||||
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
||||||
end
|
end
|
||||||
|
import('lib/tasks/rubocop.rake')
|
||||||
|
|
||||||
Bundler::GemHelper.install_tasks
|
Bundler::GemHelper.install_tasks
|
||||||
|
|
||||||
@ -30,36 +31,6 @@ namespace :yard do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
|
||||||
require 'rubocop'
|
|
||||||
require 'rubocop/rake_task'
|
|
||||||
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
|
||||||
else
|
|
||||||
Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
|
|
||||||
require 'rbconfig'
|
|
||||||
# https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2
|
|
||||||
windows_platforms = /(msdos|mswin|djgpp|mingw)/
|
|
||||||
if RbConfig::CONFIG['host_os'] =~ windows_platforms
|
|
||||||
desc 'No-op rubocop on Windows-- unsupported platform'
|
|
||||||
task :rubocop do
|
|
||||||
puts 'Skipping rubocop on Windows'
|
|
||||||
end
|
|
||||||
elsif defined?(::Rubinius)
|
|
||||||
desc 'No-op rubocop to avoid rbx segfault'
|
|
||||||
task :rubocop do
|
|
||||||
puts 'Skipping rubocop on rbx due to segfault'
|
|
||||||
puts 'https://github.com/rubinius/rubinius/issues/3499'
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
|
|
||||||
desc 'Execute rubocop'
|
|
||||||
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
||||||
task.options = ['--rails', '--display-cop-names', '--display-style-guide']
|
|
||||||
task.fail_on_error = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'rake/testtask'
|
require 'rake/testtask'
|
||||||
|
|
||||||
Rake::TestTask.new(:test) do |t|
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ Gem::Specification.new do |spec|
|
|||||||
spec.add_development_dependency 'bundler', '~> 1.6'
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
||||||
spec.add_development_dependency 'simplecov', '~> 0.11'
|
spec.add_development_dependency 'simplecov', '~> 0.11'
|
||||||
spec.add_development_dependency 'timecop', '~> 0.7'
|
spec.add_development_dependency 'timecop', '~> 0.7'
|
||||||
spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0']
|
spec.add_development_dependency 'grape', ['>= 0.13', '< 0.19.1']
|
||||||
spec.add_development_dependency 'json_schema'
|
spec.add_development_dependency 'json_schema'
|
||||||
spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0']
|
spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0']
|
||||||
end
|
end
|
||||||
|
|||||||
38
bin/rubocop
Executable file
38
bin/rubocop
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bin/rubocop [-A|-t|-h]
|
||||||
|
# bin/rubocop [file or path] [cli options]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# Autocorrect -A
|
||||||
|
# AutoGenConfig -t
|
||||||
|
# Usage -h,--help,help
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
-A)
|
||||||
|
echo "Rubocop autocorrect is ON" >&2
|
||||||
|
bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_correct
|
||||||
|
;;
|
||||||
|
|
||||||
|
-t)
|
||||||
|
echo "Rubocop is generating a new TODO" >&2
|
||||||
|
bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_gen_config
|
||||||
|
;;
|
||||||
|
|
||||||
|
-h|--help|help)
|
||||||
|
sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
# with no args, run vanilla rubocop
|
||||||
|
# else assume we're passing in arbitrary arguments
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
bundle exec rake -f lib/tasks/rubocop.rake rubocop
|
||||||
|
else
|
||||||
|
bundle exec rubocop "$@"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -141,18 +141,25 @@ This adapter follows **version 1.0** of the [format specified](../jsonapi/schema
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Included
|
### Include option
|
||||||
|
|
||||||
It will include the associated resources in the `"included"` member
|
Which [serializer associations](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#associations) are rendered can be specified using the `include` option. The option usage is consistent with [the include option in the JSON API spec](http://jsonapi.org/format/#fetching-includes), and is available in all adapters.
|
||||||
when the resource names are included in the `include` option.
|
|
||||||
Including nested associated resources is also supported.
|
|
||||||
|
|
||||||
|
Example of the usage:
|
||||||
```ruby
|
```ruby
|
||||||
render json: @posts, include: ['author', 'comments', 'comments.author']
|
render json: @posts, include: ['author', 'comments', 'comments.author']
|
||||||
# or
|
# or
|
||||||
render json: @posts, include: 'author,comments,comments.author'
|
render json: @posts, include: 'author,comments,comments.author'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The format of the `include` option can be either:
|
||||||
|
|
||||||
|
- a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes).
|
||||||
|
- an Array of Symbols and Hashes.
|
||||||
|
- a mix of both.
|
||||||
|
|
||||||
|
An empty string or an empty array will prevent rendering of any associations.
|
||||||
|
|
||||||
In addition, two types of wildcards may be used:
|
In addition, two types of wildcards may be used:
|
||||||
|
|
||||||
- `*` includes one level of associations.
|
- `*` includes one level of associations.
|
||||||
@ -164,11 +171,6 @@ These can be combined with other paths.
|
|||||||
render json: @posts, include: '**' # or '*' for a single layer
|
render json: @posts, include: '**' # or '*' for a single layer
|
||||||
```
|
```
|
||||||
|
|
||||||
The format of the `include` option can be either:
|
|
||||||
|
|
||||||
- a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes).
|
|
||||||
- an Array of Symbols and Hashes.
|
|
||||||
- a mix of both.
|
|
||||||
|
|
||||||
The following would render posts and include:
|
The following would render posts and include:
|
||||||
|
|
||||||
@ -182,6 +184,20 @@ It could be combined, like above, with other paths in any combination desired.
|
|||||||
render json: @posts, include: 'author.comments.**'
|
render json: @posts, include: 'author.comments.**'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** Wildcards are ActiveModelSerializers-specific, they are not part of the JSON API spec.
|
||||||
|
|
||||||
|
The default include for the JSON API adapter is no associations. The default for the JSON and Attributes adapters is all associations.
|
||||||
|
|
||||||
|
For the JSON API adapter associated resources will be gathered in the `"included"` member. For the JSON and Attributes
|
||||||
|
adapters associated resources will be rendered among the other attributes.
|
||||||
|
|
||||||
|
Only for the JSON API adapter you can specify, which attributes of associated resources will be rendered. This feature
|
||||||
|
is called [sparse fieldset](http://jsonapi.org/format/#fetching-sparse-fieldsets):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
render json: @posts, include: 'comments', fields: { comments: ['content', 'created_at'] }
|
||||||
|
```
|
||||||
|
|
||||||
##### Security Considerations
|
##### Security Considerations
|
||||||
|
|
||||||
Since the included options may come from the query params (i.e. user-controller):
|
Since the included options may come from the query params (i.e. user-controller):
|
||||||
|
|||||||
@ -37,7 +37,7 @@ and
|
|||||||
class CommentSerializer < ActiveModel::Serializer
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
attributes :name, :body
|
attributes :name, :body
|
||||||
|
|
||||||
belongs_to :post_id
|
belongs_to :post
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -203,7 +203,7 @@ link(:link_name) { url_for(controller: 'controller_name', action: 'index', only_
|
|||||||
|
|
||||||
#### include
|
#### include
|
||||||
|
|
||||||
PR please :)
|
See [Adapters: Include Option](/docs/general/adapters.md#include-option).
|
||||||
|
|
||||||
#### Overriding the root key
|
#### Overriding the root key
|
||||||
|
|
||||||
@ -260,15 +260,29 @@ Note that by using a string and symbol, Ruby will assume the namespace is define
|
|||||||
|
|
||||||
#### serializer
|
#### serializer
|
||||||
|
|
||||||
PR please :)
|
Specify which serializer to use if you want to use a serializer other than the default.
|
||||||
|
|
||||||
|
For a single resource:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
@post = Post.first
|
||||||
|
render json: @post, serializer: SpecialPostSerializer
|
||||||
|
```
|
||||||
|
|
||||||
|
To specify which serializer to use on individual items in a collection (i.e., an `index` action), use `each_serializer`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
@posts = Post.all
|
||||||
|
render json: @posts, each_serializer: SpecialPostSerializer
|
||||||
|
```
|
||||||
|
|
||||||
#### scope
|
#### scope
|
||||||
|
|
||||||
PR please :)
|
See [Serializers: Scope](/docs/general/serializers.md#scope).
|
||||||
|
|
||||||
#### scope_name
|
#### scope_name
|
||||||
|
|
||||||
PR please :)
|
See [Serializers: Scope](/docs/general/serializers.md#scope).
|
||||||
|
|
||||||
## Using a serializer without `render`
|
## Using a serializer without `render`
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,10 @@ Where:
|
|||||||
- `unless:`
|
- `unless:`
|
||||||
- `virtual_value:`
|
- `virtual_value:`
|
||||||
- `polymorphic:` defines if polymorphic relation type should be nested in serialized association.
|
- `polymorphic:` defines if polymorphic relation type should be nested in serialized association.
|
||||||
|
- `type:` the resource type as used by JSON:API, especially on a `belongs_to` relationship.
|
||||||
|
- `class_name:` used to determine `type` when `type` not given
|
||||||
|
- `foreign_key:` used by JSON:API on a `belongs_to` relationship to avoid unnecessarily loading the association object.
|
||||||
|
- `namespace:` used when looking up the serializer and `serializer` is not given. Falls back to the parent serializer's `:namespace` instance options, which, when present, comes from the render options. See [Rendering#namespace](rendering.md#namespace] for more details.
|
||||||
- optional: `&block` is a context that returns the association's attributes.
|
- optional: `&block` is a context that returns the association's attributes.
|
||||||
- prevents `association_name` method from being called.
|
- prevents `association_name` method from being called.
|
||||||
- return value of block is used as the association value.
|
- return value of block is used as the association value.
|
||||||
@ -382,11 +386,26 @@ The serialized value for a given key. e.g. `read_attribute_for_serialization(:ti
|
|||||||
|
|
||||||
#### #links
|
#### #links
|
||||||
|
|
||||||
PR please :)
|
Allows you to modify the `links` node. By default, this node will be populated with the attributes set using the [::link](#link) method. Using `links: nil` will remove the `links` node.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
@post,
|
||||||
|
adapter: :json_api,
|
||||||
|
links: {
|
||||||
|
self: {
|
||||||
|
href: 'http://example.com/posts',
|
||||||
|
meta: {
|
||||||
|
stuff: 'value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
#### #json_key
|
#### #json_key
|
||||||
|
|
||||||
PR please :)
|
Returns the key used by the adapter as the resource root. See [root](#root) for more information.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,7 @@ ActiveModelSerializers pagination relies on a paginated collection with the meth
|
|||||||
|
|
||||||
### JSON adapter
|
### JSON adapter
|
||||||
|
|
||||||
If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
|
If you are not using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
|
||||||
|
|
||||||
Add this method to your base API controller.
|
Add this method to your base API controller.
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,9 @@ Then, in your controller you can tell rails you're accepting and rendering the j
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Note:
|
||||||
|
In Rails 5, the "unsafe" method ( `jsonapi_parse!` vs the safe `jsonapi_parse`) throws an `InvalidDocument` exception when the payload does not meet basic criteria for JSON API deserialization.
|
||||||
|
|
||||||
|
|
||||||
### Adapter Changes
|
### Adapter Changes
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,7 @@ require 'active_model/serializer/collection_serializer'
|
|||||||
require 'active_model/serializer/array_serializer'
|
require 'active_model/serializer/array_serializer'
|
||||||
require 'active_model/serializer/error_serializer'
|
require 'active_model/serializer/error_serializer'
|
||||||
require 'active_model/serializer/errors_serializer'
|
require 'active_model/serializer/errors_serializer'
|
||||||
require 'active_model/serializer/concerns/associations'
|
|
||||||
require 'active_model/serializer/concerns/attributes'
|
|
||||||
require 'active_model/serializer/concerns/caching'
|
require 'active_model/serializer/concerns/caching'
|
||||||
require 'active_model/serializer/concerns/configuration'
|
|
||||||
require 'active_model/serializer/concerns/links'
|
|
||||||
require 'active_model/serializer/concerns/meta'
|
|
||||||
require 'active_model/serializer/concerns/type'
|
|
||||||
require 'active_model/serializer/fieldset'
|
require 'active_model/serializer/fieldset'
|
||||||
require 'active_model/serializer/lint'
|
require 'active_model/serializer/lint'
|
||||||
|
|
||||||
@ -18,33 +12,40 @@ require 'active_model/serializer/lint'
|
|||||||
# reified when subclassed to decorate a resource.
|
# reified when subclassed to decorate a resource.
|
||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
|
undef_method :select, :display # These IO methods, which are mixed into Kernel,
|
||||||
|
# sometimes conflict with attribute names. We don't need these IO methods.
|
||||||
|
|
||||||
# @see #serializable_hash for more details on these valid keys.
|
# @see #serializable_hash for more details on these valid keys.
|
||||||
SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
|
SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
autoload :Adapter
|
autoload :Adapter
|
||||||
autoload :Null
|
autoload :Null
|
||||||
include Configuration
|
autoload :Attribute
|
||||||
include Associations
|
autoload :Association
|
||||||
include Attributes
|
autoload :Reflection
|
||||||
|
autoload :SingularReflection
|
||||||
|
autoload :CollectionReflection
|
||||||
|
autoload :BelongsToReflection
|
||||||
|
autoload :HasOneReflection
|
||||||
|
autoload :HasManyReflection
|
||||||
|
include ActiveSupport::Configurable
|
||||||
include Caching
|
include Caching
|
||||||
include Links
|
|
||||||
include Meta
|
|
||||||
include Type
|
|
||||||
|
|
||||||
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
|
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
|
||||||
# @return [ActiveModel::Serializer]
|
# @return [ActiveModel::Serializer]
|
||||||
# Preferentially returns
|
# Preferentially returns
|
||||||
# 1. resource.serializer
|
# 1. resource.serializer_class
|
||||||
# 2. ArraySerializer when resource is a collection
|
# 2. ArraySerializer when resource is a collection
|
||||||
# 3. options[:serializer]
|
# 3. options[:serializer]
|
||||||
# 4. lookup serializer when resource is a Class
|
# 4. lookup serializer when resource is a Class
|
||||||
def self.serializer_for(resource, options = {})
|
def self.serializer_for(resource_or_class, options = {})
|
||||||
if resource.respond_to?(:serializer_class)
|
if resource_or_class.respond_to?(:serializer_class)
|
||||||
resource.serializer_class
|
resource_or_class.serializer_class
|
||||||
elsif resource.respond_to?(:to_ary)
|
elsif resource_or_class.respond_to?(:to_ary)
|
||||||
config.collection_serializer
|
config.collection_serializer
|
||||||
else
|
else
|
||||||
options.fetch(:serializer) { get_serializer_for(resource.class, options[:namespace]) }
|
resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
|
||||||
|
options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -91,6 +92,8 @@ module ActiveModel
|
|||||||
serializer_class
|
serializer_class
|
||||||
elsif klass.superclass
|
elsif klass.superclass
|
||||||
get_serializer_for(klass.superclass)
|
get_serializer_for(klass.superclass)
|
||||||
|
else
|
||||||
|
nil # No serializer found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -111,6 +114,193 @@ module ActiveModel
|
|||||||
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
|
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Preferred interface is ActiveModelSerializers.config
|
||||||
|
# BEGIN DEFAULT CONFIGURATION
|
||||||
|
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
|
||||||
|
config.serializer_lookup_enabled = true
|
||||||
|
|
||||||
|
# @deprecated Use {#config.collection_serializer=} instead of this. Is
|
||||||
|
# compatibility layer for ArraySerializer.
|
||||||
|
def config.array_serializer=(collection_serializer)
|
||||||
|
self.collection_serializer = collection_serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
# @deprecated Use {#config.collection_serializer} instead of this. Is
|
||||||
|
# compatibility layer for ArraySerializer.
|
||||||
|
def config.array_serializer
|
||||||
|
collection_serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
config.default_includes = '*'
|
||||||
|
config.adapter = :attributes
|
||||||
|
config.key_transform = nil
|
||||||
|
config.jsonapi_pagination_links_enabled = true
|
||||||
|
config.jsonapi_resource_type = :plural
|
||||||
|
config.jsonapi_namespace_separator = '-'.freeze
|
||||||
|
config.jsonapi_version = '1.0'
|
||||||
|
config.jsonapi_toplevel_meta = {}
|
||||||
|
# Make JSON API top-level jsonapi member opt-in
|
||||||
|
# ref: http://jsonapi.org/format/#document-top-level
|
||||||
|
config.jsonapi_include_toplevel_object = false
|
||||||
|
config.include_data_default = true
|
||||||
|
|
||||||
|
# For configuring how serializers are found.
|
||||||
|
# This should be an array of procs.
|
||||||
|
#
|
||||||
|
# The priority of the output is that the first item
|
||||||
|
# in the evaluated result array will take precedence
|
||||||
|
# over other possible serializer paths.
|
||||||
|
#
|
||||||
|
# i.e.: First match wins.
|
||||||
|
#
|
||||||
|
# @example output
|
||||||
|
# => [
|
||||||
|
# "CustomNamespace::ResourceSerializer",
|
||||||
|
# "ParentSerializer::ResourceSerializer",
|
||||||
|
# "ResourceNamespace::ResourceSerializer" ,
|
||||||
|
# "ResourceSerializer"]
|
||||||
|
#
|
||||||
|
# If CustomNamespace::ResourceSerializer exists, it will be used
|
||||||
|
# for serialization
|
||||||
|
config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
|
||||||
|
|
||||||
|
config.schema_path = 'test/support/schemas'
|
||||||
|
# END DEFAULT CONFIGURATION
|
||||||
|
|
||||||
|
with_options instance_writer: false, instance_reader: false do |serializer|
|
||||||
|
serializer.class_attribute :_attributes_data # @api private
|
||||||
|
self._attributes_data ||= {}
|
||||||
|
end
|
||||||
|
with_options instance_writer: false, instance_reader: true do |serializer|
|
||||||
|
serializer.class_attribute :_reflections
|
||||||
|
self._reflections ||= {}
|
||||||
|
serializer.class_attribute :_links # @api private
|
||||||
|
self._links ||= {}
|
||||||
|
serializer.class_attribute :_meta # @api private
|
||||||
|
serializer.class_attribute :_type # @api private
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.inherited(base)
|
||||||
|
super
|
||||||
|
base._attributes_data = _attributes_data.dup
|
||||||
|
base._reflections = _reflections.dup
|
||||||
|
base._links = _links.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Array<Symbol>] Key names of declared attributes
|
||||||
|
# @see Serializer::attribute
|
||||||
|
def self._attributes
|
||||||
|
_attributes_data.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
# BEGIN SERIALIZER MACROS
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# attributes :id, :name, :recent_edits
|
||||||
|
def self.attributes(*attrs)
|
||||||
|
attrs = attrs.first if attrs.first.class == Array
|
||||||
|
|
||||||
|
attrs.each do |attr|
|
||||||
|
attribute(attr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# attributes :id, :recent_edits
|
||||||
|
# attribute :name, key: :title
|
||||||
|
#
|
||||||
|
# attribute :full_name do
|
||||||
|
# "#{object.first_name} #{object.last_name}"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def recent_edits
|
||||||
|
# object.edits.last(5)
|
||||||
|
# end
|
||||||
|
def self.attribute(attr, options = {}, &block)
|
||||||
|
key = options.fetch(:key, attr)
|
||||||
|
_attributes_data[key] = Attribute.new(attr, options, block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Symbol] name of the association
|
||||||
|
# @param [Hash<Symbol => any>] options for the reflection
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# has_many :comments, serializer: CommentSummarySerializer
|
||||||
|
#
|
||||||
|
def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
||||||
|
associate(HasManyReflection.new(name, options, block))
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Symbol] name of the association
|
||||||
|
# @param [Hash<Symbol => any>] options for the reflection
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# belongs_to :author, serializer: AuthorSerializer
|
||||||
|
#
|
||||||
|
def self.belongs_to(name, options = {}, &block)
|
||||||
|
associate(BelongsToReflection.new(name, options, block))
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [Symbol] name of the association
|
||||||
|
# @param [Hash<Symbol => any>] options for the reflection
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# has_one :author, serializer: AuthorSerializer
|
||||||
|
#
|
||||||
|
def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
||||||
|
associate(HasOneReflection.new(name, options, block))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add reflection and define {name} accessor.
|
||||||
|
# @param [ActiveModel::Serializer::Reflection] reflection
|
||||||
|
# @return [void]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
def self.associate(reflection)
|
||||||
|
key = reflection.options[:key] || reflection.name
|
||||||
|
self._reflections[key] = reflection
|
||||||
|
end
|
||||||
|
private_class_method :associate
|
||||||
|
|
||||||
|
# Define a link on a serializer.
|
||||||
|
# @example
|
||||||
|
# link(:self) { resource_url(object) }
|
||||||
|
# @example
|
||||||
|
# link(:self) { "http://example.com/resource/#{object.id}" }
|
||||||
|
# @example
|
||||||
|
# link :resource, "http://example.com/resource"
|
||||||
|
#
|
||||||
|
def self.link(name, value = nil, &block)
|
||||||
|
_links[name] = block || value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set the JSON API meta attribute of a serializer.
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# meta { stuff: 'value' }
|
||||||
|
# @example
|
||||||
|
# meta do
|
||||||
|
# { comment_count: object.comments.count }
|
||||||
|
# end
|
||||||
|
def self.meta(value = nil, &block)
|
||||||
|
self._meta = block || value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set the JSON API type of a serializer.
|
||||||
|
# @example
|
||||||
|
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||||
|
# type 'authors'
|
||||||
|
def self.type(type)
|
||||||
|
self._type = type && type.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# END SERIALIZER MACROS
|
||||||
|
|
||||||
attr_accessor :object, :root, :scope
|
attr_accessor :object, :root, :scope
|
||||||
|
|
||||||
# `scope_name` is set as :current_user by default in the controller.
|
# `scope_name` is set as :current_user by default in the controller.
|
||||||
@ -131,53 +321,49 @@ module ActiveModel
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return the +attributes+ of +object+ as presented
|
||||||
|
# by the serializer.
|
||||||
|
def attributes(requested_attrs = nil, reload = false)
|
||||||
|
@attributes = nil if reload
|
||||||
|
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
|
||||||
|
next if attr.excluded?(self)
|
||||||
|
next unless requested_attrs.nil? || requested_attrs.include?(key)
|
||||||
|
hash[key] = attr.value(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [JSONAPI::IncludeDirective] include_directive (defaults to the
|
||||||
|
# +default_include_directive+ config value when not provided)
|
||||||
|
# @return [Enumerator<Association>]
|
||||||
|
def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
|
||||||
|
include_slice ||= include_directive
|
||||||
|
return Enumerator.new unless object
|
||||||
|
|
||||||
|
Enumerator.new do |y|
|
||||||
|
self.class._reflections.each do |key, reflection|
|
||||||
|
next if reflection.excluded?(self)
|
||||||
|
next unless include_directive.key?(key)
|
||||||
|
|
||||||
|
association = reflection.build_association(self, instance_options, include_slice)
|
||||||
|
y.yield association
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# @return [Hash] containing the attributes and first level
|
# @return [Hash] containing the attributes and first level
|
||||||
# associations, similar to how ActiveModel::Serializers::JSON is used
|
# associations, similar to how ActiveModel::Serializers::JSON is used
|
||||||
# in ActiveRecord::Base.
|
# in ActiveRecord::Base.
|
||||||
#
|
|
||||||
# TODO: Include <tt>ActiveModel::Serializers::JSON</tt>.
|
|
||||||
# So that the below is true:
|
|
||||||
# @param options [nil, Hash] The same valid options passed to `serializable_hash`
|
|
||||||
# (:only, :except, :methods, and :include).
|
|
||||||
#
|
|
||||||
# See
|
|
||||||
# https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serializers/json.rb#L17-L101
|
|
||||||
# https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serialization.rb#L85-L123
|
|
||||||
# https://github.com/rails/rails/blob/v5.0.0.beta2/activerecord/lib/active_record/serialization.rb#L11-L17
|
|
||||||
# https://github.com/rails/rails/blob/v5.0.0.beta2/activesupport/lib/active_support/core_ext/object/json.rb#L147-L162
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # The :only and :except options can be used to limit the attributes included, and work
|
|
||||||
# # similar to the attributes method.
|
|
||||||
# serializer.as_json(only: [:id, :name])
|
|
||||||
# serializer.as_json(except: [:id, :created_at, :age])
|
|
||||||
#
|
|
||||||
# # To include the result of some method calls on the model use :methods:
|
|
||||||
# serializer.as_json(methods: :permalink)
|
|
||||||
#
|
|
||||||
# # To include associations use :include:
|
|
||||||
# serializer.as_json(include: :posts)
|
|
||||||
# # Second level and higher order associations work as well:
|
|
||||||
# serializer.as_json(include: { posts: { include: { comments: { only: :body } }, only: :title } })
|
|
||||||
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
|
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
|
||||||
adapter_options ||= {}
|
adapter_options ||= {}
|
||||||
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
|
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
|
||||||
cached_attributes = adapter_options[:cached_attributes] ||= {}
|
resource = attributes_hash(adapter_options, options, adapter_instance)
|
||||||
resource = fetch_attributes(options[:fields], cached_attributes, adapter_instance)
|
relationships = associations_hash(adapter_options, options, adapter_instance)
|
||||||
relationships = resource_relationships(adapter_options, options, adapter_instance)
|
|
||||||
resource.merge(relationships)
|
resource.merge(relationships)
|
||||||
end
|
end
|
||||||
alias to_hash serializable_hash
|
alias to_hash serializable_hash
|
||||||
alias to_h serializable_hash
|
alias to_h serializable_hash
|
||||||
|
|
||||||
# @see #serializable_hash
|
# @see #serializable_hash
|
||||||
# TODO: When moving attributes adapter logic here, @see #serializable_hash
|
|
||||||
# So that the below is true:
|
|
||||||
# @param options [nil, Hash] The same valid options passed to `as_json`
|
|
||||||
# (:root, :only, :except, :methods, and :include).
|
|
||||||
# The default for `root` is nil.
|
|
||||||
# The default value for include_root is false. You can change it to true if the given
|
|
||||||
# JSON string includes a single root node.
|
|
||||||
def as_json(adapter_opts = nil)
|
def as_json(adapter_opts = nil)
|
||||||
serializable_hash(adapter_opts)
|
serializable_hash(adapter_opts)
|
||||||
end
|
end
|
||||||
@ -196,32 +382,24 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def resource_relationships(adapter_options, options, adapter_instance)
|
def attributes_hash(_adapter_options, options, adapter_instance)
|
||||||
relationships = {}
|
if self.class.cache_enabled?
|
||||||
include_directive = options.fetch(:include_directive)
|
fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
|
||||||
associations(include_directive).each do |association|
|
elsif self.class.fragment_cache_enabled?
|
||||||
adapter_opts = adapter_options.merge(include_directive: include_directive[association.key])
|
fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
|
||||||
relationships[association.key] ||= relationship_value_for(association, adapter_opts, adapter_instance)
|
else
|
||||||
|
attributes(options[:fields], true)
|
||||||
end
|
end
|
||||||
|
|
||||||
relationships
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def relationship_value_for(association, adapter_options, adapter_instance)
|
def associations_hash(adapter_options, options, adapter_instance)
|
||||||
return association.options[:virtual_value] if association.options[:virtual_value]
|
include_directive = options.fetch(:include_directive)
|
||||||
association_serializer = association.serializer
|
include_slice = options[:include_slice]
|
||||||
association_object = association_serializer && association_serializer.object
|
associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
|
||||||
return unless association_object
|
adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
|
||||||
|
relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
|
||||||
relationship_value = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
|
|
||||||
|
|
||||||
if association.options[:polymorphic] && relationship_value
|
|
||||||
polymorphic_type = association_object.class.name.underscore
|
|
||||||
relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
relationship_value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|||||||
@ -1,34 +1,71 @@
|
|||||||
|
require 'active_model/serializer/lazy_association'
|
||||||
|
|
||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
# This class holds all information about serializer's association.
|
# This class holds all information about serializer's association.
|
||||||
#
|
#
|
||||||
# @attr [Symbol] name
|
# @api private
|
||||||
# @attr [Hash{Symbol => Object}] options
|
Association = Struct.new(:reflection, :association_options) do
|
||||||
# @attr [block]
|
attr_reader :lazy_association
|
||||||
#
|
delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association
|
||||||
# @example
|
|
||||||
# Association.new(:comments, { serializer: CommentSummarySerializer })
|
def initialize(*)
|
||||||
#
|
super
|
||||||
class Association < Field
|
@lazy_association = LazyAssociation.new(reflection, association_options)
|
||||||
# @return [Symbol]
|
|
||||||
def key
|
|
||||||
options.fetch(:key, name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ActiveModel::Serializer, nil]
|
# @return [Symbol]
|
||||||
def serializer
|
delegate :name, to: :reflection
|
||||||
options[:serializer]
|
|
||||||
|
# @return [Symbol]
|
||||||
|
def key
|
||||||
|
reflection_options.fetch(:key, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [True,False]
|
||||||
|
def key?
|
||||||
|
reflection_options.key?(:key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def links
|
def links
|
||||||
options.fetch(:links) || {}
|
reflection_options.fetch(:links) || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [Hash, nil]
|
# @return [Hash, nil]
|
||||||
|
# This gets mutated, so cannot use the cached reflection_options
|
||||||
def meta
|
def meta
|
||||||
options[:meta]
|
reflection.options[:meta]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def belongs_to?
|
||||||
|
reflection.foreign_key_on == :self
|
||||||
|
end
|
||||||
|
|
||||||
|
def polymorphic?
|
||||||
|
true == reflection_options[:polymorphic]
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def serializable_hash(adapter_options, adapter_instance)
|
||||||
|
association_serializer = lazy_association.serializer
|
||||||
|
return virtual_value if virtual_value
|
||||||
|
association_object = association_serializer && association_serializer.object
|
||||||
|
return unless association_object
|
||||||
|
|
||||||
|
serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
|
||||||
|
|
||||||
|
if polymorphic? && serialization
|
||||||
|
polymorphic_type = association_object.class.name.underscore
|
||||||
|
serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
|
||||||
|
end
|
||||||
|
|
||||||
|
serialization
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
delegate :reflection_options, to: :lazy_association
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
# @api private
|
# @api private
|
||||||
class BelongsToReflection < SingularReflection
|
class BelongsToReflection < Reflection
|
||||||
|
# @api private
|
||||||
|
def foreign_key_on
|
||||||
|
:self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
# @api private
|
|
||||||
class CollectionReflection < Reflection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
# Defines an association in the object should be rendered.
|
|
||||||
#
|
|
||||||
# The serializer object should implement the association name
|
|
||||||
# as a method which should return an array when invoked. If a method
|
|
||||||
# with the association name does not exist, the association name is
|
|
||||||
# dispatched to the serialized object.
|
|
||||||
#
|
|
||||||
module Associations
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
with_options instance_writer: false, instance_reader: true do |serializer|
|
|
||||||
serializer.class_attribute :_reflections
|
|
||||||
self._reflections ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
autoload :Association
|
|
||||||
autoload :Reflection
|
|
||||||
autoload :SingularReflection
|
|
||||||
autoload :CollectionReflection
|
|
||||||
autoload :BelongsToReflection
|
|
||||||
autoload :HasOneReflection
|
|
||||||
autoload :HasManyReflection
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def inherited(base)
|
|
||||||
super
|
|
||||||
base._reflections = _reflections.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [Symbol] name of the association
|
|
||||||
# @param [Hash<Symbol => any>] options for the reflection
|
|
||||||
# @return [void]
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# has_many :comments, serializer: CommentSummarySerializer
|
|
||||||
#
|
|
||||||
def has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
|
||||||
associate(HasManyReflection.new(name, options, block))
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [Symbol] name of the association
|
|
||||||
# @param [Hash<Symbol => any>] options for the reflection
|
|
||||||
# @return [void]
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# belongs_to :author, serializer: AuthorSerializer
|
|
||||||
#
|
|
||||||
def belongs_to(name, options = {}, &block)
|
|
||||||
associate(BelongsToReflection.new(name, options, block))
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [Symbol] name of the association
|
|
||||||
# @param [Hash<Symbol => any>] options for the reflection
|
|
||||||
# @return [void]
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# has_one :author, serializer: AuthorSerializer
|
|
||||||
#
|
|
||||||
def has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
|
||||||
associate(HasOneReflection.new(name, options, block))
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Add reflection and define {name} accessor.
|
|
||||||
# @param [ActiveModel::Serializer::Reflection] reflection
|
|
||||||
# @return [void]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def associate(reflection)
|
|
||||||
key = reflection.options[:key] || reflection.name
|
|
||||||
self._reflections[key] = reflection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [JSONAPI::IncludeDirective] include_directive (defaults to the
|
|
||||||
# +default_include_directive+ config value when not provided)
|
|
||||||
# @return [Enumerator<Association>]
|
|
||||||
#
|
|
||||||
def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
|
|
||||||
include_slice ||= include_directive
|
|
||||||
return unless object
|
|
||||||
|
|
||||||
Enumerator.new do |y|
|
|
||||||
self.class._reflections.values.each do |reflection|
|
|
||||||
next if reflection.excluded?(self)
|
|
||||||
key = reflection.options.fetch(:key, reflection.name)
|
|
||||||
next unless include_directive.key?(key)
|
|
||||||
|
|
||||||
y.yield reflection.build_association(self, instance_options, include_slice)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Attributes
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
with_options instance_writer: false, instance_reader: false do |serializer|
|
|
||||||
serializer.class_attribute :_attributes_data # @api private
|
|
||||||
self._attributes_data ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
autoload :Attribute
|
|
||||||
|
|
||||||
# Return the +attributes+ of +object+ as presented
|
|
||||||
# by the serializer.
|
|
||||||
def attributes(requested_attrs = nil, reload = false)
|
|
||||||
@attributes = nil if reload
|
|
||||||
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
|
|
||||||
next if attr.excluded?(self)
|
|
||||||
next unless requested_attrs.nil? || requested_attrs.include?(key)
|
|
||||||
hash[key] = attr.value(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def inherited(base)
|
|
||||||
super
|
|
||||||
base._attributes_data = _attributes_data.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
# @example
|
|
||||||
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
||||||
# attributes :id, :name, :recent_edits
|
|
||||||
def attributes(*attrs)
|
|
||||||
attrs = attrs.first if attrs.first.class == Array
|
|
||||||
|
|
||||||
attrs.each do |attr|
|
|
||||||
attribute(attr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @example
|
|
||||||
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
||||||
# attributes :id, :recent_edits
|
|
||||||
# attribute :name, key: :title
|
|
||||||
#
|
|
||||||
# attribute :full_name do
|
|
||||||
# "#{object.first_name} #{object.last_name}"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def recent_edits
|
|
||||||
# object.edits.last(5)
|
|
||||||
# end
|
|
||||||
def attribute(attr, options = {}, &block)
|
|
||||||
key = options.fetch(:key, attr)
|
|
||||||
_attributes_data[key] = Attribute.new(attr, options, block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# keys of attributes
|
|
||||||
# @see Serializer::attribute
|
|
||||||
def _attributes
|
|
||||||
_attributes_data.keys
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# maps attribute value to explicit key name
|
|
||||||
# @see Serializer::attribute
|
|
||||||
# @see FragmentCache#fragment_serializer
|
|
||||||
def _attributes_keys
|
|
||||||
_attributes_data
|
|
||||||
.each_with_object({}) do |(key, attr), hash|
|
|
||||||
next if key == attr.name
|
|
||||||
hash[attr.name] = { key: key }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -40,9 +40,9 @@ module ActiveModel
|
|||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def inherited(base)
|
def inherited(base)
|
||||||
super
|
|
||||||
caller_line = caller[1]
|
caller_line = caller[1]
|
||||||
base._cache_digest_file_path = caller_line
|
base._cache_digest_file_path = caller_line
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def _cache_digest
|
def _cache_digest
|
||||||
@ -68,6 +68,18 @@ module ActiveModel
|
|||||||
_cache_options && _cache_options[:skip_digest]
|
_cache_options && _cache_options[:skip_digest]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# maps attribute value to explicit key name
|
||||||
|
# @see Serializer::attribute
|
||||||
|
# @see Serializer::fragmented_attributes
|
||||||
|
def _attributes_keys
|
||||||
|
_attributes_data
|
||||||
|
.each_with_object({}) do |(key, attr), hash|
|
||||||
|
next if key == attr.name
|
||||||
|
hash[attr.name] = { key: key }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fragmented_attributes
|
def fragmented_attributes
|
||||||
cached = _cache_only ? _cache_only : _attributes - _cache_except
|
cached = _cache_only ? _cache_only : _attributes - _cache_except
|
||||||
cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
|
cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
|
||||||
@ -158,6 +170,7 @@ module ActiveModel
|
|||||||
|
|
||||||
# Read cache from cache_store
|
# Read cache from cache_store
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
# Used in CollectionSerializer to set :cached_attributes
|
||||||
def cache_read_multi(collection_serializer, adapter_instance, include_directive)
|
def cache_read_multi(collection_serializer, adapter_instance, include_directive)
|
||||||
return {} if ActiveModelSerializers.config.cache_store.blank?
|
return {} if ActiveModelSerializers.config.cache_store.blank?
|
||||||
|
|
||||||
@ -180,12 +193,14 @@ module ActiveModel
|
|||||||
cache_keys << object_cache_key(serializer, adapter_instance)
|
cache_keys << object_cache_key(serializer, adapter_instance)
|
||||||
|
|
||||||
serializer.associations(include_directive).each do |association|
|
serializer.associations(include_directive).each do |association|
|
||||||
if association.serializer.respond_to?(:each)
|
# TODO(BF): Process relationship without evaluating lazy_association
|
||||||
association.serializer.each do |sub_serializer|
|
association_serializer = association.lazy_association.serializer
|
||||||
|
if association_serializer.respond_to?(:each)
|
||||||
|
association_serializer.each do |sub_serializer|
|
||||||
cache_keys << object_cache_key(sub_serializer, adapter_instance)
|
cache_keys << object_cache_key(sub_serializer, adapter_instance)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cache_keys << object_cache_key(association.serializer, adapter_instance)
|
cache_keys << object_cache_key(association_serializer, adapter_instance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -203,23 +218,18 @@ module ActiveModel
|
|||||||
|
|
||||||
### INSTANCE METHODS
|
### INSTANCE METHODS
|
||||||
def fetch_attributes(fields, cached_attributes, adapter_instance)
|
def fetch_attributes(fields, cached_attributes, adapter_instance)
|
||||||
if serializer_class.cache_enabled?
|
|
||||||
key = cache_key(adapter_instance)
|
key = cache_key(adapter_instance)
|
||||||
cached_attributes.fetch(key) do
|
cached_attributes.fetch(key) do
|
||||||
serializer_class.cache_store.fetch(key, serializer_class._cache_options) do
|
fetch(adapter_instance, serializer_class._cache_options, key) do
|
||||||
attributes(fields, true)
|
attributes(fields, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elsif serializer_class.fragment_cache_enabled?
|
|
||||||
fetch_attributes_fragment(adapter_instance, cached_attributes)
|
|
||||||
else
|
|
||||||
attributes(fields, true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch(adapter_instance, cache_options = serializer_class._cache_options)
|
def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
|
||||||
if serializer_class.cache_store
|
if serializer_class.cache_store
|
||||||
serializer_class.cache_store.fetch(cache_key(adapter_instance), cache_options) do
|
key ||= cache_key(adapter_instance)
|
||||||
|
serializer_class.cache_store.fetch(key, cache_options) do
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -230,7 +240,6 @@ module ActiveModel
|
|||||||
# 1. Determine cached fields from serializer class options
|
# 1. Determine cached fields from serializer class options
|
||||||
# 2. Get non_cached_fields and fetch cache_fields
|
# 2. Get non_cached_fields and fetch cache_fields
|
||||||
# 3. Merge the two hashes using adapter_instance#fragment_cache
|
# 3. Merge the two hashes using adapter_instance#fragment_cache
|
||||||
# rubocop:disable Metrics/AbcSize
|
|
||||||
def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
|
def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
|
||||||
serializer_class._cache_options ||= {}
|
serializer_class._cache_options ||= {}
|
||||||
serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
|
serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
|
||||||
@ -239,22 +248,21 @@ module ActiveModel
|
|||||||
non_cached_fields = fields[:non_cached].dup
|
non_cached_fields = fields[:non_cached].dup
|
||||||
non_cached_hash = attributes(non_cached_fields, true)
|
non_cached_hash = attributes(non_cached_fields, true)
|
||||||
include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
|
include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
|
||||||
non_cached_hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
|
non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
|
||||||
|
|
||||||
cached_fields = fields[:cached].dup
|
cached_fields = fields[:cached].dup
|
||||||
key = cache_key(adapter_instance)
|
key = cache_key(adapter_instance)
|
||||||
cached_hash =
|
cached_hash =
|
||||||
cached_attributes.fetch(key) do
|
cached_attributes.fetch(key) do
|
||||||
serializer_class.cache_store.fetch(key, serializer_class._cache_options) do
|
fetch(adapter_instance, serializer_class._cache_options, key) do
|
||||||
hash = attributes(cached_fields, true)
|
hash = attributes(cached_fields, true)
|
||||||
include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
|
include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
|
||||||
hash.merge! resource_relationships({}, { include_directive: include_directive }, adapter_instance)
|
hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# Merge both results
|
# Merge both results
|
||||||
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
|
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/AbcSize
|
|
||||||
|
|
||||||
def cache_key(adapter_instance)
|
def cache_key(adapter_instance)
|
||||||
return @cache_key if defined?(@cache_key)
|
return @cache_key if defined?(@cache_key)
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Configuration
|
|
||||||
include ActiveSupport::Configurable
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
# Configuration options may also be set in
|
|
||||||
# Serializers and Adapters
|
|
||||||
included do |base|
|
|
||||||
config = base.config
|
|
||||||
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
|
|
||||||
config.serializer_lookup_enabled = true
|
|
||||||
|
|
||||||
def config.array_serializer=(collection_serializer)
|
|
||||||
self.collection_serializer = collection_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def config.array_serializer
|
|
||||||
collection_serializer
|
|
||||||
end
|
|
||||||
|
|
||||||
config.default_includes = '*'
|
|
||||||
config.adapter = :attributes
|
|
||||||
config.key_transform = nil
|
|
||||||
config.jsonapi_pagination_links_enabled = true
|
|
||||||
config.jsonapi_resource_type = :plural
|
|
||||||
config.jsonapi_namespace_separator = '-'.freeze
|
|
||||||
config.jsonapi_version = '1.0'
|
|
||||||
config.jsonapi_toplevel_meta = {}
|
|
||||||
# Make JSON API top-level jsonapi member opt-in
|
|
||||||
# ref: http://jsonapi.org/format/#document-top-level
|
|
||||||
config.jsonapi_include_toplevel_object = false
|
|
||||||
config.include_data_default = true
|
|
||||||
|
|
||||||
# For configuring how serializers are found.
|
|
||||||
# This should be an array of procs.
|
|
||||||
#
|
|
||||||
# The priority of the output is that the first item
|
|
||||||
# in the evaluated result array will take precedence
|
|
||||||
# over other possible serializer paths.
|
|
||||||
#
|
|
||||||
# i.e.: First match wins.
|
|
||||||
#
|
|
||||||
# @example output
|
|
||||||
# => [
|
|
||||||
# "CustomNamespace::ResourceSerializer",
|
|
||||||
# "ParentSerializer::ResourceSerializer",
|
|
||||||
# "ResourceNamespace::ResourceSerializer" ,
|
|
||||||
# "ResourceSerializer"]
|
|
||||||
#
|
|
||||||
# If CustomNamespace::ResourceSerializer exists, it will be used
|
|
||||||
# for serialization
|
|
||||||
config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
|
|
||||||
|
|
||||||
config.schema_path = 'test/support/schemas'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Links
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
with_options instance_writer: false, instance_reader: true do |serializer|
|
|
||||||
serializer.class_attribute :_links # @api private
|
|
||||||
self._links ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def inherited(base)
|
|
||||||
super
|
|
||||||
base._links = _links.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define a link on a serializer.
|
|
||||||
# @example
|
|
||||||
# link(:self) { resource_url(object) }
|
|
||||||
# @example
|
|
||||||
# link(:self) { "http://example.com/resource/#{object.id}" }
|
|
||||||
# @example
|
|
||||||
# link :resource, "http://example.com/resource"
|
|
||||||
#
|
|
||||||
def link(name, value = nil, &block)
|
|
||||||
_links[name] = block || value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Meta
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
with_options instance_writer: false, instance_reader: true do |serializer|
|
|
||||||
serializer.class_attribute :_meta # @api private
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Set the JSON API meta attribute of a serializer.
|
|
||||||
# @example
|
|
||||||
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
||||||
# meta { stuff: 'value' }
|
|
||||||
# @example
|
|
||||||
# meta do
|
|
||||||
# { comment_count: object.comments.count }
|
|
||||||
# end
|
|
||||||
def meta(value = nil, &block)
|
|
||||||
self._meta = block || value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
module Type
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
with_options instance_writer: false, instance_reader: true do |serializer|
|
|
||||||
serializer.class_attribute :_type # @api private
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Set the JSON API type of a serializer.
|
|
||||||
# @example
|
|
||||||
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
||||||
# type 'authors'
|
|
||||||
def type(type)
|
|
||||||
self._type = type && type.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
# @api private
|
# @api private
|
||||||
class HasManyReflection < CollectionReflection
|
class HasManyReflection < Reflection
|
||||||
|
def collection?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
# @api private
|
# @api private
|
||||||
class HasOneReflection < SingularReflection
|
class HasOneReflection < Reflection
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
95
lib/active_model/serializer/lazy_association.rb
Normal file
95
lib/active_model/serializer/lazy_association.rb
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
# @api private
|
||||||
|
LazyAssociation = Struct.new(:reflection, :association_options) do
|
||||||
|
REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze
|
||||||
|
|
||||||
|
delegate :collection?, to: :reflection
|
||||||
|
|
||||||
|
def reflection_options
|
||||||
|
@reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def object
|
||||||
|
@object ||= reflection.value(
|
||||||
|
association_options.fetch(:parent_serializer),
|
||||||
|
association_options.fetch(:include_slice)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
alias_method :eval_reflection_block, :object
|
||||||
|
|
||||||
|
def include_data?
|
||||||
|
eval_reflection_block if reflection.block
|
||||||
|
reflection.include_data?(
|
||||||
|
association_options.fetch(:include_slice)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ActiveModel::Serializer, nil]
|
||||||
|
def serializer
|
||||||
|
return @serializer if defined?(@serializer)
|
||||||
|
if serializer_class
|
||||||
|
serialize_object!(object)
|
||||||
|
elsif !object.nil? && !object.instance_of?(Object)
|
||||||
|
cached_result[:virtual_value] = object
|
||||||
|
end
|
||||||
|
@serializer = cached_result[:serializer]
|
||||||
|
end
|
||||||
|
|
||||||
|
def virtual_value
|
||||||
|
cached_result[:virtual_value] || reflection_options[:virtual_value]
|
||||||
|
end
|
||||||
|
|
||||||
|
def serializer_class
|
||||||
|
return @serializer_class if defined?(@serializer_class)
|
||||||
|
serializer_for_options = { namespace: namespace }
|
||||||
|
serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer)
|
||||||
|
@serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cached_result
|
||||||
|
@cached_result ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_object!(object)
|
||||||
|
if collection?
|
||||||
|
if (serializer = instantiate_collection_serializer(object)).nil?
|
||||||
|
# BUG: per #2027, JSON API resource relationships are only id and type, and hence either
|
||||||
|
# *require* a serializer or we need to be a little clever about figuring out the id/type.
|
||||||
|
# In either case, returning the raw virtual value will almost always be incorrect.
|
||||||
|
#
|
||||||
|
# Should be reflection_options[:virtual_value] or adapter needs to figure out what to do
|
||||||
|
# with an object that is non-nil and has no defined serializer.
|
||||||
|
cached_result[:virtual_value] = object.try(:as_json) || object
|
||||||
|
else
|
||||||
|
cached_result[:serializer] = serializer
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cached_result[:serializer] = instantiate_serializer(object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def instantiate_serializer(object)
|
||||||
|
serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer)
|
||||||
|
serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class
|
||||||
|
serializer = reflection_options.fetch(:serializer, nil)
|
||||||
|
serializer_options[:serializer] = serializer if serializer
|
||||||
|
serializer_class.new(object, serializer_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def instantiate_collection_serializer(object)
|
||||||
|
serializer = catch(:no_serializer) do
|
||||||
|
instantiate_serializer(object)
|
||||||
|
end
|
||||||
|
serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def namespace
|
||||||
|
reflection_options[:namespace] ||
|
||||||
|
association_options.fetch(:parent_serializer_options)[:namespace]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,4 +1,5 @@
|
|||||||
require 'active_model/serializer/field'
|
require 'active_model/serializer/field'
|
||||||
|
require 'active_model/serializer/association'
|
||||||
|
|
||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
@ -8,12 +9,26 @@ module ActiveModel
|
|||||||
# @example
|
# @example
|
||||||
# class PostSerializer < ActiveModel::Serializer
|
# class PostSerializer < ActiveModel::Serializer
|
||||||
# has_one :author, serializer: AuthorSerializer
|
# has_one :author, serializer: AuthorSerializer
|
||||||
|
# belongs_to :boss, type: :users, foreign_key: :boss_id
|
||||||
# has_many :comments
|
# has_many :comments
|
||||||
# has_many :comments, key: :last_comments do
|
# has_many :comments, key: :last_comments do
|
||||||
# object.comments.last(1)
|
# object.comments.last(1)
|
||||||
# end
|
# end
|
||||||
# has_many :secret_meta_data, if: :is_admin?
|
# has_many :secret_meta_data, if: :is_admin?
|
||||||
#
|
#
|
||||||
|
# has_one :blog do |serializer|
|
||||||
|
# meta count: object.roles.count
|
||||||
|
# serializer.cached_blog
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# private
|
||||||
|
#
|
||||||
|
# def cached_blog
|
||||||
|
# cache_store.fetch("cached_blog:#{object.updated_at}") do
|
||||||
|
# Blog.find(object.blog_id)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
# def is_admin?
|
# def is_admin?
|
||||||
# current_user.admin?
|
# current_user.admin?
|
||||||
# end
|
# end
|
||||||
@ -24,51 +39,117 @@ module ActiveModel
|
|||||||
# 2) as 'object.comments.last(1)' and named 'last_comments'.
|
# 2) as 'object.comments.last(1)' and named 'last_comments'.
|
||||||
#
|
#
|
||||||
# PostSerializer._reflections # =>
|
# PostSerializer._reflections # =>
|
||||||
# # [
|
# # {
|
||||||
# # HasOneReflection.new(:author, serializer: AuthorSerializer),
|
# # author: HasOneReflection.new(:author, serializer: AuthorSerializer),
|
||||||
# # HasManyReflection.new(:comments)
|
# # comments: HasManyReflection.new(:comments)
|
||||||
# # HasManyReflection.new(:comments, { key: :last_comments }, #<Block>)
|
# # last_comments: HasManyReflection.new(:comments, { key: :last_comments }, #<Block>)
|
||||||
# # HasManyReflection.new(:secret_meta_data, { if: :is_admin? })
|
# # secret_meta_data: HasManyReflection.new(:secret_meta_data, { if: :is_admin? })
|
||||||
# # ]
|
# # }
|
||||||
#
|
#
|
||||||
# So you can inspect reflections in your Adapters.
|
# So you can inspect reflections in your Adapters.
|
||||||
#
|
|
||||||
class Reflection < Field
|
class Reflection < Field
|
||||||
|
attr_reader :foreign_key, :type
|
||||||
|
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@_links = {}
|
options[:links] = {}
|
||||||
@_include_data = Serializer.config.include_data_default
|
options[:include_data_setting] = Serializer.config.include_data_default
|
||||||
@_meta = nil
|
options[:meta] = nil
|
||||||
|
@type = options.fetch(:type) do
|
||||||
|
class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
|
||||||
|
class_name.underscore.pluralize.to_sym
|
||||||
|
end
|
||||||
|
@foreign_key = options.fetch(:foreign_key) do
|
||||||
|
if collection?
|
||||||
|
"#{name.to_s.singularize}_ids".to_sym
|
||||||
|
else
|
||||||
|
"#{name}_id".to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def link(name, value = nil, &block)
|
# @api public
|
||||||
@_links[name] = block || value
|
# @example
|
||||||
|
# has_one :blog do
|
||||||
|
# include_data false
|
||||||
|
# link :self, 'a link'
|
||||||
|
# link :related, 'another link'
|
||||||
|
# link :self, '//example.com/link_author/relationships/bio'
|
||||||
|
# id = object.profile.id
|
||||||
|
# link :related do
|
||||||
|
# "//example.com/profiles/#{id}" if id != 123
|
||||||
|
# end
|
||||||
|
# link :related do
|
||||||
|
# ids = object.likes.map(&:id).join(',')
|
||||||
|
# href "//example.com/likes/#{ids}"
|
||||||
|
# meta ids: ids
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
def link(name, value = nil)
|
||||||
|
options[:links][name] = block_given? ? Proc.new : value
|
||||||
:nil
|
:nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def meta(value = nil, &block)
|
# @api public
|
||||||
@_meta = block || value
|
# @example
|
||||||
|
# has_one :blog do
|
||||||
|
# include_data false
|
||||||
|
# meta(id: object.blog.id)
|
||||||
|
# meta liked: object.likes.any?
|
||||||
|
# link :self do
|
||||||
|
# href object.blog.id.to_s
|
||||||
|
# meta(id: object.blog.id)
|
||||||
|
# end
|
||||||
|
def meta(value = nil)
|
||||||
|
options[:meta] = block_given? ? Proc.new : value
|
||||||
:nil
|
:nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api public
|
||||||
|
# @example
|
||||||
|
# has_one :blog do
|
||||||
|
# include_data false
|
||||||
|
# link :self, 'a link'
|
||||||
|
# link :related, 'another link'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# has_one :blog do
|
||||||
|
# include_data false
|
||||||
|
# link :self, 'a link'
|
||||||
|
# link :related, 'another link'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# belongs_to :reviewer do
|
||||||
|
# meta name: 'Dan Brown'
|
||||||
|
# include_data true
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# has_many :tags, serializer: TagSerializer do
|
||||||
|
# link :self, '//example.com/link_author/relationships/tags'
|
||||||
|
# include_data :if_sideloaded
|
||||||
|
# end
|
||||||
def include_data(value = true)
|
def include_data(value = true)
|
||||||
@_include_data = value
|
options[:include_data_setting] = value
|
||||||
:nil
|
:nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def collection?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_data?(include_slice)
|
||||||
|
include_data_setting = options[:include_data_setting]
|
||||||
|
case include_data_setting
|
||||||
|
when :if_sideloaded then include_slice.key?(name)
|
||||||
|
when true then true
|
||||||
|
when false then false
|
||||||
|
else fail ArgumentError, "Unknown include_data_setting '#{include_data_setting.inspect}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# @param serializer [ActiveModel::Serializer]
|
# @param serializer [ActiveModel::Serializer]
|
||||||
# @yield [ActiveModel::Serializer]
|
# @yield [ActiveModel::Serializer]
|
||||||
# @return [:nil, associated resource or resource collection]
|
# @return [:nil, associated resource or resource collection]
|
||||||
# @example
|
|
||||||
# has_one :blog do |serializer|
|
|
||||||
# serializer.cached_blog
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def cached_blog
|
|
||||||
# cache_store.fetch("cached_blog:#{object.updated_at}") do
|
|
||||||
# Blog.find(object.blog_id)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def value(serializer, include_slice)
|
def value(serializer, include_slice)
|
||||||
@object = serializer.object
|
@object = serializer.object
|
||||||
@scope = serializer.scope
|
@scope = serializer.scope
|
||||||
@ -83,6 +164,11 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def foreign_key_on
|
||||||
|
:related
|
||||||
|
end
|
||||||
|
|
||||||
# Build association. This method is used internally to
|
# Build association. This method is used internally to
|
||||||
# build serializer's association by its reflection.
|
# build serializer's association by its reflection.
|
||||||
#
|
#
|
||||||
@ -103,61 +189,19 @@ module ActiveModel
|
|||||||
# comments_reflection.build_association(post_serializer, foo: 'bar')
|
# comments_reflection.build_association(post_serializer, foo: 'bar')
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
|
||||||
def build_association(parent_serializer, parent_serializer_options, include_slice = {})
|
def build_association(parent_serializer, parent_serializer_options, include_slice = {})
|
||||||
reflection_options = options.dup
|
association_options = {
|
||||||
|
parent_serializer: parent_serializer,
|
||||||
# Pass the parent's namespace onto the child serializer
|
parent_serializer_options: parent_serializer_options,
|
||||||
reflection_options[:namespace] ||= parent_serializer_options[:namespace]
|
include_slice: include_slice
|
||||||
|
}
|
||||||
association_value = value(parent_serializer, include_slice)
|
Association.new(self, association_options)
|
||||||
serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options)
|
|
||||||
reflection_options[:include_data] = include_data?(include_slice)
|
|
||||||
reflection_options[:links] = @_links
|
|
||||||
reflection_options[:meta] = @_meta
|
|
||||||
|
|
||||||
if serializer_class
|
|
||||||
serializer = catch(:no_serializer) do
|
|
||||||
serializer_class.new(
|
|
||||||
association_value,
|
|
||||||
serializer_options(parent_serializer, parent_serializer_options, reflection_options)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
if serializer.nil?
|
|
||||||
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
|
|
||||||
else
|
|
||||||
reflection_options[:serializer] = serializer
|
|
||||||
end
|
|
||||||
elsif !association_value.nil? && !association_value.instance_of?(Object)
|
|
||||||
reflection_options[:virtual_value] = association_value
|
|
||||||
end
|
|
||||||
|
|
||||||
block = nil
|
|
||||||
Association.new(name, reflection_options, block)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
# used in instance exec
|
||||||
attr_accessor :object, :scope
|
attr_accessor :object, :scope
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def include_data?(include_slice)
|
|
||||||
if @_include_data == :if_sideloaded
|
|
||||||
include_slice.key?(name)
|
|
||||||
else
|
|
||||||
@_include_data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializer_options(parent_serializer, parent_serializer_options, reflection_options)
|
|
||||||
serializer = reflection_options.fetch(:serializer, nil)
|
|
||||||
|
|
||||||
serializer_options = parent_serializer_options.except(:serializer)
|
|
||||||
serializer_options[:serializer] = serializer if serializer
|
|
||||||
serializer_options[:serializer_context_class] = parent_serializer.class
|
|
||||||
serializer_options
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
module ActiveModel
|
|
||||||
class Serializer
|
|
||||||
# @api private
|
|
||||||
class SingularReflection < Reflection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
module ActiveModel
|
module ActiveModel
|
||||||
class Serializer
|
class Serializer
|
||||||
VERSION = '0.10.5'.freeze
|
VERSION = '0.10.6'.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -257,7 +257,8 @@ module ActiveModelSerializers
|
|||||||
|
|
||||||
def process_relationships(serializer, include_slice)
|
def process_relationships(serializer, include_slice)
|
||||||
serializer.associations(include_slice).each do |association|
|
serializer.associations(include_slice).each do |association|
|
||||||
process_relationship(association.serializer, include_slice[association.key])
|
# TODO(BF): Process relationship without evaluating lazy_association
|
||||||
|
process_relationship(association.lazy_association.serializer, include_slice[association.key])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -294,20 +295,8 @@ module ActiveModelSerializers
|
|||||||
|
|
||||||
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
||||||
def resource_object_for(serializer, include_slice = {})
|
def resource_object_for(serializer, include_slice = {})
|
||||||
resource_object = serializer.fetch(self) do
|
resource_object = data_for(serializer, include_slice)
|
||||||
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
|
||||||
|
|
||||||
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
|
||||||
attributes = attributes_for(serializer, requested_fields)
|
|
||||||
resource_object[:attributes] = attributes if attributes.any?
|
|
||||||
resource_object
|
|
||||||
end
|
|
||||||
|
|
||||||
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
|
||||||
relationships = relationships_for(serializer, requested_associations, include_slice)
|
|
||||||
resource_object[:relationships] = relationships if relationships.any?
|
|
||||||
|
|
||||||
links = links_for(serializer)
|
|
||||||
# toplevel_links
|
# toplevel_links
|
||||||
# definition:
|
# definition:
|
||||||
# allOf
|
# allOf
|
||||||
@ -321,7 +310,10 @@ module ActiveModelSerializers
|
|||||||
# prs:
|
# prs:
|
||||||
# https://github.com/rails-api/active_model_serializers/pull/1247
|
# https://github.com/rails-api/active_model_serializers/pull/1247
|
||||||
# https://github.com/rails-api/active_model_serializers/pull/1018
|
# https://github.com/rails-api/active_model_serializers/pull/1018
|
||||||
resource_object[:links] = links if links.any?
|
if (links = links_for(serializer)).any?
|
||||||
|
resource_object ||= {}
|
||||||
|
resource_object[:links] = links
|
||||||
|
end
|
||||||
|
|
||||||
# toplevel_meta
|
# toplevel_meta
|
||||||
# alias meta
|
# alias meta
|
||||||
@ -331,12 +323,33 @@ module ActiveModelSerializers
|
|||||||
# {
|
# {
|
||||||
# :'git-ref' => 'abc123'
|
# :'git-ref' => 'abc123'
|
||||||
# }
|
# }
|
||||||
meta = meta_for(serializer)
|
if (meta = meta_for(serializer)).present?
|
||||||
resource_object[:meta] = meta unless meta.blank?
|
resource_object ||= {}
|
||||||
|
resource_object[:meta] = meta
|
||||||
|
end
|
||||||
|
|
||||||
resource_object
|
resource_object
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def data_for(serializer, include_slice)
|
||||||
|
data = serializer.fetch(self) do
|
||||||
|
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
||||||
|
break nil if resource_object.nil?
|
||||||
|
|
||||||
|
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
||||||
|
attributes = attributes_for(serializer, requested_fields)
|
||||||
|
resource_object[:attributes] = attributes if attributes.any?
|
||||||
|
resource_object
|
||||||
|
end
|
||||||
|
data.tap do |resource_object|
|
||||||
|
next if resource_object.nil?
|
||||||
|
# NOTE(BF): the attributes are cached above, separately from the relationships, below.
|
||||||
|
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
||||||
|
relationships = relationships_for(serializer, requested_associations, include_slice)
|
||||||
|
resource_object[:relationships] = relationships if relationships.any?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
|
# {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
|
||||||
# relationships
|
# relationships
|
||||||
# definition:
|
# definition:
|
||||||
|
|||||||
@ -15,9 +15,7 @@ module ActiveModelSerializers
|
|||||||
def as_json
|
def as_json
|
||||||
hash = {}
|
hash = {}
|
||||||
|
|
||||||
if association.options[:include_data]
|
hash[:data] = data_for(association) if association.include_data?
|
||||||
hash[:data] = data_for(association)
|
|
||||||
end
|
|
||||||
|
|
||||||
links = links_for(association)
|
links = links_for(association)
|
||||||
hash[:links] = links if links.any?
|
hash[:links] = links if links.any?
|
||||||
@ -35,14 +33,45 @@ module ActiveModelSerializers
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self
|
||||||
def data_for(association)
|
def data_for(association)
|
||||||
serializer = association.serializer
|
if association.collection?
|
||||||
if serializer.respond_to?(:each)
|
data_for_many(association)
|
||||||
serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
|
else
|
||||||
elsif (virtual_value = association.options[:virtual_value])
|
data_for_one(association)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_for_one(association)
|
||||||
|
if association.belongs_to? &&
|
||||||
|
parent_serializer.object.respond_to?(association.reflection.foreign_key)
|
||||||
|
id = parent_serializer.object.send(association.reflection.foreign_key)
|
||||||
|
type = association.reflection.type.to_s
|
||||||
|
ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options)
|
||||||
|
else
|
||||||
|
# TODO(BF): Process relationship without evaluating lazy_association
|
||||||
|
serializer = association.lazy_association.serializer
|
||||||
|
if (virtual_value = association.virtual_value)
|
||||||
virtual_value
|
virtual_value
|
||||||
elsif serializer && serializer.object
|
elsif serializer && association.object
|
||||||
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_for_many(association)
|
||||||
|
# TODO(BF): Process relationship without evaluating lazy_association
|
||||||
|
collection_serializer = association.lazy_association.serializer
|
||||||
|
if collection_serializer.respond_to?(:each)
|
||||||
|
collection_serializer.map do |serializer|
|
||||||
|
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
||||||
|
end
|
||||||
|
elsif (virtual_value = association.virtual_value)
|
||||||
|
virtual_value
|
||||||
|
else
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,14 @@ module ActiveModelSerializers
|
|||||||
JsonApi.send(:transform_key_casing!, raw_type, transform_options)
|
JsonApi.send(:transform_key_casing!, raw_type, transform_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.for_type_with_id(type, id, options)
|
||||||
|
return nil if id.blank?
|
||||||
|
{
|
||||||
|
id: id.to_s,
|
||||||
|
type: type_for(:no_class_needed, type, options)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
# {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
|
# {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
|
||||||
def initialize(serializer, options)
|
def initialize(serializer, options)
|
||||||
@id = id_for(serializer)
|
@id = id_for(serializer)
|
||||||
@ -29,6 +37,7 @@ module ActiveModelSerializers
|
|||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
|
return nil if id.blank?
|
||||||
{ id: id, type: type }
|
{ id: id, type: type }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
# ActiveModelSerializers::Model is a convenient superclass for making your models
|
# ActiveModelSerializers::Model is a convenient superclass for making your models
|
||||||
# from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
|
# from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
|
||||||
# that satisfies ActiveModel::Serializer::Lint::Tests.
|
# that satisfies ActiveModel::Serializer::Lint::Tests.
|
||||||
|
require 'active_support/core_ext/hash'
|
||||||
module ActiveModelSerializers
|
module ActiveModelSerializers
|
||||||
class Model
|
class Model
|
||||||
include ActiveModel::Serializers::JSON
|
include ActiveModel::Serializers::JSON
|
||||||
include ActiveModel::Model
|
include ActiveModel::Model
|
||||||
|
|
||||||
# Declare names of attributes to be included in +sttributes+ hash.
|
# Declare names of attributes to be included in +attributes+ hash.
|
||||||
# Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
|
# Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
|
||||||
# uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
|
# uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
|
||||||
#
|
#
|
||||||
@ -19,8 +20,8 @@ module ActiveModelSerializers
|
|||||||
|
|
||||||
# Easily declare instance attributes with setters and getters for each.
|
# Easily declare instance attributes with setters and getters for each.
|
||||||
#
|
#
|
||||||
# All attributes to initialize an instance must have setters.
|
# To initialize an instance, all attributes must have setters.
|
||||||
# However, the hash turned by +attributes+ instance method will ALWAYS
|
# However, the hash returned by +attributes+ instance method will ALWAYS
|
||||||
# be the value of the initial attributes, regardless of what accessors are defined.
|
# be the value of the initial attributes, regardless of what accessors are defined.
|
||||||
# The only way to change the change the attributes after initialization is
|
# The only way to change the change the attributes after initialization is
|
||||||
# to mutate the +attributes+ directly.
|
# to mutate the +attributes+ directly.
|
||||||
@ -58,7 +59,7 @@ module ActiveModelSerializers
|
|||||||
|
|
||||||
# Override the +attributes+ method so that the hash is derived from +attribute_names+.
|
# Override the +attributes+ method so that the hash is derived from +attribute_names+.
|
||||||
#
|
#
|
||||||
# The the fields in +attribute_names+ determines the returned hash.
|
# The fields in +attribute_names+ determines the returned hash.
|
||||||
# +attributes+ are returned frozen to prevent any expectations that mutation affects
|
# +attributes+ are returned frozen to prevent any expectations that mutation affects
|
||||||
# the actual values in the model.
|
# the actual values in the model.
|
||||||
def attributes
|
def attributes
|
||||||
|
|||||||
53
lib/tasks/rubocop.rake
Normal file
53
lib/tasks/rubocop.rake
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
begin
|
||||||
|
require 'rubocop'
|
||||||
|
require 'rubocop/rake_task'
|
||||||
|
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
||||||
|
else
|
||||||
|
require 'rbconfig'
|
||||||
|
# https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2
|
||||||
|
windows_platforms = /(msdos|mswin|djgpp|mingw)/
|
||||||
|
if RbConfig::CONFIG['host_os'] =~ windows_platforms
|
||||||
|
desc 'No-op rubocop on Windows-- unsupported platform'
|
||||||
|
task :rubocop do
|
||||||
|
puts 'Skipping rubocop on Windows'
|
||||||
|
end
|
||||||
|
elsif defined?(::Rubinius)
|
||||||
|
desc 'No-op rubocop to avoid rbx segfault'
|
||||||
|
task :rubocop do
|
||||||
|
puts 'Skipping rubocop on rbx due to segfault'
|
||||||
|
puts 'https://github.com/rubinius/rubinius/issues/3499'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
|
||||||
|
patterns = [
|
||||||
|
'Gemfile',
|
||||||
|
'Rakefile',
|
||||||
|
'lib/**/*.{rb,rake}',
|
||||||
|
'config/**/*.rb',
|
||||||
|
'app/**/*.rb',
|
||||||
|
'test/**/*.rb'
|
||||||
|
]
|
||||||
|
desc 'Execute rubocop'
|
||||||
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
||||||
|
task.options = ['--rails', '--display-cop-names', '--display-style-guide']
|
||||||
|
task.formatters = ['progress']
|
||||||
|
task.patterns = patterns
|
||||||
|
task.fail_on_error = true
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :rubocop do
|
||||||
|
desc 'Auto-gen rubocop config'
|
||||||
|
task :auto_gen_config do
|
||||||
|
options = ['--auto-gen-config'].concat patterns
|
||||||
|
require 'benchmark'
|
||||||
|
result = 0
|
||||||
|
cli = RuboCop::CLI.new
|
||||||
|
time = Benchmark.realtime do
|
||||||
|
result = cli.run(options)
|
||||||
|
end
|
||||||
|
puts "Finished in #{time} seconds" if cli.options[:debug]
|
||||||
|
abort('RuboCop failed!') if result.nonzero?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -19,7 +19,7 @@ module ActionController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_using_adapter_override
|
def render_using_adapter_override
|
||||||
@profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
|
@profile = Profile.new(id: 'render_using_adapter_override', name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
|
||||||
render json: @profile, adapter: :json_api
|
render json: @profile, adapter: :json_api
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ module ActionController
|
|||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
data: {
|
data: {
|
||||||
id: @controller.instance_variable_get(:@profile).id.to_s,
|
id: 'render_using_adapter_override',
|
||||||
type: 'profiles',
|
type: 'profiles',
|
||||||
attributes: {
|
attributes: {
|
||||||
name: 'Name 1',
|
name: 'Name 1',
|
||||||
|
|||||||
@ -30,18 +30,17 @@ module ActiveModel
|
|||||||
def test_has_many_and_has_one
|
def test_has_many_and_has_one
|
||||||
@author_serializer.associations.each do |association|
|
@author_serializer.associations.each do |association|
|
||||||
key = association.key
|
key = association.key
|
||||||
serializer = association.serializer
|
serializer = association.lazy_association.serializer
|
||||||
options = association.options
|
|
||||||
|
|
||||||
case key
|
case key
|
||||||
when :posts
|
when :posts
|
||||||
assert_equal true, options.fetch(:include_data)
|
assert_equal true, association.include_data?
|
||||||
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
|
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
|
||||||
when :bio
|
when :bio
|
||||||
assert_equal true, options.fetch(:include_data)
|
assert_equal true, association.include_data?
|
||||||
assert_nil serializer
|
assert_nil serializer
|
||||||
when :roles
|
when :roles
|
||||||
assert_equal true, options.fetch(:include_data)
|
assert_equal true, association.include_data?
|
||||||
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
|
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
|
||||||
else
|
else
|
||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
@ -56,12 +55,11 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
post_serializer_class.new(@post).associations.each do |association|
|
post_serializer_class.new(@post).associations.each do |association|
|
||||||
key = association.key
|
key = association.key
|
||||||
serializer = association.serializer
|
serializer = association.lazy_association.serializer
|
||||||
options = association.options
|
|
||||||
|
|
||||||
assert_equal :tags, key
|
assert_equal :tags, key
|
||||||
assert_nil serializer
|
assert_nil serializer
|
||||||
assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, options[:virtual_value].to_json
|
assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -70,7 +68,7 @@ module ActiveModel
|
|||||||
.associations
|
.associations
|
||||||
.detect { |assoc| assoc.key == :comments }
|
.detect { |assoc| assoc.key == :comments }
|
||||||
|
|
||||||
comment_serializer = association.serializer.first
|
comment_serializer = association.lazy_association.serializer.first
|
||||||
class << comment_serializer
|
class << comment_serializer
|
||||||
def custom_options
|
def custom_options
|
||||||
instance_options
|
instance_options
|
||||||
@ -82,7 +80,7 @@ module ActiveModel
|
|||||||
def test_belongs_to
|
def test_belongs_to
|
||||||
@comment_serializer.associations.each do |association|
|
@comment_serializer.associations.each do |association|
|
||||||
key = association.key
|
key = association.key
|
||||||
serializer = association.serializer
|
serializer = association.lazy_association.serializer
|
||||||
|
|
||||||
case key
|
case key
|
||||||
when :post
|
when :post
|
||||||
@ -93,7 +91,7 @@ module ActiveModel
|
|||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal true, association.options.fetch(:include_data)
|
assert_equal true, association.include_data?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -139,6 +137,34 @@ module ActiveModel
|
|||||||
assert expected_association_keys.include? :site
|
assert expected_association_keys.include? :site
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class BelongsToBlogModel < ::Model
|
||||||
|
attributes :id, :title
|
||||||
|
associations :blog
|
||||||
|
end
|
||||||
|
class BelongsToBlogModelSerializer < ActiveModel::Serializer
|
||||||
|
type :posts
|
||||||
|
belongs_to :blog
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_belongs_to_doesnt_load_record
|
||||||
|
attributes = { id: 1, title: 'Belongs to Blog', blog: Blog.new(id: 5) }
|
||||||
|
post = BelongsToBlogModel.new(attributes)
|
||||||
|
class << post
|
||||||
|
def blog
|
||||||
|
fail 'should use blog_id'
|
||||||
|
end
|
||||||
|
|
||||||
|
def blog_id
|
||||||
|
5
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
actual = serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json
|
||||||
|
expected = { data: { id: '1', type: 'posts', relationships: { blog: { data: { id: '5', type: 'blogs' } } } } }
|
||||||
|
|
||||||
|
assert_equal expected, actual
|
||||||
|
end
|
||||||
|
|
||||||
class InlineAssociationTestPostSerializer < ActiveModel::Serializer
|
class InlineAssociationTestPostSerializer < ActiveModel::Serializer
|
||||||
has_many :comments
|
has_many :comments
|
||||||
has_many :comments, key: :last_comments do
|
has_many :comments, key: :last_comments do
|
||||||
@ -203,11 +229,11 @@ module ActiveModel
|
|||||||
@post_serializer.associations.each do |association|
|
@post_serializer.associations.each do |association|
|
||||||
case association.key
|
case association.key
|
||||||
when :comments
|
when :comments
|
||||||
assert_instance_of(ResourceNamespace::CommentSerializer, association.serializer.first)
|
assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
|
||||||
when :author
|
when :author
|
||||||
assert_instance_of(ResourceNamespace::AuthorSerializer, association.serializer)
|
assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
|
||||||
when :description
|
when :description
|
||||||
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.serializer)
|
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
|
||||||
else
|
else
|
||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
end
|
end
|
||||||
@ -245,11 +271,11 @@ module ActiveModel
|
|||||||
@post_serializer.associations.each do |association|
|
@post_serializer.associations.each do |association|
|
||||||
case association.key
|
case association.key
|
||||||
when :comments
|
when :comments
|
||||||
assert_instance_of(PostSerializer::CommentSerializer, association.serializer.first)
|
assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first)
|
||||||
when :author
|
when :author
|
||||||
assert_instance_of(PostSerializer::AuthorSerializer, association.serializer)
|
assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer)
|
||||||
when :description
|
when :description
|
||||||
assert_instance_of(PostSerializer::DescriptionSerializer, association.serializer)
|
assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer)
|
||||||
else
|
else
|
||||||
flunk "Unknown association: #{key}"
|
flunk "Unknown association: #{key}"
|
||||||
end
|
end
|
||||||
@ -260,7 +286,7 @@ module ActiveModel
|
|||||||
def test_conditional_associations
|
def test_conditional_associations
|
||||||
model = Class.new(::Model) do
|
model = Class.new(::Model) do
|
||||||
attributes :true, :false
|
attributes :true, :false
|
||||||
associations :association
|
associations :something
|
||||||
end.new(true: true, false: false)
|
end.new(true: true, false: false)
|
||||||
|
|
||||||
scenarios = [
|
scenarios = [
|
||||||
@ -284,7 +310,7 @@ module ActiveModel
|
|||||||
|
|
||||||
scenarios.each do |s|
|
scenarios.each do |s|
|
||||||
serializer = Class.new(ActiveModel::Serializer) do
|
serializer = Class.new(ActiveModel::Serializer) do
|
||||||
belongs_to :association, s[:options]
|
belongs_to :something, s[:options]
|
||||||
|
|
||||||
def true
|
def true
|
||||||
true
|
true
|
||||||
@ -296,7 +322,7 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
hash = serializable(model, serializer: serializer).serializable_hash
|
hash = serializable(model, serializer: serializer).serializable_hash
|
||||||
assert_equal(s[:included], hash.key?(:association), "Error with #{s[:options]}")
|
assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -341,8 +367,8 @@ module ActiveModel
|
|||||||
@author_serializer = AuthorSerializer.new(@author)
|
@author_serializer = AuthorSerializer.new(@author)
|
||||||
@inherited_post_serializer = InheritedPostSerializer.new(@post)
|
@inherited_post_serializer = InheritedPostSerializer.new(@post)
|
||||||
@inherited_author_serializer = InheritedAuthorSerializer.new(@author)
|
@inherited_author_serializer = InheritedAuthorSerializer.new(@author)
|
||||||
@author_associations = @author_serializer.associations.to_a
|
@author_associations = @author_serializer.associations.to_a.sort_by(&:name)
|
||||||
@inherited_author_associations = @inherited_author_serializer.associations.to_a
|
@inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name)
|
||||||
@post_associations = @post_serializer.associations.to_a
|
@post_associations = @post_serializer.associations.to_a
|
||||||
@inherited_post_associations = @inherited_post_serializer.associations.to_a
|
@inherited_post_associations = @inherited_post_serializer.associations.to_a
|
||||||
end
|
end
|
||||||
@ -361,28 +387,35 @@ module ActiveModel
|
|||||||
|
|
||||||
test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
|
test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
|
||||||
expected = [:roles, :bio].sort
|
expected = [:roles, :bio].sort
|
||||||
result = (@inherited_author_associations - @author_associations).map(&:name).sort
|
result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name)
|
||||||
assert_equal(result, expected)
|
assert_equal(result, expected)
|
||||||
|
assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?)
|
||||||
|
assert_equal [false, false, false], @author_associations.map(&:polymorphic?)
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
|
test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
|
||||||
assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
|
assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
|
||||||
assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
|
assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
|
||||||
|
|
||||||
refute @post_associations.detect { |assoc| assoc.name == :author }.options.key?(:polymorphic)
|
refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
|
||||||
assert_equal true, @inherited_post_associations.detect { |assoc| assoc.name == :author }.options.fetch(:polymorphic)
|
assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
|
||||||
|
|
||||||
refute @post_associations.detect { |assoc| assoc.name == :comments }.options.key?(:key)
|
refute @post_associations.detect { |assoc| assoc.name == :comments }.key?
|
||||||
original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
|
original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
|
||||||
refute original_comment_assoc.options.key?(:key)
|
refute original_comment_assoc.key?
|
||||||
assert_equal :reviews, new_comments_assoc.options.fetch(:key)
|
assert_equal :reviews, new_comments_assoc.key
|
||||||
|
|
||||||
assert_equal @post_associations.detect { |assoc| assoc.name == :blog }, @inherited_post_associations.detect { |assoc| assoc.name == :blog }
|
original_blog = @post_associations.detect { |assoc| assoc.name == :blog }
|
||||||
|
inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog }
|
||||||
|
original_parent_serializer = original_blog.lazy_association.association_options.delete(:parent_serializer)
|
||||||
|
inherited_parent_serializer = inherited_blog.lazy_association.association_options.delete(:parent_serializer)
|
||||||
|
assert_equal PostSerializer, original_parent_serializer.class
|
||||||
|
assert_equal InheritedPostSerializer, inherited_parent_serializer.class
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
|
test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
|
||||||
expected = [:author, :comments, :blog, :reviews].sort
|
expected = [:author, :comments, :blog, :reviews].sort
|
||||||
result = @inherited_post_serializer.associations.map { |a| a.options.fetch(:key, a.name) }.sort
|
result = @inherited_post_serializer.associations.map(&:key).sort
|
||||||
assert_equal(result, expected)
|
assert_equal(result, expected)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
427
test/serializers/reflection_test.rb
Normal file
427
test/serializers/reflection_test.rb
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
class ReflectionTest < ActiveSupport::TestCase
|
||||||
|
class Blog < ActiveModelSerializers::Model
|
||||||
|
attributes :id
|
||||||
|
end
|
||||||
|
class BlogSerializer < ActiveModel::Serializer
|
||||||
|
type 'blog'
|
||||||
|
attributes :id
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@expected_meta = { id: 1 }
|
||||||
|
@expected_links = { self: 'no_uri_validation' }
|
||||||
|
@empty_links = {}
|
||||||
|
model_attributes = { blog: Blog.new(@expected_meta) }
|
||||||
|
@model = Class.new(ActiveModelSerializers::Model) do
|
||||||
|
attributes(*model_attributes.keys)
|
||||||
|
|
||||||
|
def self.name
|
||||||
|
'TestModel'
|
||||||
|
end
|
||||||
|
end.new(model_attributes)
|
||||||
|
@instance_options = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def evaluate_association_value(association)
|
||||||
|
association.lazy_association.eval_reflection_block
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Remaining tests
|
||||||
|
# test_reflection_value_block_with_scope
|
||||||
|
# test_reflection_value_uses_serializer_instance_method
|
||||||
|
# test_reflection_excluded_eh_blank_is_false
|
||||||
|
# test_reflection_excluded_eh_if
|
||||||
|
# test_reflection_excluded_eh_unless
|
||||||
|
# test_evaluate_condition_symbol_serializer_method
|
||||||
|
# test_evaluate_condition_string_serializer_method
|
||||||
|
# test_evaluate_condition_proc
|
||||||
|
# test_evaluate_condition_proc_yields_serializer
|
||||||
|
# test_evaluate_condition_other
|
||||||
|
# test_options_key
|
||||||
|
# test_options_polymorphic
|
||||||
|
# test_options_serializer
|
||||||
|
# test_options_virtual_value
|
||||||
|
# test_options_namespace
|
||||||
|
|
||||||
|
def test_reflection_value
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_nil reflection.block
|
||||||
|
assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting)
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
|
||||||
|
include_slice = :does_not_matter
|
||||||
|
assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_value_block
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
object.blog
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_respond_to reflection.block, :call
|
||||||
|
assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting)
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
|
||||||
|
include_slice = :does_not_matter
|
||||||
|
assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_value_block_with_explicit_include_data_true
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
include_data true
|
||||||
|
object.blog
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_respond_to reflection.block, :call
|
||||||
|
assert_equal Serializer.config.include_data_default, reflection.options.fetch(:include_data_setting)
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
|
||||||
|
include_slice = :does_not_matter
|
||||||
|
assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_value_block_with_include_data_false_mutates_the_reflection_include_data
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
include_data false
|
||||||
|
object.blog
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_respond_to reflection.block, :call
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
include_slice = :does_not_matter
|
||||||
|
assert_nil reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
assert_equal false, reflection.options.fetch(:include_data_setting)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_value_block_with_include_data_if_sideloaded_included_mutates_the_reflection_include_data
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
object.blog
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_respond_to reflection.block, :call
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
include_slice = {}
|
||||||
|
assert_nil reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
assert_equal :if_sideloaded, reflection.options.fetch(:include_data_setting)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_value_block_with_include_data_if_sideloaded_excluded_mutates_the_reflection_include_data
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
object.blog
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_respond_to reflection.block, :call
|
||||||
|
assert_equal true, reflection.options.fetch(:include_data_setting)
|
||||||
|
include_slice = { blog: :does_not_matter }
|
||||||
|
assert_equal @model.blog, reflection.send(:value, serializer_instance, include_slice)
|
||||||
|
assert_equal :if_sideloaded, reflection.options.fetch(:include_data_setting)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_block_with_link_mutates_the_reflection_links
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
link :self, 'no_uri_validation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_equal @empty_links, reflection.options.fetch(:links)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
|
||||||
|
# Assert association links empty when not yet evaluated
|
||||||
|
assert_equal @empty_links, reflection.options.fetch(:links)
|
||||||
|
assert_equal @empty_links, association.links
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
assert_equal @expected_links, association.links
|
||||||
|
assert_equal @expected_links, reflection.options.fetch(:links)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_block_with_link_block_mutates_the_reflection_links
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
link :self do
|
||||||
|
'no_uri_validation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_equal @empty_links, reflection.options.fetch(:links)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
|
||||||
|
# Assert association links empty when not yet evaluated
|
||||||
|
assert_equal @empty_links, association.links
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
# Assert before instance_eval link
|
||||||
|
link = association.links.fetch(:self)
|
||||||
|
assert_respond_to link, :call
|
||||||
|
assert_respond_to reflection.options.fetch(:links).fetch(:self), :call
|
||||||
|
|
||||||
|
# Assert after instance_eval link
|
||||||
|
assert_equal @expected_links.fetch(:self), reflection.instance_eval(&link)
|
||||||
|
assert_respond_to reflection.options.fetch(:links).fetch(:self), :call
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_block_with_meta_mutates_the_reflection_meta
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
meta(id: object.blog.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
assert_equal @expected_meta, association.meta
|
||||||
|
assert_equal @expected_meta, reflection.options.fetch(:meta)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reflection_block_with_meta_block_mutates_the_reflection_meta
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
meta do
|
||||||
|
{ id: object.blog.id }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
# Assert before instance_eval meta
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
assert_respond_to association.meta, :call
|
||||||
|
assert_respond_to reflection.options.fetch(:meta), :call
|
||||||
|
|
||||||
|
# Assert after instance_eval meta
|
||||||
|
assert_equal @expected_meta, reflection.instance_eval(&association.meta)
|
||||||
|
assert_respond_to reflection.options.fetch(:meta), :call
|
||||||
|
assert_respond_to association.meta, :call
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
def test_reflection_block_with_meta_in_link_block_mutates_the_reflection_meta
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
link :self do
|
||||||
|
meta(id: object.blog.id)
|
||||||
|
'no_uri_validation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
assert_equal @empty_links, reflection.options.fetch(:links)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
# Assert before instance_eval link meta
|
||||||
|
assert_nil association.meta
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
link = association.links.fetch(:self)
|
||||||
|
assert_respond_to link, :call
|
||||||
|
assert_respond_to reflection.options.fetch(:links).fetch(:self), :call
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
# Assert after instance_eval link
|
||||||
|
assert_equal 'no_uri_validation', reflection.instance_eval(&link)
|
||||||
|
assert_equal @expected_meta, reflection.options.fetch(:meta)
|
||||||
|
assert_equal @expected_meta, association.meta
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
def test_reflection_block_with_meta_block_in_link_block_mutates_the_reflection_meta
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
link :self do
|
||||||
|
meta do
|
||||||
|
{ id: object.blog.id }
|
||||||
|
end
|
||||||
|
'no_uri_validation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
assert_nil association.meta
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
# Assert before instance_eval link
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
link = association.links.fetch(:self)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
assert_respond_to link, :call
|
||||||
|
assert_respond_to association.links.fetch(:self), :call
|
||||||
|
|
||||||
|
# Assert after instance_eval link
|
||||||
|
assert_equal 'no_uri_validation', reflection.instance_eval(&link)
|
||||||
|
assert_respond_to association.links.fetch(:self), :call
|
||||||
|
# Assert before instance_eval link meta
|
||||||
|
assert_respond_to reflection.options.fetch(:meta), :call
|
||||||
|
assert_respond_to association.meta, :call
|
||||||
|
|
||||||
|
# Assert after instance_eval link meta
|
||||||
|
assert_equal @expected_meta, reflection.instance_eval(&reflection.options.fetch(:meta))
|
||||||
|
assert_respond_to association.meta, :call
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
|
||||||
|
def test_no_href_in_vanilla_reflection
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
link :self do
|
||||||
|
href 'no_uri_validation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
|
||||||
|
# Get Reflection
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_equal @empty_links, reflection.options.fetch(:links)
|
||||||
|
|
||||||
|
# Build Association
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
# Assert before instance_eval link
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
link = association.links.fetch(:self)
|
||||||
|
assert_respond_to link, :call
|
||||||
|
|
||||||
|
# Assert after instance_eval link
|
||||||
|
exception = assert_raise(NoMethodError) do
|
||||||
|
reflection.instance_eval(&link)
|
||||||
|
end
|
||||||
|
assert_match(/undefined method `href'/, exception.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
def test_mutating_reflection_block_is_not_thread_safe
|
||||||
|
serializer_class = Class.new(ActiveModel::Serializer) do
|
||||||
|
has_one :blog do
|
||||||
|
meta(id: object.blog.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
model1_meta = @expected_meta
|
||||||
|
# Evaluate reflection meta for model with id 1
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
assert_nil reflection.options.fetch(:meta)
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
assert_equal model1_meta, association.meta
|
||||||
|
assert_equal model1_meta, reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
model2_meta = @expected_meta.merge(id: 2)
|
||||||
|
# Evaluate reflection meta for model with id 2
|
||||||
|
@model.blog.id = 2
|
||||||
|
assert_equal 2, @model.blog.id # sanity check
|
||||||
|
serializer_instance = serializer_class.new(@model, @instance_options)
|
||||||
|
reflection = serializer_class._reflections.fetch(:blog)
|
||||||
|
|
||||||
|
# WARN: Thread-safety issue
|
||||||
|
# Before the reflection is evaluated, it has the value from the previous evaluation
|
||||||
|
assert_equal model1_meta, reflection.options.fetch(:meta)
|
||||||
|
|
||||||
|
association = reflection.build_association(serializer_instance, @instance_options)
|
||||||
|
|
||||||
|
evaluate_association_value(association)
|
||||||
|
|
||||||
|
assert_equal model2_meta, association.meta
|
||||||
|
assert_equal model2_meta, reflection.options.fetch(:meta)
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user