From bbdb4e55f6eceb40c2047c614f5b47cef253dfb0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Sep 2007 14:07:23 +0000 Subject: Added :include option to to_json (closes #9677) [chuyeow] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7663 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 2 +- activerecord/lib/active_record/serialization.rb | 51 ++++++++++-- .../active_record/serializers/xml_serializer.rb | 94 +++++++++------------- 3 files changed, 84 insertions(+), 63 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d32ad83791..c4387e9778 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -10,7 +10,7 @@ * Added the possibility of using symbols in addition to concrete classes with ActiveRecord::Observer#observe #3998 [robbyrussell/tarmo] -* Added ActiveRecord::Base#to_json/from_json (currently does not support :include like to_xml) [DHH] +* Added ActiveRecord::Base#to_json/from_json [DHH/chuyeow] * Added ActiveRecord::Base#from_xml [DHH]. Example: diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index f10d1b3b7e..2c8210a299 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -2,7 +2,7 @@ module ActiveRecord #:nodoc: module Serialization class Serializer #:nodoc: attr_reader :options - + def initialize(record, options = {}) @record, @options = record, options.dup end @@ -23,31 +23,70 @@ module ActiveRecord #:nodoc: 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 << name if @record.respond_to?(name.to_s) method_attributes end end - + def serializable_names serializable_attribute_names + serializable_method_names end + # Add associations specified via the :includes option. + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def add_includes(&block) + if include_associations = options.delete(:include) + base_only_or_except = { :except => options[:except], + :only => options[:only] } + + include_has_options = include_associations.is_a?(Hash) + associations = include_has_options ? include_associations.keys : Array(include_associations) + + for association in associations + records = case @record.class.reflect_on_association(association).macro + when :has_many, :has_and_belongs_to_many + @record.send(association).to_a + when :has_one, :belongs_to + @record.send(association) + end + + unless records.nil? + association_options = include_has_options ? include_associations[association] : base_only_or_except + opts = options.merge(association_options) + yield(association, records, opts) + end + end + + options[:include] = include_associations + end + end + def serializable_record returning(serializable_record = {}) do serializable_names.each { |name| serializable_record[name] = @record.send(name) } + add_includes do |association, records, opts| + if records.is_a?(Enumerable) + serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record } + else + serializable_record[association] = self.class.new(records, opts).serializable_record + end + end end end def serialize # overwrite to implement - end - + end + def to_s(&block) serialize(&block) end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 1e4101f183..89559ce709 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -141,7 +141,7 @@ module ActiveRecord #:nodoc: builder.instruct! options[:skip_instruct] = true end - + builder end end @@ -150,7 +150,7 @@ module ActiveRecord #:nodoc: root = (options[:root] || @record.class.to_s.underscore).to_s dasherize? ? root.dasherize : root end - + def dasherize? !options.has_key?(:dasherize) || options[:dasherize] end @@ -179,47 +179,6 @@ module ActiveRecord #:nodoc: end end - def add_includes - if include_associations = options.delete(:include) - root_only_or_except = { :except => options[:except], - :only => options[:only] } - - include_has_options = include_associations.is_a?(Hash) - - for association in include_has_options ? include_associations.keys : Array(include_associations) - association_options = include_has_options ? include_associations[association] : root_only_or_except - - opts = options.merge(association_options) - - case @record.class.reflect_on_association(association).macro - when :has_many, :has_and_belongs_to_many - records = @record.send(association).to_a - tag = association.to_s - tag = tag.dasherize if dasherize? - if records.empty? - builder.tag!(tag, :type => :array) - else - builder.tag!(tag, :type => :array) do - association_name = association.to_s.singularize - records.each do |record| - record.to_xml opts.merge( - :root => association_name, - :type => (record.class.to_s.underscore == association_name ? nil : record.class.name) - ) - end - end - end - when :has_one, :belongs_to - if record = @record.send(association) - record.to_xml(opts.merge(:root => association)) - end - end - end - - options[:include] = include_associations - end - end - def add_procs if procs = options.delete(:procs) [ *procs ].each do |proc| @@ -228,7 +187,6 @@ module ActiveRecord #:nodoc: end end - def add_tag(attribute) builder.tag!( dasherize? ? attribute.name.dasherize : attribute.name, @@ -237,30 +195,54 @@ module ActiveRecord #:nodoc: ) end + def add_associations(association, records, opts) + if records.is_a?(Enumerable) + tag = association.to_s + tag = tag.dasherize if dasherize? + if records.empty? + builder.tag!(tag, :type => :array) + else + builder.tag!(tag, :type => :array) do + association_name = association.to_s.singularize + records.each do |record| + record.to_xml opts.merge( + :root => association_name, + :type => (record.class.to_s.underscore == association_name ? nil : record.class.name) + ) + end + end + end + else + if record = @record.send(association) + record.to_xml(opts.merge(:root => association)) + end + end + end + def serialize args = [root] if options[:namespace] args << {:xmlns=>options[:namespace]} end - + if options[:type] args << {:type=>options[:type]} end - + builder.tag!(*args) do add_attributes - add_includes + add_includes { |association, records, opts| add_associations(association, records, opts) } add_procs yield builder if block_given? end - end + end class Attribute #:nodoc: attr_reader :name, :value, :type - + def initialize(name, record) @name, @record = name, record - + @type = compute_type @value = compute_value end @@ -277,21 +259,21 @@ module ActiveRecord #:nodoc: def needs_encoding? ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type) end - + def decorations(include_types = true) decorations = {} if type == :binary decorations[:encoding] = 'base64' end - + if include_types && type != :string decorations[:type] = type end - + decorations end - + protected def compute_type type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type @@ -305,10 +287,10 @@ module ActiveRecord #:nodoc: type end end - + def compute_value value = @record.send(name) - + if formatter = Hash::XML_FORMATTING[type.to_s] value ? formatter.call(value) : nil else -- cgit v1.2.3