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

Ticket #9677: add_includes_to_to_json.2.diff

File add_includes_to_to_json.2.diff, 8.8 kB (added by chuyeow, 1 year ago)

Add :includes option to ActiveRecord#to_json

  • 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      def serializable_associations 
     42 
     43      end 
     44 
    4145      def serializable_record 
    4246        returning(serializable_record = {}) do 
    4347          serializable_names.each { |name| serializable_record[name] = @record.send(name) } 
     
    4650 
    4751      def serialize 
    4852        # overwrite to implement 
    49       end         
    50      
     53      end 
     54 
    5155      def to_s(&block) 
    5256        serialize(&block) 
    5357      end 
  • activerecord/lib/active_record/serializers/json_serializer.rb

    old new  
    1313      def serialize 
    1414        serializable_record.to_json 
    1515      end 
     16 
     17      def serializable_record 
     18        returning(serializable_record = {}) do 
     19          serializable_names.each { |name| serializable_record[name] = @record.send(name) } 
     20          add_includes(serializable_record) 
     21        end 
     22      end 
     23 
     24      protected 
     25        # Add associations specified via the :includes option. 
     26        def add_includes(serializable_record) 
     27          if include_associations = options.delete(:include) 
     28            base_only_or_except = { :except => options[:except], 
     29                                    :only => options[:only] } 
     30 
     31            include_has_options = include_associations.is_a?(Hash) 
     32            associations = include_has_options ? include_associations.keys : Array(include_associations) 
     33 
     34            for association in associations 
     35              association_options = include_has_options ? include_associations[association] : base_only_or_except 
     36 
     37              opts = options.merge(association_options) 
     38 
     39              case @record.class.reflect_on_association(association).macro 
     40              when :has_many, :has_and_belongs_to_many 
     41                records = @record.send(association).to_a 
     42                unless records.empty? 
     43                  serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record } 
     44                end 
     45              when :has_one, :belongs_to 
     46                if record = @record.send(association) 
     47                  serializable_record[association] = self.class.new(record, opts).serializable_record 
     48                end 
     49              end 
     50            end 
     51 
     52            options[:include] = include_associations 
     53          end 
     54        end 
    1655    end 
    1756  end 
    1857end