mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Extend serializer lookup to the parent serializer.
This commit is contained in:
parent
d02cd30fe5
commit
9147469842
@ -14,6 +14,7 @@ Breaking changes:
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
|
- [#1225](https://github.com/rails-api/active_model_serializers/pull/1125) Better serializer lookup, use nested serializer when it exists (@beauby)
|
||||||
- [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4)
|
- [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4)
|
||||||
- [#1158](https://github.com/rails-api/active_model_serializers/pull/1158) Add support for wildcards in `include` option (@beauby)
|
- [#1158](https://github.com/rails-api/active_model_serializers/pull/1158) Add support for wildcards in `include` option (@beauby)
|
||||||
- [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested
|
- [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested
|
||||||
|
|||||||
@ -57,6 +57,37 @@ class CommentSerializer < ActiveModel::Serializer
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Namespaced Models
|
||||||
|
|
||||||
|
When serializing a model inside a namespace, such as `Api::V1::Post`, AMS will expect the corresponding serializer to be inside the same namespace (namely `Api::V1::PostSerializer`).
|
||||||
|
|
||||||
|
### Model Associations and Nested Serializers
|
||||||
|
|
||||||
|
When declaring a serializer for a model with associations, such as:
|
||||||
|
```ruby
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
has_many :comments
|
||||||
|
end
|
||||||
|
```
|
||||||
|
AMS will look for `PostSerializer::CommentSerializer` in priority, and fall back to `::CommentSerializer` in case the former does not exist. This allows for more control over the way a model gets serialized as an association of an other model.
|
||||||
|
|
||||||
|
For example, in the following situation:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
|
attributes :body, :date, :nb_likes
|
||||||
|
end
|
||||||
|
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
has_many :comments
|
||||||
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
|
attributes :body_short
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
AMS will use `PostSerializer::CommentSerializer` (thus including only the `:body_short` attribute) when serializing a `Comment` as part of a `Post`, but use `::CommentSerializer` when serializing a `Comment` directly (thus including `:body, :date, :nb_likes`).
|
||||||
|
|
||||||
## Rails Integration
|
## Rails Integration
|
||||||
|
|
||||||
AMS will automatically integrate with you Rails app, you won't need to update your controller, this is a example of how it will look like:
|
AMS will automatically integrate with you Rails app, you won't need to update your controller, this is a example of how it will look like:
|
||||||
|
|||||||
@ -108,10 +108,25 @@ module ActiveModel
|
|||||||
Digest::MD5.hexdigest(serializer_file_contents)
|
Digest::MD5.hexdigest(serializer_file_contents)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def self.serializer_lookup_chain_for(klass)
|
||||||
|
chain = []
|
||||||
|
|
||||||
|
resource_class_name = klass.name.demodulize
|
||||||
|
resource_namespace = klass.name.deconstantize
|
||||||
|
serializer_class_name = "#{resource_class_name}Serializer"
|
||||||
|
|
||||||
|
chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
|
||||||
|
chain.push("#{resource_namespace}::#{serializer_class_name}")
|
||||||
|
|
||||||
|
chain
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
def self.get_serializer_for(klass)
|
def self.get_serializer_for(klass)
|
||||||
serializers_cache.fetch_or_store(klass) do
|
serializers_cache.fetch_or_store(klass) do
|
||||||
serializer_class_name = "#{klass.name}Serializer"
|
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
||||||
serializer_class = serializer_class_name.safe_constantize
|
serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x }
|
||||||
|
|
||||||
if serializer_class
|
if serializer_class
|
||||||
serializer_class
|
serializer_class
|
||||||
|
|||||||
@ -11,9 +11,8 @@ module ActiveModel
|
|||||||
@root = options[:root]
|
@root = options[:root]
|
||||||
@object = resources
|
@object = resources
|
||||||
@serializers = resources.map do |resource|
|
@serializers = resources.map do |resource|
|
||||||
serializer_class = options.fetch(:serializer) do
|
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
|
||||||
ActiveModel::Serializer.serializer_for(resource)
|
serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
|
||||||
end
|
|
||||||
|
|
||||||
if serializer_class.nil?
|
if serializer_class.nil?
|
||||||
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
|
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
|
||||||
|
|||||||
@ -42,13 +42,13 @@ module ActiveModel
|
|||||||
def build_association(subject, parent_serializer_options)
|
def build_association(subject, parent_serializer_options)
|
||||||
association_value = subject.send(name)
|
association_value = subject.send(name)
|
||||||
reflection_options = options.dup
|
reflection_options = options.dup
|
||||||
serializer_class = ActiveModel::Serializer.serializer_for(association_value, reflection_options)
|
serializer_class = subject.class.serializer_for(association_value, reflection_options)
|
||||||
|
|
||||||
if serializer_class
|
if serializer_class
|
||||||
begin
|
begin
|
||||||
serializer = serializer_class.new(
|
serializer = serializer_class.new(
|
||||||
association_value,
|
association_value,
|
||||||
serializer_options(parent_serializer_options, reflection_options)
|
serializer_options(subject, parent_serializer_options, reflection_options)
|
||||||
)
|
)
|
||||||
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
||||||
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
|
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
|
||||||
@ -62,11 +62,12 @@ module ActiveModel
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def serializer_options(parent_serializer_options, reflection_options)
|
def serializer_options(subject, parent_serializer_options, reflection_options)
|
||||||
serializer = reflection_options.fetch(:serializer, nil)
|
serializer = reflection_options.fetch(:serializer, nil)
|
||||||
|
|
||||||
serializer_options = parent_serializer_options.except(:serializer)
|
serializer_options = parent_serializer_options.except(:serializer)
|
||||||
serializer_options[:serializer] = serializer if serializer
|
serializer_options[:serializer] = serializer if serializer
|
||||||
|
serializer_options[:serializer_context_class] = subject.class
|
||||||
serializer_options
|
serializer_options
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -125,6 +125,88 @@ module ActiveModel
|
|||||||
assert expected_association_keys.include? :writer
|
assert expected_association_keys.include? :writer
|
||||||
assert expected_association_keys.include? :site
|
assert expected_association_keys.include? :site
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class NamespacedResourcesTest < Minitest::Test
|
||||||
|
class ResourceNamespace
|
||||||
|
Post = Class.new(::Model)
|
||||||
|
Comment = Class.new(::Model)
|
||||||
|
Author = Class.new(::Model)
|
||||||
|
Description = Class.new(::Model)
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
has_many :comments
|
||||||
|
belongs_to :author
|
||||||
|
has_one :description
|
||||||
|
end
|
||||||
|
CommentSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
AuthorSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
DescriptionSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@comment = ResourceNamespace::Comment.new
|
||||||
|
@author = ResourceNamespace::Author.new
|
||||||
|
@description = ResourceNamespace::Description.new
|
||||||
|
@post = ResourceNamespace::Post.new(comments: [@comment],
|
||||||
|
author: @author,
|
||||||
|
description: @description)
|
||||||
|
@post_serializer = ResourceNamespace::PostSerializer.new(@post)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_associations_namespaced_resources
|
||||||
|
@post_serializer.associations.each do |association|
|
||||||
|
case association.key
|
||||||
|
when :comments
|
||||||
|
assert_instance_of(ResourceNamespace::CommentSerializer, association.serializer.first)
|
||||||
|
when :author
|
||||||
|
assert_instance_of(ResourceNamespace::AuthorSerializer, association.serializer)
|
||||||
|
when :description
|
||||||
|
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.serializer)
|
||||||
|
else
|
||||||
|
flunk "Unknown association: #{key}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NestedSerializersTest < Minitest::Test
|
||||||
|
Post = Class.new(::Model)
|
||||||
|
Comment = Class.new(::Model)
|
||||||
|
Author = Class.new(::Model)
|
||||||
|
Description = Class.new(::Model)
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
has_many :comments
|
||||||
|
CommentSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
belongs_to :author
|
||||||
|
AuthorSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
has_one :description
|
||||||
|
DescriptionSerializer = Class.new(ActiveModel::Serializer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@comment = Comment.new
|
||||||
|
@author = Author.new
|
||||||
|
@description = Description.new
|
||||||
|
@post = Post.new(comments: [@comment],
|
||||||
|
author: @author,
|
||||||
|
description: @description)
|
||||||
|
@post_serializer = PostSerializer.new(@post)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_associations_namespaced_resources
|
||||||
|
@post_serializer.associations.each do |association|
|
||||||
|
case association.key
|
||||||
|
when :comments
|
||||||
|
assert_instance_of(PostSerializer::CommentSerializer, association.serializer.first)
|
||||||
|
when :author
|
||||||
|
assert_instance_of(PostSerializer::AuthorSerializer, association.serializer)
|
||||||
|
when :description
|
||||||
|
assert_instance_of(PostSerializer::DescriptionSerializer, association.serializer)
|
||||||
|
else
|
||||||
|
flunk "Unknown association: #{key}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -27,8 +27,19 @@ module ActiveModel
|
|||||||
end
|
end
|
||||||
|
|
||||||
class SerializerTest < Minitest::Test
|
class SerializerTest < Minitest::Test
|
||||||
|
module ResourceNamespace
|
||||||
|
Post = Class.new(::Model)
|
||||||
|
Comment = Class.new(::Model)
|
||||||
|
|
||||||
|
class PostSerializer < ActiveModel::Serializer
|
||||||
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class MyProfile < Profile
|
class MyProfile < Profile
|
||||||
end
|
end
|
||||||
|
|
||||||
class CustomProfile
|
class CustomProfile
|
||||||
def serializer_class; ProfileSerializer; end
|
def serializer_class; ProfileSerializer; end
|
||||||
end
|
end
|
||||||
@ -59,6 +70,18 @@ module ActiveModel
|
|||||||
serializer = ActiveModel::Serializer.serializer_for(@custom_profile)
|
serializer = ActiveModel::Serializer.serializer_for(@custom_profile)
|
||||||
assert_equal ProfileSerializer, serializer
|
assert_equal ProfileSerializer, serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_serializer_for_namespaced_resource
|
||||||
|
post = ResourceNamespace::Post.new
|
||||||
|
serializer = ActiveModel::Serializer.serializer_for(post)
|
||||||
|
assert_equal(ResourceNamespace::PostSerializer, serializer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_serializer_for_nested_resource
|
||||||
|
comment = ResourceNamespace::Comment.new
|
||||||
|
serializer = ResourceNamespace::PostSerializer.serializer_for(comment)
|
||||||
|
assert_equal(ResourceNamespace::PostSerializer::CommentSerializer, serializer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user