diff options
| -rw-r--r-- | activemodel/lib/active_model/serialization.rb | 33 | ||||
| -rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 40 | ||||
| -rw-r--r-- | activemodel/test/cases/serialization_test.rb | 73 | ||||
| -rw-r--r-- | activemodel/test/cases/serializers/xml_serialization_test.rb | 52 | ||||
| -rw-r--r-- | activerecord/lib/active_record/serialization.rb | 40 | ||||
| -rw-r--r-- | activerecord/lib/active_record/serializers/xml_serializer.rb | 42 | 
6 files changed, 190 insertions, 90 deletions
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 0b4067257e..9260c5082d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -77,7 +77,38 @@ module ActiveModel        end        method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } -      Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] +      hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] + +      serializable_add_includes(options) do |association, records, opts| +        hash[association] = if records.is_a?(Enumerable) +          records.map { |a| a.serializable_hash(opts) } +        else +          records.serializable_hash(opts) +        end +      end + +      hash      end + +    private +      # Add associations specified via the <tt>:include</tt> option. +      # +      # Expects a block that takes as arguments: +      #   +association+ - name of the association +      #   +records+     - the association record(s) to be serialized +      #   +opts+        - options for the association records +      def serializable_add_includes(options = {}) +        return unless include = options[:include] + +        unless include.is_a?(Hash) +          include = Hash[Array.wrap(include).map { |n| [n, {}] }] +        end + +        include.each do |association, opts| +          if records = send(association) +            yield association, records, opts +          end +        end +      end    end  end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 9812af43d6..64dda3bcee 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -101,6 +101,7 @@ module ActiveModel            @builder.tag!(*args) do              add_attributes_and_methods +            add_includes              add_extra_behavior              add_procs              yield @builder if block_given? @@ -120,6 +121,45 @@ module ActiveModel            end          end +        def add_includes +          @serializable.send(:serializable_add_includes, options) do |association, records, opts| +            add_associations(association, records, opts) +          end +        end + +        # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. +        def add_associations(association, records, opts) +          merged_options = opts.merge(options.slice(:builder, :indent)) +          merged_options[:skip_instruct] = true + +          if records.is_a?(Enumerable) +            tag  = ActiveSupport::XmlMini.rename_key(association.to_s, options) +            type = options[:skip_types] ? { } : {:type => "array"} +            association_name = association.to_s.singularize +            merged_options[:root] = association_name + +            if records.empty? +              @builder.tag!(tag, type) +            else +              @builder.tag!(tag, type) do +                records.each do |record| +                  if options[:skip_types] +                    record_type = {} +                  else +                    record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name +                    record_type = {:type => record_class} +                  end + +                  record.to_xml merged_options.merge(record_type) +                end +              end +            end +          else +            merged_options[:root] = association.to_s +            records.to_xml(merged_options) +          end +        end +          def add_procs            if procs = options.delete(:procs)              Array.wrap(procs).each do |proc| diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index 10cb600e7b..5122f08eec 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -1,13 +1,19 @@  require "cases/helper" +require 'active_support/core_ext/object/instance_variables'  class SerializationTest < ActiveModel::TestCase    class User      include ActiveModel::Serialization -    attr_accessor :name, :email, :gender +    attr_accessor :name, :email, :gender, :address, :friends + +    def initialize(name, email, gender) +      @name, @email, @gender = name, email, gender +      @friends = [] +    end      def attributes -      @attributes ||= {'name' => 'nil', 'email' => 'nil', 'gender' => 'nil'} +      instance_values.except("address", "friends")      end      def foo @@ -15,11 +21,25 @@ class SerializationTest < ActiveModel::TestCase      end    end +  class Address +    include ActiveModel::Serialization + +    attr_accessor :street, :city, :state, :zip + +    def attributes +      instance_values +    end +  end +    setup do -    @user = User.new -    @user.name = 'David' -    @user.email = 'david@example.com' -    @user.gender = 'male' +    @user = User.new('David', 'david@example.com', 'male') +    @user.address = Address.new +    @user.address.street = "123 Lane" +    @user.address.city = "Springfield" +    @user.address.state = "CA" +    @user.address.zip = 11111 +    @user.friends = [User.new('Joe', 'joe@example.com', 'male'), +                     User.new('Sue', 'sue@example.com', 'female')]    end    def test_method_serializable_hash_should_work @@ -57,4 +77,45 @@ class SerializationTest < ActiveModel::TestCase      assert_equal expected , @user.serializable_hash(:methods => [:bar])    end +  def test_include_option_with_singular_association +    expected =  {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", +                 :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} +    assert_equal expected , @user.serializable_hash(:include => :address) +  end + +  def test_include_option_with_plural_association +    expected =  {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", +                 :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, +                            {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} +    assert_equal expected , @user.serializable_hash(:include => :friends) +  end + +  def test_include_option_with_empty_association +    @user.friends = [] +    expected =  {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} +    assert_equal expected , @user.serializable_hash(:include => :friends) +  end + +  def test_multiple_includes +    expected =  {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", +                 :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, +                 :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, +                            {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} +    assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) +  end + +  def test_include_with_options +    expected =  {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", +                 :address=>{"street"=>"123 Lane"}} +    assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) +  end + +  def test_nested_include +    @user.friends.first.friends = [@user] +    expected =  {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", +                 :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', +                             :friends => ["email"=>"david@example.com", "gender"=>"male", "name"=>"David"]}, +                            {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} +    assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) +  end  end diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index f978191d22..a38ef8e223 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -7,9 +7,11 @@ class Contact    extend ActiveModel::Naming    include ActiveModel::Serializers::Xml +  attr_accessor :address, :friends +    def attributes -    instance_values -  end unless method_defined?(:attributes) +    instance_values.except("address", "friends") +  end  end  module Admin @@ -20,6 +22,17 @@ end  class Customer < Struct.new(:name)  end +class Address +  extend ActiveModel::Naming +  include ActiveModel::Serializers::Xml + +  attr_accessor :street, :city, :state, :zip + +  def attributes +    instance_values +  end +end +  class XmlSerializationTest < ActiveModel::TestCase    def setup      @contact = Contact.new @@ -30,6 +43,12 @@ class XmlSerializationTest < ActiveModel::TestCase      customer = Customer.new      customer.name = "John"      @contact.preferences = customer +    @contact.address = Address.new +    @contact.address.street = "123 Lane" +    @contact.address.city = "Springfield" +    @contact.address.state = "CA" +    @contact.address.zip = 11111 +    @contact.friends = [Contact.new, Contact.new]    end    test "should serialize default root" do @@ -138,4 +157,33 @@ class XmlSerializationTest < ActiveModel::TestCase      assert_match %r{<contact type="Contact">}, xml      assert_match %r{<name>aaron stack</name>}, xml    end + +  test "include option with singular association" do +    xml = @contact.to_xml :include => :address, :indent => 0 +    assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) +  end + +  test "include option with plural association" do +    xml = @contact.to_xml :include => :friends, :indent => 0 +    assert_match %r{<friends type="array">}, xml +    assert_match %r{<friend type="Contact">}, xml +  end + +  test "multiple includes" do +    xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ] +    assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) +    assert_match %r{<friends type="array">}, xml +    assert_match %r{<friend type="Contact">}, xml +  end + +  test "include with options" do +    xml = @contact.to_xml :indent  => 0, :skip_instruct => true, :include => { :address => { :only => :city } } +    assert xml.include?(%(><address><city>Springfield</city></address>)) +  end + +  test "propagates skip_types option to included associations" do +    xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true +    assert_match %r{<friends>}, xml +    assert_match %r{<friend>}, xml +  end  end diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index e3185a9c5a..5ad40d8cd9 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -10,46 +10,8 @@ module ActiveRecord #:nodoc:        options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }        options[:except] |= Array.wrap(self.class.inheritance_column) -      hash = super(options) - -      serializable_add_includes(options) do |association, records, opts| -        hash[association] = records.is_a?(Enumerable) ? -          records.map { |r| r.serializable_hash(opts) } : -          records.serializable_hash(opts) -      end - -      hash +      super(options)      end - -    private -      # Add associations specified via the <tt>:include</tt> option. -      # -      # Expects a block that takes as arguments: -      #   +association+ - name of the association -      #   +records+     - the association record(s) to be serialized -      #   +opts+        - options for the association records -      def serializable_add_includes(options = {}) -        return unless include_associations = options.delete(:include) - -        include_has_options = include_associations.is_a?(Hash) -        associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - -        associations.each do |association| -          records = case self.class.reflect_on_association(association).macro -          when :has_many, :has_and_belongs_to_many -            send(association).to_a -          when :has_one, :belongs_to -            send(association) -          end - -          if records -            association_options = include_has_options ? include_associations[association] : {} -            yield(association, records, association_options) -          end -        end - -        options[:include] = include_associations -      end    end  end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 67f6c7833c..cbfa1ad609 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -182,48 +182,6 @@ module ActiveRecord #:nodoc:        options[:except] |= Array.wrap(@serializable.class.inheritance_column)      end -    def add_extra_behavior -      add_includes -    end - -    def add_includes -      procs = options.delete(:procs) -      @serializable.send(:serializable_add_includes, options) do |association, records, opts| -        add_associations(association, records, opts) -      end -      options[:procs] = procs -    end - -    # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. -    def add_associations(association, records, opts) -      association_name = association.to_s.singularize -      merged_options   = options.merge(opts).merge!(:root => association_name, :skip_instruct => true) - -      if records.is_a?(Enumerable) -        tag  = ActiveSupport::XmlMini.rename_key(association.to_s, options) -        type = options[:skip_types] ? { } : {:type => "array"} - -        if records.empty? -          @builder.tag!(tag, type) -        else -          @builder.tag!(tag, type) do -            records.each do |record| -              if options[:skip_types] -                record_type = {} -              else -                record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name -                record_type = {:type => record_class} -              end - -              record.to_xml merged_options.merge(record_type) -            end -          end -        end -      else -        records.to_xml(merged_options) -      end -    end -      class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:        def compute_type          klass = @serializable.class  | 
