diff --git a/README.markdown b/README.md similarity index 67% rename from README.markdown rename to README.md index 94292668..aeb19a39 100644 --- a/README.markdown +++ b/README.md @@ -39,7 +39,7 @@ $ rails g resource post title:string body:string This will generate a serializer in `app/serializers/post_serializer.rb` for your new model. You can also generate a serializer for an existing model with -the `serializer generator`: +the serializer generator: ``` $ rails g serializer post @@ -66,10 +66,108 @@ end In this case, Rails will look for a serializer named `PostSerializer`, and if it exists, use it to serialize the `Post`. -This also works with `render_with`, which uses `to_json` under the hood. Also +This also works with `respond_with`, which uses `to_json` under the hood. Also note that any options passed to `render :json` will be passed to your serializer and available as `@options` inside. +To specify a custom serializer for an object, there are 2 options: + +#### 1. Specify the serializer in your model: + +```ruby +class Post < ActiveRecord::Base + def active_model_serializer + FancyPostSerializer + end +end +``` + +#### 2. Specify the serializer when you render the object: + +```ruby +render :json => @post, :serializer => FancyPostSerializer +``` + +## Arrays + +In your controllers, when you use `render :json` for an array of objects, AMS will +use `ActiveModel::ArraySerializer` (included in this project) as the base serializer, +and the individual `Serializer` for the objects contained in that array. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :title, :body +end + +class PostsController < ApplicationController + def index + @posts = Post.all + render :json => @posts + end +end +``` + +Given the example above, the index action will return + +```json +{ + "posts": + [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] +} +``` + +By default, the root element is the name of the controller. For example, `PostsController` +generates a root element "posts". To change it: + +```ruby +render :json => @posts, :root => "some_posts" +``` + +You may disable the root element for arrays at the top level, which will result in +more concise json. To disable the root element for arrays, you have 3 options: + +#### 1. Disable root globally for in `ArraySerializer`. In an initializer: + +```ruby +ActiveModel::ArraySerializer.root = false +``` + +#### 2. Disable root per render call in your controller: + +```ruby +render :json => @posts, :root => false +``` + +#### 3. Create a custom `ArraySerializer` and render arrays with it: + +```ruby +class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = false +end + +# controller: +render :json => @posts, :serializer => CustomArraySerializer +``` + +Disabling the root element of the array with any of the above 3 methods +will produce + +```json +[ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } +] +``` + +To specify a custom serializer for the items within an array: + +```ruby +render :json => @posts, :each_serializer => FancyPostSerializer +``` + ## Getting the old version If you find that your project is already relying on the old rails to_json @@ -107,6 +205,45 @@ class PostSerializer < ActiveModel::Serializer end ``` +## Custom Attributes + +If you would like customize your JSON to include things beyond the simple +attributes of the model, you can override its `attributes` method +to return anything you need. + +The most common scenario to use this feature is when an attribute +depends on a serialization scope. By default, the current user of your +application will be available in your serializer under the method +`scope`. This allows you to check for permissions before adding +an attribute. For example: + +```ruby +class Person < ActiveRecord::Base + def full_name + "#{first_name} #{last_name}" + end +end + +class PersonSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + + def attributes + hash = super + hash["full_name"] = object.full_name if scope.admin? + hash + end +end +``` + +The serialization scope can be customized in your controller by +calling `serialization_scope`: + +```ruby +class ApplicationController < ActionController::Base + serialization_scope :current_admin +end +``` + ## Associations For specified associations, the serializer will look up the association and @@ -126,12 +263,12 @@ class PostSerializer < ActiveModel::Serializer # only let the user see comments he created. def comments - post.comments.where(:created_by => options[:scope]) + post.comments.where(:created_by => scope) end end ``` -In a serializer, `options[:scope]` is the current authorization scope (usually +In a serializer, `scope` is the current authorization scope (usually `current_user`), which the controller gives to the serializer when you call `render :json` diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 00061b74..47bd7100 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,14 @@ +# VERSION 0.6 (to be released) + +* Serialize sets properly +* Add root option to ArraySerializer +* Support polymorphic associations +* Support :each_serializer in ArraySerializer +* Add `scope` method to easily access the scope in the serializer +* Fix regression with Rails 3.2.6 +* Allow serialization_scope to be disabled with serialization_scope nil +* Array serializer should support pure ruby objects besides serializers + # VERSION 0.5 (May 16, 2012) * First tagged version diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 4230e1d0..b4cbc73d 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -40,15 +40,19 @@ module ActionController end def _render_option_json(json, options) - if json.respond_to?(:to_ary) - options[:root] ||= controller_name unless options[:root] == false - end - serializer = options.delete(:serializer) || (json.respond_to?(:active_model_serializer) && json.active_model_serializer) + if json.respond_to?(:to_ary) + if options[:root] != false && serializer.root != false + # default root element for arrays is serializer's root or the controller name + # the serializer for an Array is ActiveModel::ArraySerializer + options[:root] ||= serializer.root || controller_name + end + end + if serializer - options[:scope] = serialization_scope + options[:scope] = serialization_scope unless options.has_key?(:scope) options[:url_options] = url_options json = serializer.new(json, options.merge(default_serializer_options || {})) end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index fa1cfd19..cd743e4c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -29,18 +29,31 @@ module ActiveModel # Active Model Array Serializer # - # It serializes an array checking if each element that implements + # It serializes an Array, checking if each element that implements # the +active_model_serializer+ method. + # + # To disable serialization of root elements: + # + # ActiveModel::ArraySerializer.root = false + # class ArraySerializer attr_reader :object, :options + class_attribute :root + def initialize(object, options={}) @object, @options = object, options end def serializable_array @object.map do |item| - if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer) + if @options.has_key? :each_serializer + serializer = @options[:each_serializer] + elsif item.respond_to?(:active_model_serializer) + serializer = item.active_model_serializer + end + + if serializer serializer.new(item, @options) else item @@ -72,7 +85,7 @@ module ActiveModel # # Provides a basic serializer implementation that allows you to easily # control how a given object is going to be serialized. On initialization, - # it expects to object as arguments, a resource and options. For example, + # it expects two objects as arguments, a resource and options. For example, # one may do in a controller: # # PostSerializer.new(@post, :scope => current_user).to_json @@ -81,7 +94,7 @@ module ActiveModel # in for authorization purposes. # # We use the scope to check if a given attribute should be serialized or not. - # For example, some attributes maybe only be returned if +current_user+ is the + # For example, some attributes may only be returned if +current_user+ is the # author of the post: # # class PostSerializer < ActiveModel::Serializer @@ -97,11 +110,23 @@ module ActiveModel # end # # def author? - # post.author == options[:scope] + # post.author == scope # end # end # class Serializer + class IncludeError < StandardError + attr_reader :source, :association + + def initialize(source, association) + @source, @association = source, association + end + + def to_s + "Cannot serialize #{association} when #{source} does not have a root!" + end + end + module Associations #:nodoc: class Config #:nodoc: class_attribute :options @@ -176,6 +201,10 @@ module ActiveModel option(:include, source_serializer._root_embed) end + def embeddable? + !associated_object.nil? + end + protected def find_serializable(object) @@ -210,13 +239,41 @@ module ActiveModel end class HasOne < Config #:nodoc: + def embeddable? + if polymorphic? && associated_object.nil? + false + else + true + end + end + + def polymorphic? + option :polymorphic + end + + def polymorphic_key + associated_object.class.to_s.demodulize.underscore.to_sym + end + def plural_key - key.to_s.pluralize.to_sym + if polymorphic? + associated_object.class.to_s.pluralize.demodulize.underscore.to_sym + else + key.to_s.pluralize.to_sym + end end def serialize object = associated_object - object && find_serializable(object).serializable_hash + + if object && polymorphic? + { + :type => polymorphic_key, + polymorphic_key => find_serializable(object).serializable_hash + } + elsif object + find_serializable(object).serializable_hash + end end def serialize_many @@ -226,7 +283,14 @@ module ActiveModel end def serialize_ids - if object = associated_object + object = associated_object + + if object && polymorphic? + { + :type => polymorphic_key, + :id => object.read_attribute_for_serialization(:id) + } + elsif object object.read_attribute_for_serialization(:id) else nil @@ -257,10 +321,12 @@ module ActiveModel end def attribute(attr, options={}) - self._attributes = _attributes.merge(attr => options[:key] || attr) + self._attributes = _attributes.merge(attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym) unless method_defined?(attr) - class_eval "def #{attr}() object.read_attribute_for_serialization(:#{attr}) end", __FILE__, __LINE__ + define_method attr do + object.read_attribute_for_serialization(attr.to_sym) + end end end @@ -270,7 +336,9 @@ module ActiveModel attrs.each do |attr| unless method_defined?(attr) - class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__ + define_method attr do + object.send attr + end end self._associations[attr] = klass.refine(attr, options) @@ -387,7 +455,7 @@ module ActiveModel end def url_options - @options[:url_options] + @options[:url_options] || {} end # Returns a json representation of the serializable @@ -475,7 +543,9 @@ module ActiveModel if association.embed_ids? node[association.key] = association.serialize_ids - if association.embed_in_root? + if association.embed_in_root? && hash.nil? + raise IncludeError.new(self.class, association.name) + elsif association.embed_in_root? && association.embeddable? merge_association hash, association.root, association.serialize_many, unique_values end elsif association.embed_objects? @@ -514,6 +584,11 @@ module ActiveModel hash end + # Returns options[:scope] + def scope + @options[:scope] + end + alias :read_attribute_for_serialization :send # Use ActiveSupport::Notifications to send events to external systems. @@ -523,10 +598,3 @@ module ActiveModel end end end - -class Array - # Array uses ActiveModel::ArraySerializer. - def active_model_serializer - ActiveModel::ArraySerializer - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 38f4044e..3d44bac6 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/string/inflections" require "active_support/notifications" require "active_model" require "active_model/serializer" +require "set" if defined?(Rails) module ActiveModel @@ -57,6 +58,19 @@ ActiveSupport.on_load(:active_record) do include ActiveModel::SerializerSupport end +module ActiveModel::ArraySerializerSupport + def active_model_serializer + ActiveModel::ArraySerializer + end +end + +Array.send(:include, ActiveModel::ArraySerializerSupport) +Set.send(:include, ActiveModel::ArraySerializerSupport) + +ActiveSupport.on_load(:active_record) do + ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport) +end + begin require 'action_controller' require 'action_controller/serialization' diff --git a/test/serialization_test.rb b/test/serialization_test.rb index d382f2ce..2bcc1c9a 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -66,6 +66,10 @@ class RenderJsonTest < ActionController::TestCase end end + class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = "items" + end + class TestController < ActionController::Base protect_from_forgery @@ -127,13 +131,25 @@ class RenderJsonTest < ActionController::TestCase render :json => JsonSerializable.new, :options => true end + def render_json_with_serializer_and_scope_option + @current_user = Struct.new(:as_json).new(:current_user => true) + scope = Struct.new(:as_json).new(:current_user => false) + render :json => JsonSerializable.new, :scope => scope + end + def render_json_with_serializer_api_but_without_serializer @current_user = Struct.new(:as_json).new(:current_user => true) render :json => JsonSerializable.new(true) end + # To specify a custom serializer for an object, use :serializer. def render_json_with_custom_serializer - render :json => [], :serializer => CustomSerializer + render :json => Object.new, :serializer => CustomSerializer + end + + # To specify a custom serializer for each item in the Array, use :each_serializer. + def render_json_array_with_custom_serializer + render :json => [Object.new], :each_serializer => CustomSerializer end def render_json_with_links @@ -144,6 +160,14 @@ class RenderJsonTest < ActionController::TestCase render :json => [], :root => false end + def render_json_empty_array + render :json => [] + end + + def render_json_array_with_custom_array_serializer + render :json => [], :serializer => CustomArraySerializer + end + private def default_serializer_options @@ -241,6 +265,11 @@ class RenderJsonTest < ActionController::TestCase assert_match '"options":true', @response.body end + def test_render_json_with_serializer_and_scope_option + get :render_json_with_serializer_and_scope_option + assert_match '"scope":{"current_user":false}', @response.body + end + def test_render_json_with_serializer_api_but_without_serializer get :render_json_with_serializer_api_but_without_serializer assert_match '{"serializable_object":true}', @response.body @@ -251,6 +280,11 @@ class RenderJsonTest < ActionController::TestCase assert_match '{"hello":true}', @response.body end + def test_render_json_array_with_custom_serializer + get :render_json_array_with_custom_serializer + assert_match '{"test":[{"hello":true}]}', @response.body + end + def test_render_json_with_links get :render_json_with_links assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body @@ -260,4 +294,23 @@ class RenderJsonTest < ActionController::TestCase get :render_json_array_with_no_root assert_equal '[]', @response.body end + + def test_render_json_empty_array + get :render_json_empty_array + assert_equal '{"test":[]}', @response.body + end + + def test_render_json_empty_arry_with_array_serializer_root_false + ActiveModel::ArraySerializer.root = false + get :render_json_empty_array + assert_equal '[]', @response.body + ensure # teardown + ActiveModel::ArraySerializer.root = nil + end + + def test_render_json_array_with_custom_array_serializer + get :render_json_array_with_custom_array_serializer + assert_equal '{"items":[]}', @response.body + end + end diff --git a/test/serializer_support_test.rb b/test/serializer_support_test.rb index b2d2b8be..2e963027 100644 --- a/test/serializer_support_test.rb +++ b/test/serializer_support_test.rb @@ -4,8 +4,27 @@ class RandomModel include ActiveModel::SerializerSupport end +class RandomModelCollection + include ActiveModel::ArraySerializerSupport +end + +module ActiveRecord + class Relation + end +end + class SerializerSupportTest < ActiveModel::TestCase test "it returns nil if no serializer exists" do assert_equal nil, RandomModel.new.active_model_serializer end -end \ No newline at end of file + + test "it returns ArraySerializer for a collection" do + assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer + end + + test "it automatically includes array_serializer in active_record/relation" do + ActiveSupport.run_load_hooks(:active_record) + assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer + end +end + diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 082cd6bf..8358178d 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -94,6 +94,11 @@ class SerializerTest < ActiveModel::TestCase has_many :comments, :serializer => CommentSerializer end + def test_scope_works_correct + serializer = ActiveModel::Serializer.new :foo, :scope => :bar + assert_equal serializer.scope, :bar + end + def test_attributes user = User.new user_serializer = DefaultUserSerializer.new(user, {}) @@ -138,6 +143,12 @@ class SerializerTest < ActiveModel::TestCase assert_equal({ :host => "test.local" }, user_serializer.url_options) end + def test_serializer_returns_empty_hash_without_url_options + user = User.new + user_serializer = UserSerializer.new(user) + assert_equal({}, user_serializer.url_options) + end + def test_pretty_accessors user = User.new user.superuser = true @@ -411,6 +422,40 @@ class SerializerTest < ActiveModel::TestCase assert_equal({ :items => [ hash.as_json ]}, serializer.as_json) end + class CustomPostSerializer < ActiveModel::Serializer + attributes :title + end + + def test_array_serializer_with_specified_seriailizer + post1 = Post.new(:title => "Post1", :author => "Author1", :id => 1) + post2 = Post.new(:title => "Post2", :author => "Author2", :id => 2) + + array = [ post1, post2 ] + + serializer = array.active_model_serializer.new array, :each_serializer => CustomPostSerializer + + assert_equal([ + { :title => "Post1" }, + { :title => "Post2" } + ], serializer.as_json) + end + + def test_sets_can_be_serialized + post1 = Post.new(:title => "Post1", :author => "Author1", :id => 1) + post2 = Post.new(:title => "Post2", :author => "Author2", :id => 2) + + set = Set.new + set << post1 + set << post2 + + serializer = set.active_model_serializer.new set, :each_serializer => CustomPostSerializer + + as_json = serializer.as_json + assert_equal 2, as_json.size + assert as_json.include?({ :title => "Post1" }) + assert as_json.include?({ :title => "Post2" }) + end + class CustomBlog < Blog attr_accessor :public_posts, :public_user end @@ -890,4 +935,358 @@ class SerializerTest < ActiveModel::TestCase end assert_equal ActiveModel::Serializer, loaded end + + def tests_query_attributes_strip_question_mark + todo = Class.new do + def overdue? + true + end + + def read_attribute_for_serialization(name) + send name + end + end + + serializer = Class.new(ActiveModel::Serializer) do + attribute :overdue? + end + + actual = serializer.new(todo.new).as_json + + assert_equal({ + :overdue => true + }, actual) + end + + def tests_query_attributes_allow_key_option + todo = Class.new do + def overdue? + true + end + + def read_attribute_for_serialization(name) + send name + end + end + + serializer = Class.new(ActiveModel::Serializer) do + attribute :overdue?, :key => :foo + end + + actual = serializer.new(todo.new).as_json + + assert_equal({ + :foo => true + }, actual) + end + + # Set up some classes for polymorphic testing + class Attachment < Model + def attachable + @attributes[:attachable] + end + + def readable + @attributes[:readable] + end + + def edible + @attributes[:edible] + end + end + + def tests_can_handle_polymorphism + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :subject => 'foo', :body => 'bar' + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :type => :email, + :email => { :subject => 'foo', :body => 'bar' } + } + }, actual) + end + + def test_can_handle_polymoprhic_ids + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + embed :ids + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :id => 1 + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :type => :email, + :id => 1 + } + }, actual) + end + + def test_polymorphic_associations_are_included_at_root + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body, :id + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + root :attachment + embed :ids, :include => true + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :id => 1, :subject => "Hello", :body => "World" + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :attachment => { + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :type => :email, + :id => 1 + }}, + :emails => [{ + :id => 1, + :subject => "Hello", + :body => "World" + }] + }, actual) + end + + def test_multiple_polymorphic_associations + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body, :id + end + + orange_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + + attributes :plu, :id + has_one :readable, :polymorphic => true + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + orange_class = Class.new(Model) do + def self.to_s + "Orange" + end + + def readable + @attributes[:readable] + end + + define_method :active_model_serializer do + orange_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + root :attachment + embed :ids, :include => true + + attributes :name, :url + + has_one :attachable, :polymorphic => true + has_one :readable, :polymorphic => true + has_one :edible, :polymorphic => true + end + + email = email_class.new :id => 1, :subject => "Hello", :body => "World" + orange = orange_class.new :id => 1, :plu => "3027", :readable => email + + attachment = Attachment.new({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => email, + :readable => email, + :edible => orange + }) + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :emails => [{ + :subject => "Hello", + :body => "World", + :id => 1 + }], + + :oranges => [{ + :plu => "3027", + :id => 1, + :readable => { :type => :email, :id => 1 } + }], + + :attachment => { + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { :type => :email, :id => 1 }, + :readable => { :type => :email, :id => 1 }, + :edible => { :type => :orange, :id => 1 } + } + }, actual) + end + + def test_raises_an_error_when_a_child_serializer_includes_associations_when_the_source_doesnt + attachment_serializer = Class.new(ActiveModel::Serializer) do + attributes :name + end + + fruit_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + has_one :attachment, :serializer => attachment_serializer + attribute :color + end + + banana_class = Class.new Model do + def self.to_s + 'banana' + end + + def attachment + @attributes[:attachment] + end + + define_method :active_model_serializer do + fruit_serializer + end + end + + strawberry_class = Class.new Model do + def self.to_s + 'strawberry' + end + + def attachment + @attributes[:attachment] + end + + define_method :active_model_serializer do + fruit_serializer + end + end + + smoothie = Class.new do + attr_reader :base, :flavor + + def initialize(base, flavor) + @base, @flavor = base, flavor + end + end + + smoothie_serializer = Class.new(ActiveModel::Serializer) do + root false + embed :ids, :include => true + + has_one :base, :polymorphic => true + has_one :flavor, :polymorphic => true + end + + banana_attachment = Attachment.new({ + :name => 'banana_blending.md', + :id => 3, + }) + + strawberry_attachment = Attachment.new({ + :name => 'strawberry_cleaning.doc', + :id => 4 + }) + + banana = banana_class.new :color => "yellow", :id => 1, :attachment => banana_attachment + strawberry = strawberry_class.new :color => "red", :id => 2, :attachment => strawberry_attachment + + smoothie = smoothie_serializer.new(smoothie.new(banana, strawberry)) + + assert_raise ActiveModel::Serializer::IncludeError do + smoothie.as_json + end + end + + def tests_includes_does_not_include_nil_polymoprhic_associations + post_serializer = Class.new(ActiveModel::Serializer) do + root :post + embed :ids, :include => true + has_one :author, :polymorphic => true + attributes :title + end + + post = Post.new(:title => 'Foo') + + actual = post_serializer.new(post).as_json + + assert_equal({ + :post => { + :title => 'Foo', + :author => nil + } + }, actual) + end end