Permalink
Browse files

Added ActiveRecord::Base#to_json/from_json (currently does not suppor…

…t :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
  • Loading branch information...
1 parent dc399b9 commit e86d1cd621ca62af6f71b04032b1e07a66c06bb6 @dhh dhh committed Sep 20, 2007
@@ -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]
@@ -49,7 +49,7 @@
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 @@
include ActiveRecord::Transactions
include ActiveRecord::Reflection
include ActiveRecord::Calculations
- include ActiveRecord::XmlSerialization
+ include ActiveRecord::Serialization
include ActiveRecord::AttributeMethods
end
@@ -158,6 +158,7 @@ def replace(other_array)
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 @@ def ensure_owner_is_not_new
end
end
-end
+end
@@ -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'
@@ -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
@@ -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 @@ def to_xml(options = {}, &block)
serializer = XmlSerializer.new(self, options)
block_given? ? serializer.to_s(&block) : serializer.to_s
end
- end
- class XmlSerializer #:nodoc:
- attr_reader :options
-
- def initialize(record, options = {})
- @record, @options = record, options.dup
+ def from_xml(xml)
+ self.attributes = Hash.from_xml(xml).values.first
+ self
end
-
+ end
+
+ class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
def builder
@builder ||= begin
options[:indent] ||= 2
@@ -164,17 +163,7 @@ def dasherize?
# 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 @@ def serialize
yield builder if block_given?
end
end
-
- alias_method :to_s, :serialize
class Attribute #:nodoc:
attr_reader :name, :value, :type
@@ -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
@@ -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
@@ -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 @@ def test_should_allow_undasherized_tags
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"

0 comments on commit e86d1cd

Please sign in to comment.