Skip to content

Define polymorphic association serializers #2491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: 0-9-stable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png?branch=0-9-stable)](https://travis-ci.org/rails-api/active_model_serializers)
[![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)

## This Fork

Adds polymorphic support for serializers

Allows the the passed in `context` option to be passed through to a `has_many`
association (by modifying the `ArraySerializer`). This can be used by
specifying `:context` as a key when invoking the serializer, either via `.new`
or via `respond_with`/`to_json`.

Context example:

respond_with(:api, :v2, @event, serializer: ::API::V2::EventSerializer, context: { render_guest: true }) do |format|
# Render code
end

# ActiveModel::Serializers

## Purpose
Expand Down Expand Up @@ -235,7 +250,6 @@ end
You can specify that serializers use the lower-camel key format at the config, class or instance level.

```ruby

ActiveModel::Serializer.setup do |config|
config.key_format = :lower_camel
end
Expand All @@ -251,11 +265,11 @@ BlogSerializer.new(object, key_format: :lower_camel)

You can specify that serializers use unsuffixed names as association keys by default.

`````ruby
```ruby
ActiveModel::Serializer.setup do |config|
config.default_key_type = :name
end
````
```

This will build association keys like `comments` or `author` instead of `comment_ids` or `author_id`.

Expand Down Expand Up @@ -805,6 +819,29 @@ end
}
```

You can also define serializers for a polymorphic relationship like so:


```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title
has_many :attachments, polymorphic: true, each_serializer: { video: SpecialVideoSerializer, audio: AudioSummarySerializer }
end

```

Or, for `has_one` relationships:

```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title
has_one :attachment, polymorphic: true, serializer: { video: SpecialVideoSerializer, audio: AudioSummarySerializer }
end
```

In this case, if the `attachment` is a Video object, instead of using the
standard VideoSerializer, the SpecialVideoSerializer will be used. Any other
objects not defined by the hash will use the default serializers.

## Customizing Scope

Expand Down
6 changes: 3 additions & 3 deletions lib/action_controller/serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def serialization_scope(scope)
private

def namespace_for_serializer
@namespace_for_serializer ||= self.class.parent unless self.class.parent == Object
@namespace_for_serializer ||= self.class.module_parent unless self.class.module_parent == Object
end

def default_serializer(resource)
Expand All @@ -87,8 +87,8 @@ def build_json_serializer(resource, options = {})
if serializer = options.fetch(:serializer, default_serializer(resource))
options[:scope] = serialization_scope unless options.has_key?(:scope)

if resource.respond_to?(:each)
options[:resource_name] = controller_name
if resource.respond_to?(:each) && !resource.is_a?(Hash)
options[:resource_name] = controller_name
options[:namespace] = namespace_for_serializer if namespace_for_serializer
end

Expand Down
8 changes: 5 additions & 3 deletions lib/active_model/array_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ def initialize(object, options={})
@polymorphic = options.fetch(:polymorphic, false)
@meta_key = options[:meta_key] || :meta
@meta = options[@meta_key]
@each_serializer = options[:each_serializer]
@resource_name = options[:resource_name]
@only = options[:only] ? Array(options[:only]) : nil
@except = options[:except] ? Array(options[:except]) : nil
@namespace = options[:namespace]
@key_format = options[:key_format] || options[:each_serializer].try(:key_format)
@each_serializer = options[:each_serializer]
@each_serializer_from_options = @each_serializer.is_a?(Hash) ? nil : @each_serializer
@context = options[:context]
end
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format

Expand All @@ -34,8 +36,8 @@ def json_key
end

def serializer_for(item)
serializer_class = @each_serializer || Serializer.serializer_for(item, namespace: @namespace) || DefaultSerializer
serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except, polymorphic: @polymorphic, namespace: @namespace)
serializer_class = @each_serializer_from_options || Serializer.serializer_for(item, namespace: @namespace) || DefaultSerializer
serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except, polymorphic: @polymorphic, namespace: @namespace, context: @context)
end

def serializable_object(options={})
Expand Down
13 changes: 13 additions & 0 deletions lib/active_model/polymorphic_array_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module ActiveModel
class PolymorphicArraySerializer < ArraySerializer
def serializer_for(item)
if @each_serializer.is_a?(Hash) && polymorphic?
polymorphic_object_key = item.class.name.underscore.to_sym
return nil unless @each_serializer.has_key?(polymorphic_object_key)
@each_serializer[polymorphic_object_key]
else
super
end
end
end
end
4 changes: 2 additions & 2 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def format_keys(format)
attr_reader :key_format

def serializer_for(resource, options = {})
if resource.respond_to?(:each)
if resource.respond_to?(:each) && !resource.is_a?(Hash)
if Object.constants.include?(:ArraySerializer)
::ArraySerializer
else
Expand Down Expand Up @@ -230,7 +230,7 @@ def association_options_for_serializer(association)
prefix = association.options[:prefix]
namespace = association.options[:namespace] || @namespace || self.namespace

{ scope: scope }.tap do |opts|
{ scope: scope, context: context }.tap do |opts|
opts[:namespace] = namespace if namespace
opts[:prefix] = prefix if prefix
end
Expand Down
18 changes: 15 additions & 3 deletions lib/active_model/serializer/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ def initialize(name, options={})
@embed_in_root_key = options.fetch(:embed_in_root_key) { CONFIG.embed_in_root_key }
@embed_namespace = options.fetch(:embed_namespace) { CONFIG.embed_namespace }

serializer = @options[:serializer]
@serializer_from_options = serializer.is_a?(String) ? serializer.constantize : serializer
@serializer = options[:serializer]
@serializer_from_options = if @serializer.is_a?(Hash)
nil
elsif @serializer.is_a?(String)
@serializer.constantize
else
@serializer
end
end

attr_reader :name, :embed_ids, :embed_objects, :polymorphic
Expand All @@ -43,7 +49,13 @@ def embed=(embed)
end

def serializer_from_object(object, options = {})
Serializer.serializer_for(object, options)
if @serializer.is_a?(Hash) && polymorphic?
polymorphic_object_key = object.class.name.underscore.to_sym
return nil unless @serializer.has_key?(polymorphic_object_key)
@serializer[polymorphic_object_key]
else
Serializer.serializer_for(object, options)
end
end

def default_serializer
Expand Down
4 changes: 3 additions & 1 deletion lib/active_model/serializer/association/has_many.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'active_model/polymorphic_array_serializer'

module ActiveModel
class Serializer
class Association
Expand All @@ -13,7 +15,7 @@ def initialize(name, *args)

def serializer_class(object, _)
if use_array_serializer?
ArraySerializer
polymorphic? ? PolymorphicArraySerializer : ArraySerializer
else
serializer_from_options
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_model/serializer/association/has_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ def build_serializer(object, options = {})
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ScaffoldControllerGenerator
if Rails::VERSION::MAJOR >= 4
source_root File.expand_path('../templates', __FILE__)

hook_for :serializer, default: true
hook_for :serializer, default: true, type: :boolean
end
end
end
Expand Down
16 changes: 16 additions & 0 deletions test/integration/action_controller/serialization_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,21 @@ def test_render_array
assert_equal '{"my":[{"name":"Name 1"}]}', @response.body
end
end

class SimpleHashObjectTest < ActionController::TestCase
class MyController < ActionController::Base
def render_hash
render json: { name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }
end
end

tests MyController

def test_render_hash
get :render_hash
assert_equal 'application/json', @response.content_type
assert_equal '{"name":"Name 1","description":"Description 1","comments":"Comments 1"}', @response.body
end
end
end
end