From bbdb4e55f6eceb40c2047c614f5b47cef253dfb0 Mon Sep 17 00:00:00 2001
From: David Heinemeier Hansson <david@loudthinking.com>
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