mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Remove XML Serialization from core.
This includes the following classes: - ActiveModel::Serializers::Xml - ActiveRecord::Serialization::XmlSerializer
This commit is contained in:
parent
9b7ecf0d6d
commit
f7ebdb1ac5
13 changed files with 12 additions and 1204 deletions
|
@ -3,6 +3,10 @@
|
|||
|
||||
*Jay Elaraj*
|
||||
|
||||
* Remove `ActiveModel::Serializers::Xml` from core.
|
||||
|
||||
*Zachary Scott*
|
||||
|
||||
* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
|
||||
`ActiveModel::Dirty#[attr_name]_previous_change` to improve access
|
||||
to recorded changes after the model has been saved.
|
||||
|
|
|
@ -155,7 +155,7 @@ behavior out of the box:
|
|||
* Making objects serializable
|
||||
|
||||
<tt>ActiveModel::Serialization</tt> provides a standard interface for your object
|
||||
to provide +to_json+ or +to_xml+ serialization.
|
||||
to provide +to_json+ serialization.
|
||||
|
||||
class SerialPerson
|
||||
include ActiveModel::Serialization
|
||||
|
@ -177,13 +177,6 @@ behavior out of the box:
|
|||
s = SerialPerson.new
|
||||
s.to_json # => "{\"name\":null}"
|
||||
|
||||
class SerialPerson
|
||||
include ActiveModel::Serializers::Xml
|
||||
end
|
||||
|
||||
s = SerialPerson.new
|
||||
s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
||||
|
||||
{Learn more}[link:classes/ActiveModel/Serialization.html]
|
||||
|
||||
* Internationalization (i18n) support
|
||||
|
|
|
@ -58,7 +58,6 @@ module ActiveModel
|
|||
|
||||
eager_autoload do
|
||||
autoload :JSON
|
||||
autoload :Xml
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ module ActiveModel
|
|||
#
|
||||
# class Person
|
||||
# include ActiveModel::Serializers::JSON
|
||||
# include ActiveModel::Serializers::Xml
|
||||
#
|
||||
# attr_accessor :name
|
||||
#
|
||||
|
@ -55,13 +54,11 @@ module ActiveModel
|
|||
# person.serializable_hash # => {"name"=>nil}
|
||||
# person.as_json # => {"name"=>nil}
|
||||
# person.to_json # => "{\"name\":null}"
|
||||
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
||||
#
|
||||
# person.name = "Bob"
|
||||
# person.serializable_hash # => {"name"=>"Bob"}
|
||||
# person.as_json # => {"name"=>"Bob"}
|
||||
# person.to_json # => "{\"name\":\"Bob\"}"
|
||||
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
||||
#
|
||||
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
|
||||
# <tt>:include</tt>. The following are all valid examples:
|
||||
|
|
|
@ -1,238 +0,0 @@
|
|||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
require 'active_support/core_ext/array/conversions'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/time/acts_like'
|
||||
|
||||
module ActiveModel
|
||||
module Serializers
|
||||
# == \Active \Model XML Serializer
|
||||
module Xml
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Serialization
|
||||
|
||||
included do
|
||||
extend ActiveModel::Naming
|
||||
end
|
||||
|
||||
class Serializer #:nodoc:
|
||||
class Attribute #:nodoc:
|
||||
attr_reader :name, :value, :type
|
||||
|
||||
def initialize(name, serializable, value)
|
||||
@name, @serializable = name, serializable
|
||||
|
||||
if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
|
||||
value = value.in_time_zone
|
||||
end
|
||||
|
||||
@value = value
|
||||
@type = compute_type
|
||||
end
|
||||
|
||||
def decorations
|
||||
decorations = {}
|
||||
decorations[:encoding] = 'base64' if type == :binary
|
||||
decorations[:type] = (type == :string) ? nil : type
|
||||
decorations[:nil] = true if value.nil?
|
||||
decorations
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def compute_type
|
||||
return if value.nil?
|
||||
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
|
||||
type ||= :string if value.respond_to?(:to_str)
|
||||
type ||= :yaml
|
||||
type
|
||||
end
|
||||
end
|
||||
|
||||
class MethodAttribute < Attribute #:nodoc:
|
||||
end
|
||||
|
||||
attr_reader :options
|
||||
|
||||
def initialize(serializable, options = nil)
|
||||
@serializable = serializable
|
||||
@options = options ? options.dup : {}
|
||||
end
|
||||
|
||||
def serializable_hash
|
||||
@serializable.serializable_hash(@options.except(:include))
|
||||
end
|
||||
|
||||
def serializable_collection
|
||||
methods = Array(options[:methods]).map(&:to_s)
|
||||
serializable_hash.map do |name, value|
|
||||
name = name.to_s
|
||||
if methods.include?(name)
|
||||
self.class::MethodAttribute.new(name, @serializable, value)
|
||||
else
|
||||
self.class::Attribute.new(name, @serializable, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
require 'builder' unless defined? ::Builder
|
||||
|
||||
options[:indent] ||= 2
|
||||
options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
|
||||
|
||||
@builder = options[:builder]
|
||||
@builder.instruct! unless options[:skip_instruct]
|
||||
|
||||
root = (options[:root] || @serializable.model_name.element).to_s
|
||||
root = ActiveSupport::XmlMini.rename_key(root, options)
|
||||
|
||||
args = [root]
|
||||
args << { xmlns: options[:namespace] } if options[:namespace]
|
||||
args << { type: options[:type] } if options[:type] && !options[:skip_types]
|
||||
|
||||
@builder.tag!(*args) do
|
||||
add_attributes_and_methods
|
||||
add_includes
|
||||
add_extra_behavior
|
||||
add_procs
|
||||
yield @builder if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_extra_behavior
|
||||
end
|
||||
|
||||
def add_attributes_and_methods
|
||||
serializable_collection.each do |attribute|
|
||||
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
|
||||
ActiveSupport::XmlMini.to_tag(key, attribute.value,
|
||||
options.merge(attribute.decorations))
|
||||
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
|
||||
|
||||
[:skip_types, :dasherize, :camelize].each do |key|
|
||||
merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
|
||||
end
|
||||
|
||||
if records.respond_to?(:to_ary)
|
||||
records = records.to_ary
|
||||
|
||||
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
|
||||
|
||||
unless records.class.to_s.underscore == association.to_s
|
||||
merged_options[:type] = records.class.name
|
||||
end
|
||||
|
||||
records.to_xml merged_options
|
||||
end
|
||||
end
|
||||
|
||||
def add_procs
|
||||
if procs = options.delete(:procs)
|
||||
Array(procs).each do |proc|
|
||||
if proc.arity == 1
|
||||
proc.call(options)
|
||||
else
|
||||
proc.call(options, @serializable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns XML representing the model. Configuration can be
|
||||
# passed through +options+.
|
||||
#
|
||||
# Without any +options+, the returned XML string will include all the
|
||||
# model's attributes.
|
||||
#
|
||||
# user = User.find(1)
|
||||
# user.to_xml
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <user>
|
||||
# <id type="integer">1</id>
|
||||
# <name>David</name>
|
||||
# <age type="integer">16</age>
|
||||
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
|
||||
# </user>
|
||||
#
|
||||
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
|
||||
# attributes included, and work similar to the +attributes+ method.
|
||||
#
|
||||
# To include the result of some method calls on the model use <tt>:methods</tt>.
|
||||
#
|
||||
# To include associations use <tt>:include</tt>.
|
||||
#
|
||||
# For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
|
||||
def to_xml(options = {}, &block)
|
||||
Serializer.new(self, options).serialize(&block)
|
||||
end
|
||||
|
||||
# Sets the model +attributes+ from an XML string. Returns +self+.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Serializers::Xml
|
||||
#
|
||||
# attr_accessor :name, :age, :awesome
|
||||
#
|
||||
# def attributes=(hash)
|
||||
# hash.each do |key, value|
|
||||
# instance_variable_set("@#{key}", value)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def attributes
|
||||
# instance_values
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# xml = { name: 'bob', age: 22, awesome:true }.to_xml
|
||||
# person = Person.new
|
||||
# person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
|
||||
# person.name # => "bob"
|
||||
# person.age # => 22
|
||||
# person.awesome # => true
|
||||
def from_xml(xml)
|
||||
self.attributes = Hash.from_xml(xml).values.first
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,251 +0,0 @@
|
|||
require 'cases/helper'
|
||||
require 'models/contact'
|
||||
require 'active_support/core_ext/object/instance_variables'
|
||||
require 'ostruct'
|
||||
require 'yaml'
|
||||
|
||||
module Admin
|
||||
class Contact < ::Contact
|
||||
end
|
||||
end
|
||||
|
||||
class Customer < Struct.new(:name)
|
||||
end
|
||||
|
||||
class Address
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :street, :city, :state, :zip, :apt_number
|
||||
|
||||
def attributes
|
||||
instance_values
|
||||
end
|
||||
end
|
||||
|
||||
class SerializableContact < Contact
|
||||
def serializable_hash(options={})
|
||||
super(options.merge(only: [:name, :age]))
|
||||
end
|
||||
end
|
||||
|
||||
class XmlSerializationTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@contact = Contact.new
|
||||
@contact.name = 'aaron stack'
|
||||
@contact.age = 25
|
||||
@contact.created_at = Time.utc(2006, 8, 1)
|
||||
@contact.awesome = false
|
||||
customer = Customer.new
|
||||
customer.name = "John"
|
||||
@contact.preferences = customer
|
||||
@contact.address = Address.new
|
||||
@contact.address.city = "Springfield"
|
||||
@contact.address.apt_number = 35
|
||||
@contact.friends = [Contact.new, Contact.new]
|
||||
@contact.contact = SerializableContact.new
|
||||
end
|
||||
|
||||
test "should serialize default root" do
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{^<contact>}, xml
|
||||
assert_match %r{</contact>$}, xml
|
||||
end
|
||||
|
||||
test "should serialize namespaced root" do
|
||||
xml = Admin::Contact.new(@contact.attributes).to_xml
|
||||
assert_match %r{^<contact>}, xml
|
||||
assert_match %r{</contact>$}, xml
|
||||
end
|
||||
|
||||
test "should serialize default root with namespace" do
|
||||
xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
|
||||
assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, xml
|
||||
assert_match %r{</contact>$}, xml
|
||||
end
|
||||
|
||||
test "should serialize custom root" do
|
||||
xml = @contact.to_xml root: 'xml_contact'
|
||||
assert_match %r{^<xml-contact>}, xml
|
||||
assert_match %r{</xml-contact>$}, xml
|
||||
end
|
||||
|
||||
test "should allow undasherized tags" do
|
||||
xml = @contact.to_xml root: 'xml_contact', dasherize: false
|
||||
assert_match %r{^<xml_contact>}, xml
|
||||
assert_match %r{</xml_contact>$}, xml
|
||||
assert_match %r{<created_at}, xml
|
||||
end
|
||||
|
||||
test "should allow camelized tags" do
|
||||
xml = @contact.to_xml root: 'xml_contact', camelize: true
|
||||
assert_match %r{^<XmlContact>}, xml
|
||||
assert_match %r{</XmlContact>$}, xml
|
||||
assert_match %r{<CreatedAt}, xml
|
||||
end
|
||||
|
||||
test "should allow lower-camelized tags" do
|
||||
xml = @contact.to_xml root: 'xml_contact', camelize: :lower
|
||||
assert_match %r{^<xmlContact>}, xml
|
||||
assert_match %r{</xmlContact>$}, xml
|
||||
assert_match %r{<createdAt}, xml
|
||||
end
|
||||
|
||||
test "should use serializable hash" do
|
||||
@contact = SerializableContact.new
|
||||
@contact.name = 'aaron stack'
|
||||
@contact.age = 25
|
||||
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{<name>aaron stack</name>}, xml
|
||||
assert_match %r{<age type="integer">25</age>}, xml
|
||||
assert_no_match %r{<awesome>}, xml
|
||||
end
|
||||
|
||||
test "should allow skipped types" do
|
||||
xml = @contact.to_xml skip_types: true
|
||||
assert_match %r{<age>25</age>}, xml
|
||||
end
|
||||
|
||||
test "should include yielded additions" do
|
||||
xml_output = @contact.to_xml do |xml|
|
||||
xml.creator "David"
|
||||
end
|
||||
assert_match %r{<creator>David</creator>}, xml_output
|
||||
end
|
||||
|
||||
test "should serialize string" do
|
||||
assert_match %r{<name>aaron stack</name>}, @contact.to_xml
|
||||
end
|
||||
|
||||
test "should serialize nil" do
|
||||
assert_match %r{<pseudonyms nil="true"/>}, @contact.to_xml(methods: :pseudonyms)
|
||||
end
|
||||
|
||||
test "should serialize integer" do
|
||||
assert_match %r{<age type="integer">25</age>}, @contact.to_xml
|
||||
end
|
||||
|
||||
test "should serialize datetime" do
|
||||
assert_match %r{<created-at type="dateTime">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
|
||||
end
|
||||
|
||||
test "should serialize boolean" do
|
||||
assert_match %r{<awesome type="boolean">false</awesome>}, @contact.to_xml
|
||||
end
|
||||
|
||||
test "should serialize array" do
|
||||
assert_match %r{<social type="array">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social)
|
||||
end
|
||||
|
||||
test "should serialize hash" do
|
||||
assert_match %r{<network>\s*<git type="symbol">github</git>\s*</network>}, @contact.to_xml(methods: :network)
|
||||
end
|
||||
|
||||
test "should serialize yaml" do
|
||||
assert_match %r{<preferences type="yaml">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml
|
||||
end
|
||||
|
||||
test "should call proc on object" do
|
||||
proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') }
|
||||
xml = @contact.to_xml(procs: [ proc ])
|
||||
assert_match %r{<nationality>unknown</nationality>}, xml
|
||||
end
|
||||
|
||||
test "should supply serializable to second proc argument" do
|
||||
proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
|
||||
xml = @contact.to_xml(procs: [ proc ])
|
||||
assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml
|
||||
end
|
||||
|
||||
test "should serialize string correctly when type passed" do
|
||||
xml = @contact.to_xml type: 'Contact'
|
||||
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
|
||||
|
||||
class FriendList
|
||||
def initialize(friends)
|
||||
@friends = friends
|
||||
end
|
||||
|
||||
def to_ary
|
||||
@friends
|
||||
end
|
||||
end
|
||||
|
||||
test "include option with ary" do
|
||||
@contact.friends = FriendList.new(@contact.friends)
|
||||
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
|
||||
|
||||
test "propagates skip-types option to included associations and attributes" do
|
||||
xml = @contact.to_xml skip_types: true, include: :address, indent: 0
|
||||
assert_match %r{<address>}, xml
|
||||
assert_match %r{<apt-number>}, xml
|
||||
end
|
||||
|
||||
test "propagates camelize option to included associations and attributes" do
|
||||
xml = @contact.to_xml camelize: true, include: :address, indent: 0
|
||||
assert_match %r{<Address>}, xml
|
||||
assert_match %r{<AptNumber type="integer">}, xml
|
||||
end
|
||||
|
||||
test "propagates dasherize option to included associations and attributes" do
|
||||
xml = @contact.to_xml dasherize: false, include: :address, indent: 0
|
||||
assert_match %r{<apt_number type="integer">}, xml
|
||||
end
|
||||
|
||||
test "don't propagate skip_types if skip_types is defined at the included association level" do
|
||||
xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0
|
||||
assert_match %r{<address>}, xml
|
||||
assert_match %r{<apt-number type="integer">}, xml
|
||||
end
|
||||
|
||||
test "don't propagate camelize if camelize is defined at the included association level" do
|
||||
xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0
|
||||
assert_match %r{<address>}, xml
|
||||
assert_match %r{<apt-number type="integer">}, xml
|
||||
end
|
||||
|
||||
test "don't propagate dasherize if dasherize is defined at the included association level" do
|
||||
xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0
|
||||
assert_match %r{<address>}, xml
|
||||
assert_match %r{<apt-number type="integer">}, xml
|
||||
end
|
||||
|
||||
test "association with sti" do
|
||||
xml = @contact.to_xml(include: :contact)
|
||||
assert xml.include?(%(<contact type="SerializableContact">))
|
||||
end
|
||||
end
|
|
@ -4,7 +4,6 @@ class Contact
|
|||
include ActiveModel::Validations
|
||||
|
||||
include ActiveModel::Serializers::JSON
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
|
||||
attr_accessor :address, :friends, :contact
|
||||
|
|
|
@ -345,6 +345,10 @@
|
|||
|
||||
*Ryuta Kamizono*
|
||||
|
||||
* Remove `ActiveRecord::Serialization::XmlSerializer` from core.
|
||||
|
||||
*Zachary Scott*
|
||||
|
||||
* Make `unscope` aware of "less than" and "greater than" conditions.
|
||||
|
||||
*TAKAHASHI Kazuaki*
|
||||
|
|
|
@ -18,5 +18,3 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/serializers/xml_serializer'
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
require 'active_support/core_ext/hash/conversions'
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
# Builds an XML document to represent the model. Some configuration is
|
||||
# available through +options+. However more complicated cases should
|
||||
# override ActiveRecord::Base#to_xml.
|
||||
#
|
||||
# By default the generated XML document will include the processing
|
||||
# instruction and all the object's attributes. For example:
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <topic>
|
||||
# <title>The First Topic</title>
|
||||
# <author-name>David</author-name>
|
||||
# <id type="integer">1</id>
|
||||
# <approved type="boolean">false</approved>
|
||||
# <replies-count type="integer">0</replies-count>
|
||||
# <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
|
||||
# <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
|
||||
# <content>Have a nice day</content>
|
||||
# <author-email-address>david@loudthinking.com</author-email-address>
|
||||
# <parent-id></parent-id>
|
||||
# <last-read type="date">2004-04-15</last-read>
|
||||
# </topic>
|
||||
#
|
||||
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
|
||||
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
|
||||
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
|
||||
# +attributes+ method. The default is to dasherize all column names, but you
|
||||
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
|
||||
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
|
||||
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
# topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
|
||||
#
|
||||
# <topic>
|
||||
# <title>The First Topic</title>
|
||||
# <author-name>David</author-name>
|
||||
# <approved type="boolean">false</approved>
|
||||
# <content>Have a nice day</content>
|
||||
# <author-email-address>david@loudthinking.com</author-email-address>
|
||||
# <parent-id></parent-id>
|
||||
# <last-read type="date">2004-04-15</last-read>
|
||||
# </topic>
|
||||
#
|
||||
# To include first level associations use <tt>:include</tt>:
|
||||
#
|
||||
# firm.to_xml include: [ :account, :clients ]
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <firm>
|
||||
# <id type="integer">1</id>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>37signals</name>
|
||||
# <clients type="array">
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Summit</name>
|
||||
# </client>
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Microsoft</name>
|
||||
# </client>
|
||||
# </clients>
|
||||
# <account>
|
||||
# <id type="integer">1</id>
|
||||
# <credit-limit type="integer">50</credit-limit>
|
||||
# </account>
|
||||
# </firm>
|
||||
#
|
||||
# Additionally, the record being serialized will be passed to a Proc's second
|
||||
# parameter. This allows for ad hoc additions to the resultant document that
|
||||
# incorporate the context of the record being serialized. And by leveraging the
|
||||
# closure created by a Proc, to_xml can be used to add elements that normally fall
|
||||
# outside of the scope of the model -- for example, generating and appending URLs
|
||||
# associated with models.
|
||||
#
|
||||
# proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
|
||||
# firm.to_xml procs: [ proc ]
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <name-reverse>slangis73</name-reverse>
|
||||
# </firm>
|
||||
#
|
||||
# To include deeper levels of associations pass a hash like this:
|
||||
#
|
||||
# firm.to_xml include: {account: {}, clients: {include: :address}}
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <firm>
|
||||
# <id type="integer">1</id>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>37signals</name>
|
||||
# <clients type="array">
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Summit</name>
|
||||
# <address>
|
||||
# ...
|
||||
# </address>
|
||||
# </client>
|
||||
# <client>
|
||||
# <rating type="integer">1</rating>
|
||||
# <name>Microsoft</name>
|
||||
# <address>
|
||||
# ...
|
||||
# </address>
|
||||
# </client>
|
||||
# </clients>
|
||||
# <account>
|
||||
# <id type="integer">1</id>
|
||||
# <credit-limit type="integer">50</credit-limit>
|
||||
# </account>
|
||||
# </firm>
|
||||
#
|
||||
# To include any methods on the model being called use <tt>:methods</tt>:
|
||||
#
|
||||
# firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <calculated-earnings>100000000000000000</calculated-earnings>
|
||||
# <real-earnings>5</real-earnings>
|
||||
# </firm>
|
||||
#
|
||||
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
|
||||
# modified version of the options hash that was given to +to_xml+:
|
||||
#
|
||||
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
|
||||
# firm.to_xml procs: [ proc ]
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <abc>def</abc>
|
||||
# </firm>
|
||||
#
|
||||
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
|
||||
#
|
||||
# firm.to_xml do |xml|
|
||||
# xml.creator do
|
||||
# xml.first_name "David"
|
||||
# xml.last_name "Heinemeier Hansson"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# <firm>
|
||||
# # ... normal attributes as shown above ...
|
||||
# <creator>
|
||||
# <first_name>David</first_name>
|
||||
# <last_name>Heinemeier Hansson</last_name>
|
||||
# </creator>
|
||||
# </firm>
|
||||
#
|
||||
# As noted above, you may override +to_xml+ in your ActiveRecord::Base
|
||||
# subclasses to have complete control about what's generated. The general
|
||||
# form of doing this is:
|
||||
#
|
||||
# class IHaveMyOwnXML < ActiveRecord::Base
|
||||
# def to_xml(options = {})
|
||||
# require 'builder'
|
||||
# options[:indent] ||= 2
|
||||
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
|
||||
# xml.instruct! unless options[:skip_instruct]
|
||||
# xml.level_one do
|
||||
# xml.tag!(:second_level, 'content')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
def to_xml(options = {}, &block)
|
||||
XmlSerializer.new(self, options).serialize(&block)
|
||||
end
|
||||
end
|
||||
|
||||
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
|
||||
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
|
||||
def compute_type
|
||||
klass = @serializable.class
|
||||
cast_type = klass.type_for_attribute(name)
|
||||
|
||||
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type
|
||||
|
||||
{ :text => :string,
|
||||
:time => :datetime }[type] || type
|
||||
end
|
||||
protected :compute_type
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ require 'models/post'
|
|||
class SerializationTest < ActiveRecord::TestCase
|
||||
fixtures :books
|
||||
|
||||
FORMATS = [ :xml, :json ]
|
||||
FORMATS = [ :json ]
|
||||
|
||||
def setup
|
||||
@contact_attributes = {
|
||||
|
|
|
@ -1,447 +0,0 @@
|
|||
require "cases/helper"
|
||||
require "rexml/document"
|
||||
require 'models/contact'
|
||||
require 'models/post'
|
||||
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
|
||||
@xml = Contact.new.to_xml
|
||||
assert_match %r{^<contact>}, @xml
|
||||
assert_match %r{</contact>$}, @xml
|
||||
end
|
||||
|
||||
def test_should_serialize_default_root_with_namespace
|
||||
@xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact"
|
||||
assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml
|
||||
assert_match %r{</contact>$}, @xml
|
||||
end
|
||||
|
||||
def test_should_serialize_custom_root
|
||||
@xml = Contact.new.to_xml :root => 'xml_contact'
|
||||
assert_match %r{^<xml-contact>}, @xml
|
||||
assert_match %r{</xml-contact>$}, @xml
|
||||
end
|
||||
|
||||
def test_should_allow_undasherized_tags
|
||||
@xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false
|
||||
assert_match %r{^<xml_contact>}, @xml
|
||||
assert_match %r{</xml_contact>$}, @xml
|
||||
assert_match %r{<created_at}, @xml
|
||||
end
|
||||
|
||||
def test_should_allow_camelized_tags
|
||||
@xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true
|
||||
assert_match %r{^<XmlContact>}, @xml
|
||||
assert_match %r{</XmlContact>$}, @xml
|
||||
assert_match %r{<CreatedAt}, @xml
|
||||
end
|
||||
|
||||
def test_should_allow_skipped_types
|
||||
@xml = Contact.new(:age => 25).to_xml :skip_types => true
|
||||
assert %r{<age>25</age>}.match(@xml)
|
||||
end
|
||||
|
||||
def test_should_include_yielded_additions
|
||||
@xml = Contact.new.to_xml do |xml|
|
||||
xml.creator "David"
|
||||
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
|
||||
def setup
|
||||
@contact = Contact.new(
|
||||
:name => 'aaron stack',
|
||||
:age => 25,
|
||||
:avatar => 'binarydata',
|
||||
:created_at => Time.utc(2006, 8, 1),
|
||||
:awesome => false,
|
||||
:preferences => { :gem => 'ruby' }
|
||||
)
|
||||
end
|
||||
|
||||
def test_should_serialize_string
|
||||
assert_match %r{<name>aaron stack</name>}, @contact.to_xml
|
||||
end
|
||||
|
||||
def test_should_serialize_integer
|
||||
assert_match %r{<age type="integer">25</age>}, @contact.to_xml
|
||||
end
|
||||
|
||||
def test_should_serialize_binary
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, xml
|
||||
assert_match %r{<avatar(.*)(type="binary")}, xml
|
||||
assert_match %r{<avatar(.*)(encoding="base64")}, xml
|
||||
end
|
||||
|
||||
def test_should_serialize_datetime
|
||||
assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
|
||||
end
|
||||
|
||||
def test_should_serialize_boolean
|
||||
assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml
|
||||
end
|
||||
|
||||
def test_should_serialize_hash
|
||||
assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml
|
||||
end
|
||||
|
||||
def test_uses_serializable_hash_with_only_option
|
||||
def @contact.serializable_hash(options=nil)
|
||||
super(only: %w(name))
|
||||
end
|
||||
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{<name>aaron stack</name>}, xml
|
||||
assert_no_match %r{age}, xml
|
||||
assert_no_match %r{awesome}, xml
|
||||
end
|
||||
|
||||
def test_uses_serializable_hash_with_except_option
|
||||
def @contact.serializable_hash(options=nil)
|
||||
super(except: %w(age))
|
||||
end
|
||||
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{<name>aaron stack</name>}, xml
|
||||
assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml
|
||||
assert_no_match %r{age}, xml
|
||||
end
|
||||
|
||||
def test_does_not_include_inheritance_column_from_sti
|
||||
@contact = ContactSti.new(@contact.attributes)
|
||||
assert_equal 'ContactSti', @contact.type
|
||||
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{<name>aaron stack</name>}, xml
|
||||
assert_no_match %r{<type}, xml
|
||||
assert_no_match %r{ContactSti}, xml
|
||||
end
|
||||
|
||||
def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
|
||||
@contact = ContactSti.new(@contact.attributes)
|
||||
assert_equal 'ContactSti', @contact.type
|
||||
|
||||
def @contact.serializable_hash(options={})
|
||||
super({ except: %w(age) }.merge!(options))
|
||||
end
|
||||
|
||||
xml = @contact.to_xml
|
||||
assert_match %r{<name>aaron stack</name>}, xml
|
||||
assert_no_match %r{age}, xml
|
||||
assert_no_match %r{<type}, xml
|
||||
assert_no_match %r{ContactSti}, xml
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
|
||||
def test_should_serialize_datetime_with_timezone
|
||||
with_timezone_config zone: "Pacific Time (US & Canada)" do
|
||||
toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1))
|
||||
assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_serialize_datetime_with_timezone_reloaded
|
||||
with_timezone_config zone: "Pacific Time (US & Canada)" do
|
||||
toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload
|
||||
assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NilXmlSerializationTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
@xml = Contact.new.to_xml(:root => 'xml_contact')
|
||||
end
|
||||
|
||||
def test_should_serialize_string
|
||||
assert_match %r{<name nil="true"/>}, @xml
|
||||
end
|
||||
|
||||
def test_should_serialize_integer
|
||||
assert %r{<age (.*)/>}.match(@xml)
|
||||
attributes = $1
|
||||
assert_match %r{nil="true"}, attributes
|
||||
assert_match %r{type="integer"}, attributes
|
||||
end
|
||||
|
||||
def test_should_serialize_binary
|
||||
assert %r{<avatar (.*)/>}.match(@xml)
|
||||
attributes = $1
|
||||
assert_match %r{type="binary"}, attributes
|
||||
assert_match %r{encoding="base64"}, attributes
|
||||
assert_match %r{nil="true"}, attributes
|
||||
end
|
||||
|
||||
def test_should_serialize_datetime
|
||||
assert %r{<created-at (.*)/>}.match(@xml)
|
||||
attributes = $1
|
||||
assert_match %r{nil="true"}, attributes
|
||||
assert_match %r{type="dateTime"}, attributes
|
||||
end
|
||||
|
||||
def test_should_serialize_boolean
|
||||
assert %r{<awesome (.*)/>}.match(@xml)
|
||||
attributes = $1
|
||||
assert_match %r{type="boolean"}, attributes
|
||||
assert_match %r{nil="true"}, attributes
|
||||
end
|
||||
|
||||
def test_should_serialize_yaml
|
||||
assert_match %r{<preferences nil=\"true\"/>}, @xml
|
||||
end
|
||||
end
|
||||
|
||||
class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
|
||||
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
|
||||
|
||||
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']
|
||||
|
||||
# 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']
|
||||
|
||||
# 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
|
||||
# getting appended to.
|
||||
|
||||
def test_modules
|
||||
projects = MyApplication::Business::Project.all
|
||||
xml = projects.to_xml
|
||||
root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize
|
||||
assert_match "<#{root} type=\"array\">", xml
|
||||
assert_match "</#{root}>", xml
|
||||
end
|
||||
|
||||
def test_passing_hash_shouldnt_reuse_builder
|
||||
options = {:include=>:posts}
|
||||
david = authors(:david)
|
||||
first_xml_size = david.to_xml(options).size
|
||||
second_xml_size = david.to_xml(options).size
|
||||
assert_equal first_xml_size, second_xml_size
|
||||
end
|
||||
|
||||
def test_include_uses_association_name
|
||||
xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0
|
||||
assert_match %r{<hello-posts type="array">}, xml
|
||||
assert_match %r{<hello-post type="Post">}, xml
|
||||
assert_match %r{<hello-post type="StiPost">}, xml
|
||||
end
|
||||
|
||||
def test_included_associations_should_skip_types
|
||||
xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true
|
||||
assert_match %r{<hello-posts>}, xml
|
||||
assert_match %r{<hello-post>}, xml
|
||||
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
|
||||
end
|
||||
|
||||
def test_should_not_call_methods_on_associations_that_dont_respond
|
||||
xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2
|
||||
assert !authors(:david).hello_posts.first.respond_to?(:label)
|
||||
assert_match %r{^ <label>.*</label>}, xml
|
||||
assert_no_match %r{^ <label>}, xml
|
||||
end
|
||||
|
||||
def test_procs_are_called_on_object
|
||||
proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
|
||||
xml = authors(:david).to_xml(:procs => [ proc ])
|
||||
assert_match %r{<nationality>Danish</nationality>}, xml
|
||||
end
|
||||
|
||||
def test_dual_arity_procs_are_called_on_object
|
||||
proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
|
||||
xml = authors(:david).to_xml(:procs => [ proc ])
|
||||
assert_match %r{<name-reverse>divaD</name-reverse>}, xml
|
||||
end
|
||||
|
||||
def test_top_level_procs_arent_applied_to_associations
|
||||
author_proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
|
||||
xml = authors(:david).to_xml(:procs => [ author_proc ], :include => :posts, :indent => 2)
|
||||
|
||||
assert_match %r{^ <nationality>Danish</nationality>}, xml
|
||||
assert_no_match %r{^ {6}<nationality>Danish</nationality>}, xml
|
||||
end
|
||||
|
||||
def test_procs_on_included_associations_are_called
|
||||
posts_proc = Proc.new { |options| options[:builder].tag!('copyright', 'DHH') }
|
||||
xml = authors(:david).to_xml(
|
||||
:indent => 2,
|
||||
:include => {
|
||||
:posts => { :procs => [ posts_proc ] }
|
||||
}
|
||||
)
|
||||
|
||||
assert_no_match %r{^ <copyright>DHH</copyright>}, xml
|
||||
assert_match %r{^ {6}<copyright>DHH</copyright>}, xml
|
||||
end
|
||||
|
||||
def test_should_include_empty_has_many_as_empty_array
|
||||
authors(:david).posts.delete_all
|
||||
xml = authors(:david).to_xml :include=>:posts, :indent => 2
|
||||
|
||||
assert_equal [], Hash.from_xml(xml)['author']['posts']
|
||||
assert_match %r{^ <posts type="array"/>}, xml
|
||||
end
|
||||
|
||||
def test_should_has_many_array_elements_should_include_type_when_different_from_guessed_value
|
||||
xml = authors(:david).to_xml :include=>:posts_with_comments, :indent => 2
|
||||
|
||||
assert Hash.from_xml(xml)
|
||||
assert_match %r{^ <posts-with-comments type="array">}, xml
|
||||
assert_match %r{^ <posts-with-comment type="Post">}, xml
|
||||
assert_match %r{^ <posts-with-comment type="StiPost">}, xml
|
||||
|
||||
types = Hash.from_xml(xml)['author']['posts_with_comments'].collect {|t| t['type'] }
|
||||
assert types.include?('SpecialPost')
|
||||
assert types.include?('Post')
|
||||
assert types.include?('StiPost')
|
||||
end
|
||||
|
||||
def test_should_produce_xml_for_methods_returning_array
|
||||
xml = authors(:david).to_xml(:methods => :social)
|
||||
array = Hash.from_xml(xml)['author']['social']
|
||||
assert_equal 2, array.size
|
||||
assert array.include? 'twitter'
|
||||
assert array.include? 'github'
|
||||
end
|
||||
|
||||
def test_should_support_aliased_attributes
|
||||
xml = Author.select("name as firstname").to_xml
|
||||
Author.all.each do |author|
|
||||
assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml
|
||||
end
|
||||
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
|
|
@ -319,9 +319,8 @@ person.serializable_hash # => {"name"=>"Bob"}
|
|||
|
||||
#### ActiveModel::Serializers
|
||||
|
||||
Rails provides two serializers `ActiveModel::Serializers::JSON` and
|
||||
`ActiveModel::Serializers::Xml`. Both of these modules automatically include
|
||||
the `ActiveModel::Serialization`.
|
||||
Rails provides a `ActiveModel::Serializers::JSON` serializer.
|
||||
This module automatically include the `ActiveModel::Serialization`.
|
||||
|
||||
##### ActiveModel::Serializers::JSON
|
||||
|
||||
|
@ -379,62 +378,6 @@ person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob">
|
|||
person.name # => "Bob"
|
||||
```
|
||||
|
||||
##### ActiveModel::Serializers::Xml
|
||||
|
||||
To use the `ActiveModel::Serializers::Xml` you only need to change from
|
||||
`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`.
|
||||
|
||||
```ruby
|
||||
class Person
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :name
|
||||
|
||||
def attributes
|
||||
{'name' => nil}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
With the `to_xml` you have an XML representing the model.
|
||||
|
||||
```ruby
|
||||
person = Person.new
|
||||
person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n"
|
||||
person.name = "Bob"
|
||||
person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
|
||||
```
|
||||
|
||||
From an XML string you define the attributes of the model.
|
||||
You need to have the `attributes=` method defined on your class:
|
||||
|
||||
```ruby
|
||||
class Person
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :name
|
||||
|
||||
def attributes=(hash)
|
||||
hash.each do |key, value|
|
||||
send("#{key}=", value)
|
||||
end
|
||||
end
|
||||
|
||||
def attributes
|
||||
{'name' => nil}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now it is possible to create an instance of person and set the attributes using `from_xml`.
|
||||
|
||||
```ruby
|
||||
xml = { name: 'Bob' }.to_xml
|
||||
person = Person.new
|
||||
person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob">
|
||||
person.name # => "Bob"
|
||||
```
|
||||
|
||||
### Translation
|
||||
|
||||
`ActiveModel::Translation` provides integration between your object and the Rails
|
||||
|
|
Loading…
Reference in a new issue