aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2007-09-20 23:22:30 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2007-09-20 23:22:30 +0000
commite86d1cd621ca62af6f71b04032b1e07a66c06bb6 (patch)
tree638a89da26f9066cc333f0ce059c3ce551a97d24
parentdc399b96c84bc66b7c20e92fb40e9ed00daf99c2 (diff)
downloadrails-e86d1cd621ca62af6f71b04032b1e07a66c06bb6.tar.gz
rails-e86d1cd621ca62af6f71b04032b1e07a66c06bb6.tar.bz2
rails-e86d1cd621ca62af6f71b04032b1e07a66c06bb6.zip
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
-rw-r--r--activerecord/CHANGELOG7
-rwxr-xr-xactiverecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb3
-rw-r--r--activerecord/lib/active_record/serialization.rb59
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb18
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb (renamed from activerecord/lib/active_record/xml_serialization.rb)29
-rw-r--r--activerecord/test/fixtures/contact.rb16
-rw-r--r--activerecord/test/serialization_test.rb47
-rw-r--r--activerecord/test/xml_serialization_test.rb30
9 files changed, 160 insertions, 53 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index aae27a9b58..7e575f4f29 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -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]
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f1585d640b..73f5229753 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -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
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 460f410cf0..66ee712d7d 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -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 \ No newline at end of file
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
new file mode 100644
index 0000000000..f10d1b3b7e
--- /dev/null
+++ b/activerecord/lib/active_record/serialization.rb
@@ -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' \ No newline at end of file
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
new file mode 100644
index 0000000000..97025ae4d3
--- /dev/null
+++ b/activerecord/lib/active_record/serializers/json_serializer.rb
@@ -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
diff --git a/activerecord/lib/active_record/xml_serialization.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index e32565745e..1e4101f183 100644
--- a/activerecord/lib/active_record/xml_serialization.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -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
- 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 @@ 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
diff --git a/activerecord/test/fixtures/contact.rb b/activerecord/test/fixtures/contact.rb
new file mode 100644
index 0000000000..c574196d94
--- /dev/null
+++ b/activerecord/test/fixtures/contact.rb
@@ -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 \ No newline at end of file
diff --git a/activerecord/test/serialization_test.rb b/activerecord/test/serialization_test.rb
new file mode 100644
index 0000000000..b3bdf6b43d
--- /dev/null
+++ b/activerecord/test/serialization_test.rb
@@ -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 \ No newline at end of file
diff --git a/activerecord/test/xml_serialization_test.rb b/activerecord/test/xml_serialization_test.rb
index f6e3d0dd9d..a5da55cd8d 100644
--- a/activerecord/test/xml_serialization_test.rb
+++ b/activerecord/test/xml_serialization_test.rb
@@ -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"