diff options
| author | José Valim <jose.valim@gmail.com> | 2011-07-18 06:50:43 -0700 | 
|---|---|---|
| committer | José Valim <jose.valim@gmail.com> | 2011-07-18 06:50:43 -0700 | 
| commit | da144894e958770c84692065ec260aea2853afe9 (patch) | |
| tree | 025d3ffb8e1919905523c0648decf21d73bbdd06 | |
| parent | 44e83ac341809ab0f4154c71fc5232380a95ec07 (diff) | |
| parent | 4860143ee4ccafef474f14f40b8f70c2b6b54656 (diff) | |
| download | rails-da144894e958770c84692065ec260aea2853afe9.tar.gz rails-da144894e958770c84692065ec260aea2853afe9.tar.bz2 rails-da144894e958770c84692065ec260aea2853afe9.zip  | |
Merge pull request #195 from bigfix/active_model_include_serialization
ActiveModel support for the :include serialization option
| -rw-r--r-- | activemodel/lib/active_model/serialization.rb | 46 | ||||
| -rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 40 | ||||
| -rw-r--r-- | activemodel/test/cases/serialization_test.rb | 88 | ||||
| -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 | ||||
| -rw-r--r-- | activerecord/test/cases/serialization_test.rb | 142 | ||||
| -rw-r--r-- | activerecord/test/cases/xml_serialization_test.rb | 134 | 
8 files changed, 343 insertions, 241 deletions
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4a174cb62a..9260c5082d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -69,18 +69,46 @@ module ActiveModel      def serializable_hash(options = nil)        options ||= {} -      only   = Array.wrap(options[:only]).map(&:to_s) -      except = Array.wrap(options[:except]).map(&:to_s) -        attribute_names = attributes.keys.sort -      if only.any? -        attribute_names &= only -      elsif except.any? -        attribute_names -= except +      if only = options[:only] +        attribute_names &= Array.wrap(only).map(&:to_s) +      elsif except = options[:except] +        attribute_names -= Array.wrap(except).map(&:to_s) +      end + +      method_names = Array.wrap(options[:methods]).select { |n| respond_to?(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 -      method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact -      Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] +      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 8cc1ccb1e7..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 @@ -42,4 +62,60 @@ class SerializationTest < ActiveModel::TestCase      assert_equal expected , @user.serializable_hash(:methods => [:foo])    end +  def test_method_serializable_hash_should_work_with_only_and_methods +    expected =  {:foo=>"i_am_foo"} +    assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) +  end + +  def test_method_serializable_hash_should_work_with_except_and_methods +    expected =  {"gender"=>"male", :foo=>"i_am_foo"} +    assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) +  end + +  def test_should_not_call_methods_that_dont_respond +    expected =  {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} +    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 f8e6cf958c..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 -      elsif record = @serializable.send(association) -        record.to_xml(merged_options) -      end -    end -      class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:        def compute_type          klass = @serializable.class diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 677d659f39..382d659289 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,13 +1,8 @@  require "cases/helper"  require 'models/contact'  require 'models/topic' -require 'models/reply' -require 'models/company'  class SerializationTest < ActiveRecord::TestCase - -  fixtures :topics, :companies, :accounts -    FORMATS = [ :xml, :json ]    def setup @@ -19,8 +14,6 @@ class SerializationTest < ActiveRecord::TestCase        :awesome     => false,        :preferences => { :gem => '<strong>ruby</strong>' }      } - -    @contact = Contact.new(@contact_attributes)    end    def test_serialized_init_with @@ -29,134 +22,6 @@ class SerializationTest < ActiveRecord::TestCase      assert_equal 'foo', topic.content    end -  def test_to_xml -    xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) -    bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema -    written_on_in_current_timezone = topics(:first).written_on.xmlschema -    last_read_in_current_timezone = topics(:first).last_read.xmlschema - -    assert_equal "topic", xml.root.name -    assert_equal "The First Topic" , xml.elements["//title"].text -    assert_equal "David" , xml.elements["//author-name"].text -    assert_match "Have a nice day", xml.elements["//content"].text - -    assert_equal "1", xml.elements["//id"].text -    assert_equal "integer" , xml.elements["//id"].attributes['type'] - -    assert_equal "1", xml.elements["//replies-count"].text -    assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - -    assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text -    assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - -    assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - -    assert_equal nil, xml.elements["//parent-id"].text -    assert_equal "integer", xml.elements["//parent-id"].attributes['type'] -    assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - -    if current_adapter?(:SybaseAdapter) -      assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text -      assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] -    else -      # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) -      assert_equal "2004-04-15", xml.elements["//last-read"].text -      assert_equal "date" , xml.elements["//last-read"].attributes['type'] -    end - -    # Oracle and DB2 don't have true boolean or time-only fields -    unless current_adapter?(:OracleAdapter, :DB2Adapter) -      assert_equal "false", xml.elements["//approved"].text -      assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - -      assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text -      assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] -    end -  end - -  def test_to_xml_skipping_attributes -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) -    assert_equal "<topic>", xml.first(7) -    assert !xml.include?(%(<title>The First Topic</title>)) -    assert xml.include?(%(<author-name>David</author-name>)) - -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) -    assert !xml.include?(%(<title>The First Topic</title>)) -    assert !xml.include?(%(<author-name>David</author-name>)) -  end - -  def test_to_xml_including_has_many_association -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) -    assert_equal "<topic>", xml.first(7) -    assert xml.include?(%(<replies type="array"><reply>)) -    assert xml.include?(%(<title>The Second Topic of the day</title>)) -  end - -  def test_array_to_xml_including_has_many_association -    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) -    assert xml.include?(%(<replies type="array"><reply>)) -  end - -  def test_array_to_xml_including_methods -    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) -    assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml -    assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml -  end - -  def test_array_to_xml_including_has_one_association -    xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) -    assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) -  end - -  def test_array_to_xml_including_belongs_to_association -    xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) -  end - -  def test_to_xml_including_belongs_to_association -    xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert !xml.include?("<firm>") - -    xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert xml.include?("<firm>") -  end - -  def test_to_xml_including_multiple_associations -    xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) -    assert_equal "<firm>", xml.first(6) -    assert xml.include?(%(<account>)) -    assert xml.include?(%(<clients type="array"><client>)) -  end - -  def test_to_xml_including_multiple_associations_with_options -    xml = companies(:first_firm).to_xml( -      :indent  => 0, :skip_instruct => true, -      :include => { :clients => { :only => :name } } -    ) - -    assert_equal "<firm>", xml.first(6) -    assert xml.include?(%(<client><name>Summit</name></client>)) -    assert xml.include?(%(<clients type="array"><client>)) -  end - -  def test_to_xml_including_methods -    xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) -    assert_equal "<company>", xml.first(9) -    assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>)) -  end - -  def test_to_xml_with_block -    value = "Rockin' the block" -    xml = Company.new.to_xml(:skip_instruct => true) do |_xml| -      _xml.tag! "arbitrary-element", value -    end -    assert_equal "<company>", xml.first(9) -    assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) -  end -    def test_serialize_should_be_reversible      for format in FORMATS        @serialized = Contact.new.send("to_#{format}") @@ -184,11 +49,4 @@ class SerializationTest < ActiveRecord::TestCase        assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}"      end    end - -  def test_serialize_should_xml_skip_instruct_for_included_records -    @contact.alternative = Contact.new(:name => 'Copa Cabana') -    @serialized = @contact.to_xml(:include => [ :alternative ]) -    assert_equal @serialized.index('<?xml '), 0 -    assert_nil @serialized.index('<?xml ', 1) -  end  end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 756c8a32eb..88751a72f9 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -5,6 +5,9 @@ require 'models/author'  require 'models/comment'  require 'models/company_in_module'  require 'models/toy' +require 'models/topic' +require 'models/reply' +require 'models/company'  class XmlSerializationTest < ActiveRecord::TestCase    def test_should_serialize_default_root @@ -50,6 +53,23 @@ class XmlSerializationTest < ActiveRecord::TestCase      end      assert_match %r{<creator>David</creator>}, @xml    end + +  def test_to_xml_with_block +    value = "Rockin' the block" +    xml = Contact.new.to_xml(:skip_instruct => true) do |_xml| +      _xml.tag! "arbitrary-element", value +    end +    assert_equal "<contact>", xml.first(9) +    assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) +  end + +  def test_should_skip_instruct_for_included_records +    @contact = Contact.new +    @contact.alternative = Contact.new(:name => 'Copa Cabana') +    @xml = @contact.to_xml(:include => [ :alternative ]) +    assert_equal @xml.index('<?xml '), 0 +    assert_nil @xml.index('<?xml ', 1) +  end  end  class DefaultXmlSerializationTest < ActiveRecord::TestCase @@ -148,7 +168,63 @@ class NilXmlSerializationTest < ActiveRecord::TestCase  end  class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase -  fixtures :authors, :posts, :projects +  fixtures :topics, :companies, :accounts, :authors, :posts, :projects + +  def test_to_xml +    xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) +    bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema +    written_on_in_current_timezone = topics(:first).written_on.xmlschema +    last_read_in_current_timezone = topics(:first).last_read.xmlschema + +    assert_equal "topic", xml.root.name +    assert_equal "The First Topic" , xml.elements["//title"].text +    assert_equal "David" , xml.elements["//author-name"].text +    assert_match "Have a nice day", xml.elements["//content"].text + +    assert_equal "1", xml.elements["//id"].text +    assert_equal "integer" , xml.elements["//id"].attributes['type'] + +    assert_equal "1", xml.elements["//replies-count"].text +    assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] + +    assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text +    assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + +    assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text + +    assert_equal nil, xml.elements["//parent-id"].text +    assert_equal "integer", xml.elements["//parent-id"].attributes['type'] +    assert_equal "true", xml.elements["//parent-id"].attributes['nil'] + +    if current_adapter?(:SybaseAdapter) +      assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text +      assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] +    else +      # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) +      assert_equal "2004-04-15", xml.elements["//last-read"].text +      assert_equal "date" , xml.elements["//last-read"].attributes['type'] +    end + +    # Oracle and DB2 don't have true boolean or time-only fields +    unless current_adapter?(:OracleAdapter, :DB2Adapter) +      assert_equal "false", xml.elements["//approved"].text +      assert_equal "boolean" , xml.elements["//approved"].attributes['type'] + +      assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text +      assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] +    end +  end + +  def test_except_option +    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) +    assert_equal "<topic>", xml.first(7) +    assert !xml.include?(%(<title>The First Topic</title>)) +    assert xml.include?(%(<author-name>David</author-name>)) + +    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) +    assert !xml.include?(%(<title>The First Topic</title>)) +    assert !xml.include?(%(<author-name>David</author-name>)) +  end    # to_xml used to mess with the hash the user provided which    # caused the builder to be reused.  This meant the document kept @@ -184,6 +260,39 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase      assert_match %r{<hello-post>}, xml    end +  def test_including_has_many_association +    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) +    assert_equal "<topic>", xml.first(7) +    assert xml.include?(%(<replies type="array"><reply>)) +    assert xml.include?(%(<title>The Second Topic of the day</title>)) +  end + +  def test_including_belongs_to_association +    xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) +    assert !xml.include?("<firm>") + +    xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) +    assert xml.include?("<firm>") +  end + +  def test_including_multiple_associations +    xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) +    assert_equal "<firm>", xml.first(6) +    assert xml.include?(%(<account>)) +    assert xml.include?(%(<clients type="array"><client>)) +  end + +  def test_including_association_with_options +    xml = companies(:first_firm).to_xml( +      :indent  => 0, :skip_instruct => true, +      :include => { :clients => { :only => :name } } +    ) + +    assert_equal "<firm>", xml.first(6) +    assert xml.include?(%(<client><name>Summit</name></client>)) +    assert xml.include?(%(<clients type="array"><client>)) +  end +    def test_methods_are_called_on_object      xml = authors(:david).to_xml :methods => :label, :indent => 0      assert_match %r{<label>.*</label>}, xml @@ -265,4 +374,27 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase      assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size    end +  def test_array_to_xml_including_has_many_association +    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) +    assert xml.include?(%(<replies type="array"><reply>)) +  end + +  def test_array_to_xml_including_methods +    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) +    assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml +    assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml +  end + +  def test_array_to_xml_including_has_one_association +    xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) +    assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) +    assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) +  end + +  def test_array_to_xml_including_belongs_to_association +    xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) +    assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) +    assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) +    assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) +  end  end  | 
