1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Added ActiveRecord::Base#to_json/from_json (currently does not support :include like to_xml) [DHH]. Added ActiveRecord::Base#from_xml [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7519 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson 2007-09-20 23:22:30 +00:00
parent dc399b96c8
commit e86d1cd621
9 changed files with 160 additions and 53 deletions

View file

@ -1,5 +1,12 @@
*SVN*
* Added ActiveRecord::Base#to_json/from_json (currently does not support :include like to_xml) [DHH]
* Added ActiveRecord::Base#from_xml [DHH]. Example:
xml = "<person><name>David</name></person>"
Person.new.from_xml(xml).name # => "David"
* Define dynamic finders as real methods after first usage. [bscofield]
* Deprecation: remove deprecated threaded_connections methods. Use allow_concurrency instead. [Jeremy Kemper]

View file

@ -49,7 +49,7 @@ require 'active_record/locking/pessimistic'
require 'active_record/migration'
require 'active_record/schema'
require 'active_record/calculations'
require 'active_record/xml_serialization'
require 'active_record/serialization'
require 'active_record/attribute_methods'
ActiveRecord::Base.class_eval do
@ -65,7 +65,7 @@ ActiveRecord::Base.class_eval do
include ActiveRecord::Transactions
include ActiveRecord::Reflection
include ActiveRecord::Calculations
include ActiveRecord::XmlSerialization
include ActiveRecord::Serialization
include ActiveRecord::AttributeMethods
end

View file

@ -158,6 +158,7 @@ module ActiveRecord
end
end
protected
def method_missing(method, *args, &block)
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
@ -218,4 +219,4 @@ module ActiveRecord
end
end
end
end

View file

@ -0,0 +1,59 @@
module ActiveRecord #:nodoc:
module Serialization
class Serializer #:nodoc:
attr_reader :options
def initialize(record, options = {})
@record, @options = record, options.dup
end
# To replicate the behavior in ActiveRecord#attributes,
# :except takes precedence over :only. If :only is not set
# for a N level model but is set for the N+1 level models,
# then because :except is set to a default value, the second
# level model can have both :except and :only set. So if
# :only is set, always delete :except.
def serializable_attribute_names
attribute_names = @record.attribute_names
if options[:only]
options.delete(:except)
attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
else
options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
end
attribute_names
end
def serializable_method_names
Array(options[:methods]).inject([]) do |method_attributes, name|
method_attributes << :name if @record.respond_to?(name.to_s)
method_attributes
end
end
def serializable_names
serializable_attribute_names + serializable_method_names
end
def serializable_record
returning(serializable_record = {}) do
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
end
end
def serialize
# overwrite to implement
end
def to_s(&block)
serialize(&block)
end
end
end
end
require 'active_record/serializers/xml_serializer'
require 'active_record/serializers/json_serializer'

View file

@ -0,0 +1,18 @@
module ActiveRecord #:nodoc:
module Serialization
def to_json(options = {}, &block)
JsonSerializer.new(self, options).to_s
end
def from_json(json)
self.attributes = ActiveSupport::JSON.decode(json)
self
end
class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
def serialize
serializable_record.to_json
end
end
end
end

View file

@ -1,5 +1,5 @@
module ActiveRecord #:nodoc:
module XmlSerialization
module Serialization
# Builds an XML document to represent the model. Some configuration is
# availble through +options+, however more complicated cases should use
# override ActiveRecord's to_xml.
@ -124,15 +124,14 @@ module ActiveRecord #:nodoc:
serializer = XmlSerializer.new(self, options)
block_given? ? serializer.to_s(&block) : serializer.to_s
end
def from_xml(xml)
self.attributes = Hash.from_xml(xml).values.first
self
end
end
class XmlSerializer #:nodoc:
attr_reader :options
def initialize(record, options = {})
@record, @options = record, options.dup
end
class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
def builder
@builder ||= begin
options[:indent] ||= 2
@ -164,17 +163,7 @@ module ActiveRecord #:nodoc:
# level model can have both :except and :only set. So if
# :only is set, always delete :except.
def serializable_attributes
attribute_names = @record.attribute_names
if options[:only]
options.delete(:except)
attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
else
options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
end
attribute_names.collect { |name| Attribute.new(name, @record) }
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
end
def serializable_method_attributes
@ -265,8 +254,6 @@ module ActiveRecord #:nodoc:
yield builder if block_given?
end
end
alias_method :to_s, :serialize
class Attribute #:nodoc:
attr_reader :name, :value, :type

16
activerecord/test/fixtures/contact.rb vendored Normal file
View file

@ -0,0 +1,16 @@
class Contact < ActiveRecord::Base
# mock out self.columns so no pesky db is needed for these tests
def self.column(name, sql_type = nil, options = {})
@columns ||= []
@columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, options[:default], sql_type.to_s, options[:null])
end
column :name, :string
column :age, :integer
column :avatar, :binary
column :created_at, :datetime
column :awesome, :boolean
column :preferences, :string
serialize :preferences
end

View file

@ -0,0 +1,47 @@
require 'abstract_unit'
require 'fixtures/contact'
class SerializationTest < Test::Unit::TestCase
FORMATS = [ :xml, :json ]
def setup
@contact_attributes = {
:name => 'aaron stack',
:age => 25,
:avatar => 'binarydata',
:created_at => Time.utc(2006, 8, 1),
:awesome => false,
:preferences => { :gem => 'ruby' }
}
@contact = Contact.new(@contact_attributes)
end
def test_serialize_should_be_reversible
for format in FORMATS
@serialized = Contact.new.send("to_#{format}")
contact = Contact.new.send("from_#{format}", @serialized)
assert_equal @contact_attributes.keys.collect(&:to_s).sort, contact.attributes.keys.collect(&:to_s).sort, "For #{format}"
end
end
def test_serialize_should_allow_attribute_only_filtering
for format in FORMATS
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_equal @contact_attributes[:name], contact.name, "For #{format}"
assert_nil contact.avatar, "For #{format}"
end
end
def test_serialize_should_allow_attribute_except_filtering
for format in FORMATS
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_nil contact.name, "For #{format}"
assert_nil contact.age, "For #{format}"
assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}"
end
end
end

View file

@ -1,26 +1,10 @@
require 'abstract_unit'
require 'fixtures/contact'
require 'fixtures/post'
require 'fixtures/author'
require 'fixtures/tagging'
require 'fixtures/comment'
class Contact < ActiveRecord::Base
# mock out self.columns so no pesky db is needed for these tests
def self.columns() @columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :name, :string
column :age, :integer
column :avatar, :binary
column :created_at, :datetime
column :awesome, :boolean
column :preferences, :string
serialize :preferences
end
class XmlSerializationTest < Test::Unit::TestCase
def test_should_serialize_default_root
@xml = Contact.new.to_xml
@ -47,18 +31,6 @@ class XmlSerializationTest < Test::Unit::TestCase
assert_match %r{<created_at}, @xml
end
def test_should_allow_attribute_filtering
@xml = Contact.new.to_xml :only => [:age, :name]
assert_match %r{<name}, @xml
assert_match %r{<age}, @xml
assert_no_match %r{<created-at}, @xml
@xml = Contact.new.to_xml :except => [:age, :name]
assert_no_match %r{<name}, @xml
assert_no_match %r{<age}, @xml
assert_match %r{<created-at}, @xml
end
def test_should_include_yielded_additions
@xml = Contact.new.to_xml do |xml|
xml.creator "David"