diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2007-09-20 23:22:30 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2007-09-20 23:22:30 +0000 |
commit | e86d1cd621ca62af6f71b04032b1e07a66c06bb6 (patch) | |
tree | 638a89da26f9066cc333f0ce059c3ce551a97d24 | |
parent | dc399b96c84bc66b7c20e92fb40e9ed00daf99c2 (diff) | |
download | rails-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/CHANGELOG | 7 | ||||
-rwxr-xr-x | activerecord/lib/active_record.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/association_collection.rb | 3 | ||||
-rw-r--r-- | activerecord/lib/active_record/serialization.rb | 59 | ||||
-rw-r--r-- | activerecord/lib/active_record/serializers/json_serializer.rb | 18 | ||||
-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.rb | 16 | ||||
-rw-r--r-- | activerecord/test/serialization_test.rb | 47 | ||||
-rw-r--r-- | activerecord/test/xml_serialization_test.rb | 30 |
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" |