Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9677: add_includes_to_serializer.diff

File add_includes_to_serializer.diff, 13.4 kB (added by chuyeow, 9 months ago)

Add add_include to Serializer

  • activerecord/test/json_serialization_test.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/contact' 
     3require 'fixtures/post' 
     4require 'fixtures/author' 
     5require 'fixtures/tagging' 
     6require 'fixtures/tag' 
     7require 'fixtures/comment' 
     8 
     9class JsonSerializationTest < Test::Unit::TestCase 
     10  def setup 
     11    # Quote all keys (so that we can test against strictly valid JSON). 
     12    ActiveSupport::JSON.unquote_hash_key_identifiers = false 
     13 
     14    @contact = Contact.new( 
     15      :name        => 'Konata Izumi', 
     16      :age         => 16, 
     17      :avatar      => 'binarydata', 
     18      :created_at  => Time.utc(2006, 8, 1), 
     19      :awesome     => true, 
     20      :preferences => { :shows => 'anime' } 
     21    ) 
     22  end 
     23 
     24  def test_should_encode_all_encodable_attributes 
     25    json = @contact.to_json 
     26 
     27    assert_match %r{"name": "Konata Izumi"}, json 
     28    assert_match %r{"age": 16}, json 
     29    assert_match %r{"created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json 
     30    assert_match %r{"awesome": true}, json 
     31    assert_match %r{"preferences": \{"shows": "anime"\}}, json 
     32  end 
     33 
     34  def test_should_allow_attribute_filtering_with_only 
     35    json = @contact.to_json(:only => [:name, :age]) 
     36 
     37    assert_match %r{"name": "Konata Izumi"}, json 
     38    assert_match %r{"age": 16}, json 
     39    assert_no_match %r{"awesome": true}, json 
     40    assert_no_match %r{"created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json 
     41    assert_no_match %r{"preferences": \{"shows": "anime"\}}, json 
     42  end 
     43 
     44  def test_should_allow_attribute_filtering_with_except 
     45    json = @contact.to_json(:except => [:name, :age]) 
     46 
     47    assert_no_match %r{"name": "Konata Izumi"}, json 
     48    assert_no_match %r{"age": 16}, json 
     49    assert_match %r{"awesome": true}, json 
     50    assert_match %r{"created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json 
     51    assert_match %r{"preferences": \{"shows": "anime"\}}, json 
     52  end 
     53 
     54  def test_methods_are_called_on_object 
     55    # Define methods on fixture. 
     56    def @contact.label; "Has cheezburger"; end 
     57    def @contact.favorite_quote; "Constraints are liberating"; end 
     58 
     59    # Single method. 
     60    assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) 
     61 
     62    # Both methods. 
     63    methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) 
     64    assert_match %r{"label": "Has cheezburger"}, methods_json 
     65    assert_match %r{"favorite_quote": "Constraints are liberating"}, methods_json 
     66  end 
     67end 
     68 
     69class DatabaseConnectedJsonEncodingTest < Test::Unit::TestCase 
     70  fixtures :authors, :posts, :comments, :tags, :taggings 
     71 
     72  def setup 
     73    ActiveSupport::JSON.unquote_hash_key_identifiers = false 
     74 
     75    @david = authors(:david) 
     76  end 
     77 
     78  def test_includes_uses_association_name 
     79    json = @david.to_json(:include => :posts) 
     80     
     81    assert_match %r{"posts": \[}, json 
     82 
     83    assert_match %r{"id": 1}, json 
     84    assert_match %r{"name": "David"}, json 
     85 
     86    assert_match %r{"author_id": 1}, json 
     87    assert_match %r{"title": "Welcome to the weblog"}, json 
     88    assert_match %r{"body": "Such a lovely day"}, json 
     89 
     90    assert_match %r{"title": "So I was thinking"}, json 
     91    assert_match %r{"body": "Like I hopefully always am"}, json 
     92  end 
     93 
     94  def test_includes_uses_association_name_and_applies_attribute_filters 
     95    json = @david.to_json(:include => { :posts => { :only => :title } }) 
     96 
     97    assert_match %r{"name": "David"}, json 
     98    assert_match %r{"posts": \[}, json 
     99 
     100    assert_match %r{"title": "Welcome to the weblog"}, json 
     101    assert_no_match %r{"body": "Such a lovely day"}, json 
     102 
     103    assert_match %r{"title": "So I was thinking"}, json 
     104    assert_no_match %r{"body": "Like I hopefully always am"}, json 
     105  end 
     106 
     107  def test_includes_fetches_second_level_associations 
     108    json = @david.to_json(:include => { :posts => { :include => { :comments => { :only => :body } } } }) 
     109 
     110    assert_match %r{"name": "David"}, json 
     111    assert_match %r{"posts": \[}, json 
     112 
     113    assert_match %r{"comments": \[}, json 
     114    assert_match %r{\{"body": "Thank you again for the welcome"\}}, json 
     115    assert_match %r{\{"body": "Don't think too hard"\}}, json 
     116    assert_no_match %r{"post_id": }, json 
     117  end 
     118 
     119  def test_includes_fetches_nth_level_associations 
     120    json = @david.to_json( 
     121      :include => { 
     122        :posts => { 
     123          :include => { 
     124            :taggings => { 
     125              :include => { 
     126                :tag => { :only => :name } 
     127              } 
     128            } 
     129          } 
     130        } 
     131    }) 
     132 
     133    assert_match %r{"name": "David"}, json 
     134    assert_match %r{"posts": \[}, json 
     135 
     136    assert_match %r{"taggings": \[}, json 
     137    assert_match %r{"tag": \{"name": "General"\}}, json 
     138  end 
     139 
     140  def test_should_not_call_methods_on_associations_that_dont_respond 
     141    def @david.favorite_quote; "Constraints are liberating"; end 
     142    json = @david.to_json(:include => :posts, :methods => :favorite_quote) 
     143 
     144    assert !@david.posts.first.respond_to?(:favorite_quote) 
     145    assert_match %r{"favorite_quote": "Constraints are liberating"}, json 
     146    assert_equal %r{"favorite_quote": }.match(json).size, 1 
     147  end 
     148end 
  • activerecord/lib/active_record/serialization.rb

    old new  
    22  module Serialization 
    33    class Serializer #:nodoc: 
    44      attr_reader :options 
    5      
     5 
    66      def initialize(record, options = {}) 
    77        @record, @options = record, options.dup 
    88      end 
     
    2323          options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column) 
    2424          attribute_names = attribute_names - options[:except].collect { |n| n.to_s } 
    2525        end 
    26        
     26 
    2727        attribute_names 
    2828      end 
    2929 
    3030      def serializable_method_names 
    3131        Array(options[:methods]).inject([]) do |method_attributes, name| 
    32           method_attributes << :name if @record.respond_to?(name.to_s) 
     32          method_attributes << name if @record.respond_to?(name.to_s) 
    3333          method_attributes 
    3434        end 
    3535      end 
    36        
     36 
    3737      def serializable_names 
    3838        serializable_attribute_names + serializable_method_names 
    3939      end 
    4040 
     41      # Add associations specified via the :includes option. 
     42      # Expects a block that takes as arguments: 
     43      #   +association+ - name of the association 
     44      #   +records+     - the association record(s) to be serialized 
     45      #   +opts+        - options for the association records 
     46      def add_includes(&block) 
     47        if include_associations = options.delete(:include) 
     48          base_only_or_except = { :except => options[:except], 
     49                                  :only => options[:only] } 
     50 
     51          include_has_options = include_associations.is_a?(Hash) 
     52          associations = include_has_options ? include_associations.keys : Array(include_associations) 
     53 
     54          for association in associations 
     55            records = case @record.class.reflect_on_association(association).macro 
     56            when :has_many, :has_and_belongs_to_many 
     57              @record.send(association).to_a 
     58            when :has_one, :belongs_to 
     59              @record.send(association) 
     60            end 
     61 
     62            unless records.nil? 
     63              association_options = include_has_options ? include_associations[association] : base_only_or_except 
     64              opts = options.merge(association_options) 
     65              yield(association, records, opts) 
     66            end 
     67          end 
     68 
     69          options[:include] = include_associations 
     70        end 
     71      end 
     72 
    4173      def serializable_record 
    4274        returning(serializable_record = {}) do 
    4375          serializable_names.each { |name| serializable_record[name] = @record.send(name) } 
     76          add_includes do |association, records, opts| 
     77            if records.is_a?(Enumerable) 
     78              serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record } 
     79            else 
     80              serializable_record[association] = self.class.new(records, opts).serializable_record 
     81            end 
     82          end 
    4483        end 
    4584      end 
    4685 
    4786      def serialize 
    4887        # overwrite to implement 
    49       end         
    50      
     88      end 
     89 
    5190      def to_s(&block) 
    5291        serialize(&block) 
    5392      end 
  • activerecord/lib/active_record/serializers/xml_serializer.rb

    old new  
    141141          builder.instruct! 
    142142          options[:skip_instruct] = true 
    143143        end 
    144          
     144 
    145145        builder 
    146146      end 
    147147    end 
     
    150150      root = (options[:root] || @record.class.to_s.underscore).to_s 
    151151      dasherize? ? root.dasherize : root 
    152152    end 
    153      
     153 
    154154    def dasherize? 
    155155      !options.has_key?(:dasherize) || options[:dasherize] 
    156156    end 
     
    179179      end 
    180180    end 
    181181 
    182     def add_includes 
    183       if include_associations = options.delete(:include) 
    184         root_only_or_except = { :except => options[:except], 
    185                                 :only => options[:only] } 
    186  
    187         include_has_options = include_associations.is_a?(Hash) 
    188  
    189         for association in include_has_options ? include_associations.keys : Array(include_associations) 
    190           association_options = include_has_options ? include_associations[association] : root_only_or_except 
    191  
    192           opts = options.merge(association_options) 
    193  
    194           case @record.class.reflect_on_association(association).macro 
    195           when :has_many, :has_and_belongs_to_many 
    196             records = @record.send(association).to_a 
    197             tag = association.to_s 
    198             tag = tag.dasherize if dasherize? 
    199             if records.empty? 
    200               builder.tag!(tag, :type => :array) 
    201             else 
    202               builder.tag!(tag, :type => :array) do 
    203                 association_name = association.to_s.singularize 
    204                 records.each do |record|  
    205                   record.to_xml opts.merge( 
    206                     :root => association_name,  
    207                     :type => (record.class.to_s.underscore == association_name ? nil : record.class.name) 
    208                   ) 
    209                 end 
    210               end 
    211             end 
    212           when :has_one, :belongs_to 
    213             if record = @record.send(association) 
    214               record.to_xml(opts.merge(:root => association)) 
    215             end 
    216           end 
    217         end 
    218  
    219         options[:include] = include_associations 
    220       end 
    221     end 
    222  
    223182    def add_procs 
    224183      if procs = options.delete(:procs) 
    225184        [ *procs ].each do |proc| 
     
    228187      end 
    229188    end 
    230189 
    231  
    232190    def add_tag(attribute) 
    233191      builder.tag!( 
    234192        dasherize? ? attribute.name.dasherize : attribute.name,  
     
    237195      ) 
    238196    end 
    239197 
     198    def add_associations(association, records, opts) 
     199      if records.is_a?(Enumerable) 
     200        tag = association.to_s 
     201        tag = tag.dasherize if dasherize? 
     202        if records.empty? 
     203          builder.tag!(tag, :type => :array) 
     204        else 
     205          builder.tag!(tag, :type => :array) do 
     206            association_name = association.to_s.singularize 
     207            records.each do |record|  
     208              record.to_xml opts.merge( 
     209                :root => association_name,  
     210                :type => (record.class.to_s.underscore == association_name ? nil : record.class.name) 
     211              ) 
     212            end 
     213          end 
     214        end 
     215      else 
     216        if record = @record.send(association) 
     217          record.to_xml(opts.merge(:root => association)) 
     218        end 
     219      end 
     220    end 
     221 
    240222    def serialize 
    241223      args = [root] 
    242224      if options[:namespace] 
    243225        args << {:xmlns=>options[:namespace]} 
    244226      end 
    245        
     227 
    246228      if options[:type] 
    247229        args << {:type=>options[:type]} 
    248230      end 
    249          
     231 
    250232      builder.tag!(*args) do 
    251233        add_attributes 
    252         add_includes 
     234        add_includes { |association, records, opts| add_associations(association, records, opts) } 
    253235        add_procs 
    254236        yield builder if block_given? 
    255237      end 
    256     end         
     238    end 
    257239 
    258240    class Attribute #:nodoc: 
    259241      attr_reader :name, :value, :type 
    260      
     242 
    261243      def initialize(name, record) 
    262244        @name, @record = name, record 
    263        
     245 
    264246        @type  = compute_type 
    265247        @value = compute_value 
    266248      end 
     
    277259      def needs_encoding? 
    278260        ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type) 
    279261      end 
    280      
     262 
    281263      def decorations(include_types = true) 
    282264        decorations = {} 
    283265 
    284266        if type == :binary 
    285267          decorations[:encoding] = 'base64' 
    286268        end 
    287        
     269 
    288270        if include_types && type != :string 
    289271          decorations[:type] = type 
    290272        end 
    291        
     273 
    292274        decorations 
    293275      end 
    294      
     276 
    295277      protected 
    296278        def compute_type 
    297279          type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type 
     
    305287              type 
    306288          end 
    307289        end 
    308      
     290 
    309291        def compute_value 
    310292          value = @record.send(name) 
    311          
     293 
    312294          if formatter = Hash::XML_FORMATTING[type.to_s] 
    313295            value ? formatter.call(value) : nil 
    314296          else