mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Merge pull request #1225 from beauby/nested-serializer-lookup
Add support for nested serializers
This commit is contained in:
commit
6018ef16c4
@ -14,6 +14,7 @@ Breaking changes:
|
||||
|
||||
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)
|
||||
- [#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
|
||||
|
||||
@ -57,6 +57,37 @@ class CommentSerializer < ActiveModel::Serializer
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
@ -111,10 +111,25 @@ module ActiveModel
|
||||
Digest::MD5.hexdigest(serializer_file_contents)
|
||||
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)
|
||||
serializers_cache.fetch_or_store(klass) do
|
||||
serializer_class_name = "#{klass.name}Serializer"
|
||||
serializer_class = serializer_class_name.safe_constantize
|
||||
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
||||
serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x }
|
||||
|
||||
if serializer_class
|
||||
serializer_class
|
||||
|
||||
@ -11,9 +11,8 @@ module ActiveModel
|
||||
@root = options[:root]
|
||||
@object = resources
|
||||
@serializers = resources.map do |resource|
|
||||
serializer_class = options.fetch(:serializer) do
|
||||
ActiveModel::Serializer.serializer_for(resource)
|
||||
end
|
||||
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
|
||||
serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
|
||||
|
||||
if serializer_class.nil?
|
||||
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
|
||||
|
||||
@ -42,13 +42,13 @@ module ActiveModel
|
||||
def build_association(subject, parent_serializer_options)
|
||||
association_value = subject.send(name)
|
||||
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
|
||||
begin
|
||||
serializer = serializer_class.new(
|
||||
association_value,
|
||||
serializer_options(parent_serializer_options, reflection_options)
|
||||
serializer_options(subject, parent_serializer_options, reflection_options)
|
||||
)
|
||||
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
|
||||
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
|
||||
@ -62,11 +62,12 @@ module ActiveModel
|
||||
|
||||
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_options = parent_serializer_options.except(:serializer)
|
||||
serializer_options[:serializer] = serializer if serializer
|
||||
serializer_options[:serializer_context_class] = subject.class
|
||||
serializer_options
|
||||
end
|
||||
end
|
||||
|
||||
@ -125,6 +125,88 @@ module ActiveModel
|
||||
assert expected_association_keys.include? :writer
|
||||
assert expected_association_keys.include? :site
|
||||
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
|
||||
|
||||
@ -27,8 +27,19 @@ module ActiveModel
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
class CustomProfile
|
||||
def serializer_class; ProfileSerializer; end
|
||||
end
|
||||
@ -59,6 +70,18 @@ module ActiveModel
|
||||
serializer = ActiveModel::Serializer.serializer_for(@custom_profile)
|
||||
assert_equal ProfileSerializer, serializer
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user