当前位置:首页-技术前沿-ORM/JPA-ActiveRecord

  • Rails源码研究之ActiveRecord:二,Associations
    作者: 本站会员  来源:javaEye  发布时间:2007-10-23 13:27:00
  • 今天学习一下ActiveRecord的Associations相关的源码,即了解一下我们常用的has_many、has_one、belongs_to、has_and_belongs_to_many的原理

    1,activerecord-1.15.3\lib\active_record\associations.rb:

    1. require 'active_record/associations/association_proxy'  
       2. require 'active_record/associations/association_collection'  
       3. require 'active_record/associations/belongs_to_association'  
       4. require 'active_record/associations/belongs_to_polymorphic_association'  
       5. require 'active_record/associations/has_one_association'  
       6. require 'active_record/associations/has_many_association'  
       7. require 'active_record/associations/has_many_through_association'  
       8. require 'active_record/associations/has_and_belongs_to_many_association'  
       9. require 'active_record/deprecated_associations'  
      10.   
      11. module ActiveRecord  
      12.   module Associations  
      13.     module ClassMethods  
      14.       def has_many(association_id, options = {}, &extension)  
      15.         reflection = create_has_many_reflection(association_id, options, &extension)  
      16.   
      17.         configure_dependency_for_has_many(reflection)  
      18.   
      19.         if options[:through]  
      20.           collection_reader_method(reflection, HasManyThroughAssociation)  
      21.         else  
      22.           add_multiple_associated_save_callbacks(reflection.name)  
      23.           add_association_callbacks(reflection.name, reflection.options)  
      24.           collection_accessor_methods(reflection, HasManyAssociation)  
      25.         end  
      26.   
      27.         add_deprecated_api_for_has_many(reflection.name)  
      28.       end  
      29.   
      30.       def has_one(association_id, options = {})  
      31.         reflection = create_has_one_reflection(association_id, options)  
      32.   
      33.         module_eval do  
      34.           after_save <<-EOF  
      35.             association = instance_variable_get("@#{reflection.name}")  
      36.             if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)  
      37.               association["#{reflection.primary_key_name}"] = id  
      38.               association.save(true)  
      39.             end  
      40.           EOF  
      41.         end  
      42.         
      43.         association_accessor_methods(reflection, HasOneAssociation)  
      44.         association_constructor_method(:build,  reflection, HasOneAssociation)  
      45.         association_constructor_method(:create, reflection, HasOneAssociation)  
      46.           
      47.         configure_dependency_for_has_one(reflection)  
      48.   
      49.         # deprecated api  
      50.         deprecated_has_association_method(reflection.name)  
      51.         deprecated_association_comparison_method(reflection.name, reflection.class_name)  
      52.       end  
      53.   
      54.       def belongs_to(association_id, options = {})  
      55.         if options.include?(:class_name) && !options.include?(:foreign_key)  
      56.           ::ActiveSupport::Deprecation.warn(  
      57.           "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ.  When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.",  
      58.           caller)  
      59.         end  
      60.           
      61.         reflection = create_belongs_to_reflection(association_id, options)  
      62.           
      63.         if reflection.options[:polymorphic]  
      64.           association_accessor_methods(reflection, BelongsToPolymorphicAssociation)  
      65.   
      66.           module_eval do  
      67.             before_save <<-EOF  
      68.               association = instance_variable_get("@#{reflection.name}")  
      69.               if association && association.target  
      70.                 if association.new_record?  
      71.                   association.save(true)  
      72.                 end  
      73.                   
      74.                 if association.updated?  
      75.                   self["#{reflection.primary_key_name}"] = association.id  
      76.                   self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s  
      77.                 end  
      78.               end  
      79.             EOF  
      80.           end  
      81.         else  
      82.           association_accessor_methods(reflection, BelongsToAssociation)  
      83.           association_constructor_method(:build,  reflection, BelongsToAssociation)  
      84.           association_constructor_method(:create, reflection, BelongsToAssociation)  
      85.   
      86.           module_eval do  
      87.             before_save <<-EOF  
      88.               association = instance_variable_get("@#{reflection.name}")  
      89.               if !association.nil?   
      90.                 if association.new_record?  
      91.                   association.save(true)  
      92.                 end  
      93.                   
      94.                 if association.updated?  
      95.                   self["#{reflection.primary_key_name}"] = association.id  
      96.                 end  
      97.               end              
      98.             EOF  
      99.           end  
     100.         
     101.           # deprecated api  
     102.           deprecated_has_association_method(reflection.name)  
     103.           deprecated_association_comparison_method(reflection.name, reflection.class_name)  
     104.         end  
     105.   
     106.         if options[:counter_cache]  
     107.           cache_column = options[:counter_cache] == true ?  
     108.             "#{self.to_s.underscore.pluralize}_count" :  
     109.             options[:counter_cache]  
     110.   
     111.           module_eval(  
     112.             "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +  
     113.             " unless #{reflection.name}.nil?'"  
     114.           )  
     115.   
     116.           module_eval(  
     117.             "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +  
     118.             " unless #{reflection.name}.nil?'"  
     119.           )            
     120.         end  
     121.       end  
     122.   
     123.       def has_and_belongs_to_many(association_id, options = {}, &extension)  
     124.         reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)  
     125.           
     126.         add_multiple_associated_save_callbacks(reflection.name)  
     127.         collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)  
     128.   
     129.         old_method = "destroy_without_habtm_shim_for_#{reflection.name}"  
     130.         class_eval <<-end_eval  
     131.           alias_method :#{old_method}, :destroy_without_callbacks  
     132.           def destroy_without_callbacks  
     133.             #{reflection.name}.clear  
     134.             #{old_method}  
     135.           end  
     136.         end_eval  
     137.   
     138.         add_association_callbacks(reflection.name, options)  
     139.           
     140.         # deprecated api  
     141.         deprecated_collection_count_method(reflection.name)  
     142.         deprecated_add_association_relation(reflection.name)  
     143.         deprecated_remove_association_relation(reflection.name)  
     144.         deprecated_has_collection_method(reflection.name)  
     145.       end  
     146.   
     147.       private  
     148.         def association_accessor_methods(reflection, association_proxy_class)  
     149.           define_method(reflection.name) do |*params|  
     150.             force_reload = params.first unless params.empty?  
     151.             association = instance_variable_get("@#{reflection.name}")  
     152.   
     153.             if association.nil? || force_reload  
     154.               association = association_proxy_class.new(self, reflection)  
     155.               retval = association.reload  
     156.               if retval.nil? and association_proxy_class == BelongsToAssociation  
     157.                 instance_variable_set("@#{reflection.name}", nil)  
     158.                 return nil  
     159.               end  
     160.               instance_variable_set("@#{reflection.name}", association)  
     161.             end  
     162.   
     163.             association.target.nil? ? nil : association  
     164.           end  
     165.   
     166.           define_method("#{reflection.name}=") do |new_value|  
     167.             association = instance_variable_get("@#{reflection.name}")  
     168.             if association.nil?  
     169.               association = association_proxy_class.new(self, reflection)  
     170.             end  
     171.   
     172.             association.replace(new_value)  
     173.   
     174.             unless new_value.nil?  
     175.               instance_variable_set("@#{reflection.name}", association)  
     176.             else  
     177.               instance_variable_set("@#{reflection.name}", nil)  
     178.               return nil  
     179.             end  
     180.   
     181.             association  
     182.           end  
     183.   
     184.           define_method("set_#{reflection.name}_target") do |target|  
     185.             return if target.nil? and association_proxy_class == BelongsToAssociation  
     186.             association = association_proxy_class.new(self, reflection)  
     187.             association.target = target  
     188.             instance_variable_set("@#{reflection.name}", association)  
     189.           end  
     190.         end  
     191.   
     192.         def collection_reader_method(reflection, association_proxy_class)  
     193.           define_method(reflection.name) do |*params|  
     194.             force_reload = params.first unless params.empty?  
     195.             association = instance_variable_get("@#{reflection.name}")  
     196.   
     197.             unless association.respond_to?(:loaded?)  
     198.               association = association_proxy_class.new(self, reflection)  
     199.               instance_variable_set("@#{reflection.name}", association)  
     200.             end  
     201.   
     202.             association.reload if force_reload  
     203.   
     204.             association  
     205.           end  
     206.         end  
     207.   
     208.         def collection_accessor_methods(reflection, association_proxy_class)  
     209.           collection_reader_method(reflection, association_proxy_class)  
     210.   
     211.           define_method("#{reflection.name}=") do |new_value|  
     212.             # Loads proxy class instance (defined in collection_reader_method) if not already loaded  
     213.             association = send(reflection.name)   
     214.             association.replace(new_value)  
     215.             association  
     216.           end  
     217.   
     218.           define_method("#{reflection.name.to_s.singularize}_ids") do  
     219.             send(reflection.name).map(&:id)  
     220.           end  
     221.   
     222.           define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|  
     223.             ids = (new_value || []).reject { |nid| nid.blank? }  
     224.             send("#{reflection.name}=", reflection.class_name.constantize.find(ids))  
     225.           end  
     226.         end  
     227.   
     228.         def association_constructor_method(constructor, reflection, association_proxy_class)  
     229.           define_method("#{constructor}_#{reflection.name}") do |*params|  
     230.             attributees      = params.first unless params.empty?  
     231.             replace_existing = params[1].nil? ? true : params[1]  
     232.             association      = instance_variable_get("@#{reflection.name}")  
     233.   
     234.             if association.nil?  
     235.               association = association_proxy_class.new(self, reflection)  
     236.               instance_variable_set("@#{reflection.name}", association)  
     237.             end  
     238.   
     239.             if association_proxy_class == HasOneAssociation  
     240.               association.send(constructor, attributees, replace_existing)  
     241.             else  
     242.               association.send(constructor, attributees)  
     243.             end  
     244.           end  
     245.         end  
     246.   
     247.         def create_has_many_reflection(association_id, options, &extension)  
     248.           options.assert_valid_keys(  
     249.             :class_name, :table_name, :foreign_key,  
     250.             :exclusively_dependent, :dependent,  
     251.             :select, :conditions, :include,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :group, :limit,<img src="/images/forum/smiles/icon_surprised.gif"/>ffset,  
     252.             :as, :through, :source, :source_type,  
     253.             :uniq,  
     254.             :finder_sql, :counter_sql,   
     255.             :before_add, :after_add, :before_remove, :after_remove,   
     256.             :extend  
     257.           )  
     258.   
     259.           options[:extend] = create_extension_module(association_id, extension) if block_given?  
     260.   
     261.           create_reflection(:has_many, association_id, options, self)  
     262.         end  
     263.   
     264.         def create_has_one_reflection(association_id, options)  
     265.           options.assert_valid_keys(  
     266.             :class_name, :foreign_key, :remote, :conditions,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :include, :dependent, :counter_cache, :extend, :as  
     267.           )  
     268.   
     269.           create_reflection(:has_one, association_id, options, self)  
     270.         end  
     271.   
     272.         def create_belongs_to_reflection(association_id, options)  
     273.           options.assert_valid_keys(  
     274.             :class_name, :foreign_key, :foreign_type, :remote, :conditions,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :include, :dependent,   
     275.             :counter_cache, :extend, :polymorphic  
     276.           )  
     277.             
     278.           reflection = create_reflection(:belongs_to, association_id, options, self)  
     279.   
     280.           if options[:polymorphic]  
     281.             reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"  
     282.           end  
     283.   
     284.           reflection  
     285.         end  
     286.           
     287.         def create_has_and_belongs_to_many_reflection(association_id, options, &extension)  
     288.           options.assert_valid_keys(  
     289.             :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,   
     290.             :select, :conditions, :include,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :group, :limit,<img src="/images/forum/smiles/icon_surprised.gif"/>ffset,  
     291.             :uniq,   
     292.             :finder_sql, :delete_sql, :insert_sql,  
     293.             :before_add, :after_add, :before_remove, :after_remove,   
     294.             :extend  
     295.           )  
     296.   
     297.           options[:extend] = create_extension_module(association_id, extension) if block_given?  
     298.   
     299.           reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)  
     300.   
     301.           reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))  
     302.             
     303.           reflection  
     304.         end  
     305.     end  
     306.   end  
     307. end

    该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类

    2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:

    1. module ActiveRecord  
       2.   module Associations  
       3.     class AssociationCollection < AssociationProxy  
       4.   
       5.       def <<(*records)  
       6.         result = true  
       7.         load_target  
       8.   
       9.         @owner.transaction do  
      10.           flatten_deeper(records).each do |record|  
      11.             raise_on_type_mismatch(record)  
      12.             callback(:before_add, record)  
      13.             result &&= insert_record(record) unless @owner.new_record?  
      14.             @target << record  
      15.             callback(:after_add, record)  
      16.           end  
      17.         end  
      18.   
      19.         result && self  
      20.       end  
      21.   
      22.       alias_method :push, :<<  
      23.       alias_method :concat, :<<  
      24.   
      25.       def delete_all  
      26.         load_target  
      27.         delete(@target)  
      28.         reset_target!  
      29.       end  
      30.   
      31.       def delete(*records)  
      32.         records = flatten_deeper(records)  
      33.         records.each { |record| raise_on_type_mismatch(record) }  
      34.         records.reject! { |record| @target.delete(record) if record.new_record? }  
      35.         return if records.empty?  
      36.           
      37.         @owner.transaction do  
      38.           records.each { |record| callback(:before_remove, record) }  
      39.           delete_records(records)  
      40.           records.each do |record|  
      41.             @target.delete(record)  
      42.             callback(:after_remove, record)  
      43.           end  
      44.         end  
      45.       end  
      46.   
      47.       def clear  
      48.         return self if length.zero? # forces load_target if hasn't happened already  
      49.   
      50.         if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all  
      51.           destroy_all  
      52.         else            
      53.           delete_all  
      54.         end  
      55.   
      56.         self  
      57.       end  
      58.         
      59.       def destroy_all  
      60.         @owner.transaction do  
      61.           each { |record| record.destroy }  
      62.         end  
      63.   
      64.         reset_target!  
      65.       end  
      66.   
      67.       def create(attributes = {})  
      68.         if attributes.is_a?(Array)  
      69.           attributes.collect { |attr| create(attr) }  
      70.         else  
      71.           record = build(attributes)  
      72.           record.save unless @owner.new_record?  
      73.           record  
      74.         end  
      75.       end  
      76.   
      77.       protected  
      78.         def find_target  
      79.           records =  
      80.             if @reflection.options[:finder_sql]  
      81.               @reflection.klass.find_by_sql(@finder_sql)  
      82.             else  
      83.               find(:all)  
      84.             end  
      85.   
      86.           @reflection.options[:uniq] ? uniq(records) : records  
      87.         end  
      88.     end  
      89.   end  
      90. end

    AssociationCollection是HasOneAssociation、HasManyAssociation、 HasManyThroughAssociation、BelongsToAssociation、 HasAndBelongsToManyAssociation、
    BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法

    3,activerecord-1.15.3\lib\active_record\reflection.rb:

    1. module ActiveRecord  
       2.   module Reflection  
       3.   
       4.     module ClassMethods  
       5.       def create_reflection(macro, name, options, active_record)  
       6.         case macro  
       7.           when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many  
       8.             reflection = AssociationReflection.new(macro, name, options, active_record)  
       9.           when :composed_of  
      10.             reflection = AggregateReflection.new(macro, name, options, active_record)  
      11.         end  
      12.         write_inheritable_hash :reflections, name => reflection  
      13.         reflection  
      14.       end  
      15.     end  
      16.   
      17.     class MacroReflection  
      18.       attr_reader :active_record  
      19.       def initialize(macro, name, options, active_record)  
      20.         @macro, @name, @options, @active_record = macro, name, options, active_record  
      21.       end  
      22.     end  
      23.   
      24.     class AggregateReflection < MacroReflection #:nodoc:  
      25.       def klass  
      26.         @klass ||= Object.const_get(options[:class_name] || class_name)  
      27.       end  
      28.     end  
      29.   
      30.     class AssociationReflection < MacroReflection #:nodoc:  
      31.       def klass  
      32.         @klass ||= active_record.send(:compute_type, class_name)  
      33.       end  
      34.     end  
      35.   
      36.   end  
      37. end

    该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用

    4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:

    1. module ActiveRecord  
    2.   module Associations  
    3.     class HasManyAssociation < AssociationCollection  
    4.       def build(attributes = {})  
    5.         if attributes.is_a?(Array)  
    6.           attributes.collect { |attr| build(attr) }  
    7.         else  
    8.           record = @reflection.klass.new(attributes)  
    9.           set_belongs_to_association_for(record)  
    10.             
    11.           @target ||= [] unless loaded?  
    12.           @target << record  
    13.             
    14.           record  
    15.         end  
    16.       end  
    17.   
    18.       def count(*args)  
    19.         if @reflection.options[:counter_sql]  
    20.           @reflection.klass.count_by_sql(@counter_sql)  
    21.         elsif @reflection.options[:finder_sql]  
    22.           @reflection.klass.count_by_sql(@finder_sql)  
    23.         else  
    24.           column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args)            
    25.           options[:conditions] = options[:conditions].nil? ?  
    26.             @finder_sql :  
    27.             @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"  
    28.           options[:include] = @reflection.options[:include]  
    29.   
    30.           @reflection.klass.count(column_name, options)  
    31.         end  
    32.       end  
    33.   
    34.       def find(*args)  
    35.         options = Base.send(:extract_options_from_args!, args)  
    36.   
    37.         if @reflection.options[:finder_sql]  
    38.           expects_array = args.first.kind_of?(Array)  
    39.           ids = args.flatten.compact.uniq  
    40.   
    41.           if ids.size == 1  
    42.             id = ids.first  
    43.             record = load_target.detect { |record| id == record.id }  
    44.             expects_array ? [ record ] : record  
    45.           else  
    46.             load_target.select { |record| ids.include?(record.id) }  
    47.           end  
    48.         else  
    49.           conditions = "#{@finder_sql}"  
    50.           if sanitized_conditions = sanitize_sql(options[:conditions])  
    51.             conditions << " AND (#{sanitized_conditions})"  
    52.           end  
    53.           options[:conditions] = conditions  
    54.   
    55.           if options[:order] && @reflection.options[:order]  
    56.             options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"  
    57.           elsif @reflection.options[:order]  
    58.             options[:order] = @reflection.options[:order]  
    59.           end  
    60.   
    61.           merge_options_from_reflection!(options)  
    62.   
    63.           # Pass through args exactly as we received them.  
    64.           args << options  
    65.           @reflection.klass.find(*args)  
    66.         end  
    67.       end  
    68.   
    69.       protected  
    70.         def method_missing(method, *args, &block)  
    71.           if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))  
    72.             super  
    73.           else  
    74.             create_scoping = {}  
    75.             set_belongs_to_association_for(create_scoping)  
    76.   
    77.             @reflection.klass.with_scope(  
    78.               :create => create_scoping,  
    79.               :find => {  
    80.                 :conditions => @finder_sql,   
    81.                 :joins      => @join_sql,   
    82.                 :readonly   => false  
    83.               }  
    84.             ) do  
    85.               @reflection.klass.send(method, *args, &block)  
    86.             end  
    87.           end  
    88.         end  
    89.   
    90.     end  
    91.   end  
    92. end  

    HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法

    总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
    了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类

    转载自:http://hideto.javaeye.com/blog/92501

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 评论 】 【收藏】 【 推荐给朋友 】 【字体: 】 【关闭
评论:共0条

发表评论:
评论: 
    

Copyright (C) 2005 EasyJF.com 简易java框架网 渝ICP备06004507号
如有意见请与我们联系