diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 0cb1ef63..6f21512d 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -38,7 +38,7 @@ module ActionController def _render_option_json(json, options) if json.respond_to?(:active_model_serializer) && (serializer = json.active_model_serializer) - json = serializer.new(json, serialization_scope) + json = serializer.new(json, serialization_scope, options) end super end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index a5074edf..9aae21e8 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -11,14 +11,15 @@ module ActiveModel class ArraySerializer attr_reader :object, :scope - def initialize(object, scope) - @object, @scope = object, scope + def initialize(object, scope, options={}) + @object, @scope, @options = object, scope, options + @hash = options[:hash] end def serializable_array @object.map do |item| if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer) - serializer.new(item, scope) + serializer.new(item, scope, :hash => @hash) else item end @@ -26,7 +27,14 @@ module ActiveModel end def as_json(*args) - serializable_array.as_json(*args) + @hash = {} + array = serializable_array.as_json(*args) + + if root = @options[:root] + @hash.merge!(root => array) + else + array + end end end @@ -75,9 +83,9 @@ module ActiveModel end class HasMany < Config #:nodoc: - def serialize(collection, scope) + def serialize(collection, scope, options) collection.map do |item| - serializer.new(item, scope).serializable_hash + serializer.new(item, scope, options).serializable_hash end end @@ -92,8 +100,8 @@ module ActiveModel end class HasOne < Config #:nodoc: - def serialize(object, scope) - object && serializer.new(object, scope).serializable_hash + def serialize(object, scope, options) + object && serializer.new(object, scope, options).serializable_hash end def serialize_ids(object, scope) @@ -245,19 +253,20 @@ module ActiveModel attr_reader :object, :scope - def initialize(object, scope) - @object, @scope = object, scope + def initialize(object, scope, options={}) + @object, @scope, @options = object, scope, options + @hash = options[:hash] end # Returns a json representation of the serializable # object including the root. def as_json(*) - if _root - hash = { _root => serializable_hash } - hash.merge!(associations) if _root_embed + if root = @options[:root] || _root + @hash = hash = {} + hash.merge!(root => serializable_hash) hash else - serializable_hash + @hash = serializable_hash end end @@ -265,6 +274,7 @@ module ActiveModel # object without the root. def serializable_hash if _embed == :ids + merge_associations(@hash, associations) if _root_embed attributes.merge(association_ids) elsif _embed == :objects attributes.merge(associations) @@ -273,6 +283,16 @@ module ActiveModel end end + def merge_associations(hash, associations) + associations.each do |key, value| + if hash[key] + hash[key] |= value + elsif value + hash[key] = value + end + end + end + # Returns a hash representation of the serializable # object associations. def associations @@ -280,7 +300,7 @@ module ActiveModel _associations.each do |association| associated_object = send(association.name) - hash[association.key] = association.serialize(associated_object, scope) + hash[association.key] = association.serialize(associated_object, scope, :hash => @hash) end hash diff --git a/test/serialization_test.rb b/test/serialization_test.rb index 05abd862..b973f227 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -15,12 +15,14 @@ class RenderJsonTest < ActionController::TestCase end class JsonSerializer - def initialize(object, scope) - @object, @scope = object, scope + def initialize(object, scope, options={}) + @object, @scope, @options = object, scope, options end def as_json(*) - { :object => @object.as_json, :scope => @scope.as_json } + hash = { :object => @object.as_json, :scope => @scope.as_json } + hash.merge!(:options => true) if @options[:options] + hash end end @@ -89,6 +91,11 @@ class RenderJsonTest < ActionController::TestCase render :json => JsonSerializable.new end + def render_json_with_serializer_and_options + @current_user = Struct.new(:as_json).new(:current_user => true) + render :json => JsonSerializable.new, :options => true + 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) @@ -165,6 +172,13 @@ class RenderJsonTest < ActionController::TestCase assert_match '"object":{"serializable_object":true}', @response.body end + def test_render_json_with_serializer_and_options + get :render_json_with_serializer_and_options + assert_match '"scope":{"current_user":true}', @response.body + assert_match '"object":{"serializable_object":true}', @response.body + assert_match '"options":true', @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 diff --git a/test/serializer_test.rb b/test/serializer_test.rb index db882efb..44a340c1 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -70,7 +70,7 @@ class SerializerTest < ActiveModel::TestCase end class CommentSerializer - def initialize(comment, scope) + def initialize(comment, scope, options={}) @comment, @scope = comment, scope end @@ -680,4 +680,133 @@ class SerializerTest < ActiveModel::TestCase } }, hash.as_json) end + + def test_root_provided_in_options + author_serializer = Class.new(ActiveModel::Serializer) do + attributes :id, :name + end + + serializer_class = Class.new(ActiveModel::Serializer) do + root :post + + attributes :title, :body + has_one :author, :serializer => author_serializer + end + + post_class = Class.new(Model) do + attr_accessor :author + end + + author_class = Class.new(Model) + + post = post_class.new(:title => "New Post", :body => "It's a new post!") + author = author_class.new(:id => 5, :name => "Tom Dale") + post.author = author + + hash = serializer_class.new(post, nil, :root => :blog_post) + + assert_equal({ + :blog_post => { + :title => "New Post", + :body => "It's a new post!", + :author => { :id => 5, :name => "Tom Dale" } + } + }, hash.as_json) + end + + def test_serializer_has_access_to_root_object + hash_object = nil + + author_serializer = Class.new(ActiveModel::Serializer) do + attributes :id, :name + + define_method :serializable_hash do + hash_object = @hash + super() + end + end + + serializer_class = Class.new(ActiveModel::Serializer) do + root :post + + attributes :title, :body + has_one :author, :serializer => author_serializer + end + + post_class = Class.new(Model) do + attr_accessor :author + end + + author_class = Class.new(Model) + + post = post_class.new(:title => "New Post", :body => "It's a new post!") + author = author_class.new(:id => 5, :name => "Tom Dale") + post.author = author + + expected = serializer_class.new(post, nil).as_json + assert_equal expected, hash_object + end + + # the point of this test is to illustrate that deeply nested serializers + # still side-load at the root. + def test_embed_with_include_inserts_at_root + tag_serializer = Class.new(ActiveModel::Serializer) do + attributes :id, :name + end + + comment_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + attributes :id, :body + has_many :tags, :serializer => tag_serializer + end + + post_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + attributes :id, :title, :body + has_many :comments, :serializer => comment_serializer + end + + post_class = Class.new(Model) do + attr_accessor :comments + + define_method :active_model_serializer do + post_serializer + end + end + + comment_class = Class.new(Model) do + attr_accessor :tags + end + + tag_class = Class.new(Model) + + post = post_class.new(:title => "New Post", :body => "NEW POST", :id => 1) + comment1 = comment_class.new(:body => "EWOT", :id => 1) + comment2 = comment_class.new(:body => "YARLY", :id => 2) + tag1 = tag_class.new(:name => "lolcat", :id => 1) + tag2 = tag_class.new(:name => "nyancat", :id => 2) + tag3 = tag_class.new(:name => "violetcat", :id => 3) + + post.comments = [comment1, comment2] + comment1.tags = [tag1, tag3] + comment2.tags = [tag1, tag2] + + actual = ActiveModel::ArraySerializer.new([post], nil, :root => :posts).as_json + assert_equal({ + :posts => [ + { :title => "New Post", :body => "NEW POST", :id => 1, :comments => [1,2] } + ], + + :comments => [ + { :body => "EWOT", :id => 1, :tags => [1,3] }, + { :body => "YARLY", :id => 2, :tags => [1,2] } + ], + + :tags => [ + { :name => "lolcat", :id => 1 }, + { :name => "violetcat", :id => 3 }, + { :name => "nyancat", :id => 2 } + ] + }, actual) + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2942aa58..97ad9f52 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,7 +1,5 @@ require "rubygems" -require "bundler" - -Bundler.setup +require "bundler/setup" require "active_model_serializers" require "active_support/json"