From 1a8526239f66c5a0fe85d4f305aa6abfeb10b757 Mon Sep 17 00:00:00 2001 From: Bounmy Stephane Date: Fri, 16 Dec 2011 11:51:57 -0800 Subject: [PATCH 1/7] fixed doc : ActiveModel::Serializer takes care of object initialization --- README.textile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.textile b/README.textile index 2b7b2acd..6743b8ac 100644 --- a/README.textile +++ b/README.textile @@ -193,10 +193,6 @@ JSON. In the above example, the +title+ and +body+ attributes were always includ class PostSerializer < ActiveModel::Serializer attributes :title, :body - def initialize(post, scope) - @post, @scope = post, scope - end - def serializable_hash hash = attributes hash.merge!(super_data) if super? @@ -355,7 +351,7 @@ h4. Modifying Associations You can also rename associations if required. Say for example you have an association that makes sense to be named one thing in your code, but another when data is serialized. -You can use the option to specify a different name for an association. +You can use the option to specify a different name for an association. Here is an exmaple:

From ac23a9e10e7f0c52f9a2e719f1a608c8ddb72ecd Mon Sep 17 00:00:00 2001
From: Yehuda Katz 
Date: Fri, 16 Dec 2011 14:49:40 -0800
Subject: [PATCH 2/7] The serialization_scope should default to current_user

---
 lib/action_controller/serialization.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb
index 97d1cd51..0cb1ef63 100644
--- a/lib/action_controller/serialization.rb
+++ b/lib/action_controller/serialization.rb
@@ -29,6 +29,7 @@ module ActionController
 
     included do
       class_attribute :_serialization_scope
+      self._serialization_scope = :current_user
     end
 
     def serialization_scope

From d0cc1e3065bc0058481a54f5acb9a3f15c46d5ba Mon Sep 17 00:00:00 2001
From: Josh Kalderimis 
Date: Sat, 17 Dec 2011 00:03:23 +0100
Subject: [PATCH 3/7] lets test against MOAR RUBIES

---
 .travis.yml | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 .travis.yml

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..213fbaf1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+rvm:
+  - 1.8.7
+  - 1.9.2
+  - 1.9.3
+  - ree
+  - jruby
+  - rbx
\ No newline at end of file

From 8bdb7da272e01a19a859cb771f076ed6d8874816 Mon Sep 17 00:00:00 2001
From: Yehuda Katz 
Date: Wed, 7 Dec 2011 16:33:28 -0800
Subject: [PATCH 4/7] Fix compatibility mistakes with ActiveRecord

---
 lib/active_model/serializer.rb | 6 +++---
 test/serializer_test.rb        | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb
index 474b2d9d..a5074edf 100644
--- a/lib/active_model/serializer.rb
+++ b/lib/active_model/serializer.rb
@@ -198,8 +198,8 @@ module ActiveModel
         columns = klass.columns_hash
 
         attrs = _attributes.inject({}) do |hash, (name,key)|
-          column = columns[name]
-          hash.merge key => column[:type]
+          column = columns[name.to_s]
+          hash.merge key => column.type
         end
 
         associations = _associations.inject({}) do |hash, association|
@@ -212,7 +212,7 @@ module ActiveModel
 
       # The model class associated with this serializer.
       def model_class
-        name.sub(/Serializer$/, '')
+        name.sub(/Serializer$/, '').constantize
       end
 
       # Define how associations should be embedded.
diff --git a/test/serializer_test.rb b/test/serializer_test.rb
index b4e50161..db882efb 100644
--- a/test/serializer_test.rb
+++ b/test/serializer_test.rb
@@ -557,7 +557,7 @@ class SerializerTest < ActiveModel::TestCase
     Class.new do
       class << self
         def columns_hash
-          { :name => { :type => :string }, :age => { :type => :integer } }
+          { "name" => Struct.new(:type).new(:string), "age" => Struct.new(:type).new(:integer) }
         end
 
         def reflect_on_association(name)

From df9ad0ef116daf7e183e8cb3a5a20fb36a529889 Mon Sep 17 00:00:00 2001
From: Yehuda Katz 
Date: Tue, 20 Dec 2011 00:03:29 -0800
Subject: [PATCH 5/7] Improve serializers:

* Many nested levels of associations can now insert
  their information alongside the original when
  embed :ids, :include => true is true
* Add support for passing options to serializers
* Fix Array serializers so they don't try to insert
  roots for each child object and so they can be
  provided a root.
* TODO: Array serializers should require a root
* TODO: Make merging associations at the root more
  efficient if possible
---
 lib/action_controller/serialization.rb |   2 +-
 lib/active_model/serializer.rb         |  50 +++++++---
 test/serialization_test.rb             |  20 +++-
 test/serializer_test.rb                | 131 ++++++++++++++++++++++++-
 test/test_helper.rb                    |   4 +-
 5 files changed, 184 insertions(+), 23 deletions(-)

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"

From cbb6389f5980dd541814687dd387bcd4ad6e3de3 Mon Sep 17 00:00:00 2001
From: Yehuda Katz 
Date: Tue, 20 Dec 2011 00:06:50 -0800
Subject: [PATCH 6/7] Add status button

---
 README.textile | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/README.textile b/README.textile
index 6743b8ac..07d7107f 100644
--- a/README.textile
+++ b/README.textile
@@ -1,3 +1,6 @@
+[![Build Status](https://secure.travis-ci.org/josevalim/active_model_serializers.png)](http://travis-ci.org/josevalim/active_model_serializers)
+
+
 h2. Rails Serializers
 
 This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn:

From e7d66ee42361b483f972d89bff3ea5a6bb898260 Mon Sep 17 00:00:00 2001
From: Yehuda Katz 
Date: Tue, 20 Dec 2011 00:14:27 -0800
Subject: [PATCH 7/7] we use textile, not markdown, duh

---
 README.textile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.textile b/README.textile
index 07d7107f..5750cae7 100644
--- a/README.textile
+++ b/README.textile
@@ -1,4 +1,4 @@
-[![Build Status](https://secure.travis-ci.org/josevalim/active_model_serializers.png)](http://travis-ci.org/josevalim/active_model_serializers)
+"!https://secure.travis-ci.org/josevalim/active_model_serializers.png!":http://travis-ci.org/josevalim/active_model_serializers
 
 
 h2. Rails Serializers