原创作者: hideto
阅读:1499次
评论:0条
更新时间:2011-05-26
今天学习一下ActiveRecord的Associations相关的源码,即了解一下我们常用的has_many、has_one、belongs_to、has_and_belongs_to_many的原理
1,activerecord-1.15.3\lib\active_record\associations.rb:
该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类
2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:
AssociationCollection是HasOneAssociation、HasManyAssociation、HasManyThroughAssociation、BelongsToAssociation、HasAndBelongsToManyAssociation、
BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法
3,activerecord-1.15.3\lib\active_record\reflection.rb:
该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用
4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:
HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法
总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类
1,activerecord-1.15.3\lib\active_record\associations.rb:
require 'active_record/associations/association_proxy' require 'active_record/associations/association_collection' require 'active_record/associations/belongs_to_association' require 'active_record/associations/belongs_to_polymorphic_association' require 'active_record/associations/has_one_association' require 'active_record/associations/has_many_association' require 'active_record/associations/has_many_through_association' require 'active_record/associations/has_and_belongs_to_many_association' require 'active_record/deprecated_associations' module ActiveRecord module Associations module ClassMethods def has_many(association_id, options = {}, &extension) reflection = create_has_many_reflection(association_id, options, &extension) configure_dependency_for_has_many(reflection) if options[:through] collection_reader_method(reflection, HasManyThroughAssociation) else add_multiple_associated_save_callbacks(reflection.name) add_association_callbacks(reflection.name, reflection.options) collection_accessor_methods(reflection, HasManyAssociation) end add_deprecated_api_for_has_many(reflection.name) end def has_one(association_id, options = {}) reflection = create_has_one_reflection(association_id, options) module_eval do after_save <<-EOF association = instance_variable_get("@#{reflection.name}") if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id) association["#{reflection.primary_key_name}"] = id association.save(true) end EOF end association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) configure_dependency_for_has_one(reflection) # deprecated api deprecated_has_association_method(reflection.name) deprecated_association_comparison_method(reflection.name, reflection.class_name) end def belongs_to(association_id, options = {}) if options.include?(:class_name) && !options.include?(:foreign_key) ::ActiveSupport::Deprecation.warn( "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.", caller) end reflection = create_belongs_to_reflection(association_id, options) if reflection.options[:polymorphic] association_accessor_methods(reflection, BelongsToPolymorphicAssociation) module_eval do before_save <<-EOF association = instance_variable_get("@#{reflection.name}") if association && association.target if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s end end EOF end else association_accessor_methods(reflection, BelongsToAssociation) association_constructor_method(:build, reflection, BelongsToAssociation) association_constructor_method(:create, reflection, BelongsToAssociation) module_eval do before_save <<-EOF association = instance_variable_get("@#{reflection.name}") if !association.nil? if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id end end EOF end # deprecated api deprecated_has_association_method(reflection.name) deprecated_association_comparison_method(reflection.name, reflection.class_name) end if options[:counter_cache] cache_column = options[:counter_cache] == true ? "#{self.to_s.underscore.pluralize}_count" : options[:counter_cache] module_eval( "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + " unless #{reflection.name}.nil?'" ) module_eval( "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + " unless #{reflection.name}.nil?'" ) end end def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) add_multiple_associated_save_callbacks(reflection.name) collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) old_method = "destroy_without_habtm_shim_for_#{reflection.name}" class_eval <<-end_eval alias_method :#{old_method}, :destroy_without_callbacks def destroy_without_callbacks #{reflection.name}.clear #{old_method} end end_eval add_association_callbacks(reflection.name, options) # deprecated api deprecated_collection_count_method(reflection.name) deprecated_add_association_relation(reflection.name) deprecated_remove_association_relation(reflection.name) deprecated_has_collection_method(reflection.name) end private def association_accessor_methods(reflection, association_proxy_class) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = instance_variable_get("@#{reflection.name}") if association.nil? || force_reload association = association_proxy_class.new(self, reflection) retval = association.reload if retval.nil? and association_proxy_class == BelongsToAssociation instance_variable_set("@#{reflection.name}", nil) return nil end instance_variable_set("@#{reflection.name}", association) end association.target.nil? ? nil : association end define_method("#{reflection.name}=") do |new_value| association = instance_variable_get("@#{reflection.name}") if association.nil? association = association_proxy_class.new(self, reflection) end association.replace(new_value) unless new_value.nil? instance_variable_set("@#{reflection.name}", association) else instance_variable_set("@#{reflection.name}", nil) return nil end association end define_method("set_#{reflection.name}_target") do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target instance_variable_set("@#{reflection.name}", association) end end def collection_reader_method(reflection, association_proxy_class) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = instance_variable_get("@#{reflection.name}") unless association.respond_to?(:loaded?) association = association_proxy_class.new(self, reflection) instance_variable_set("@#{reflection.name}", association) end association.reload if force_reload association end end def collection_accessor_methods(reflection, association_proxy_class) collection_reader_method(reflection, association_proxy_class) define_method("#{reflection.name}=") do |new_value| # Loads proxy class instance (defined in collection_reader_method) if not already loaded association = send(reflection.name) association.replace(new_value) association end define_method("#{reflection.name.to_s.singularize}_ids") do send(reflection.name).map(&:id) end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) end end def association_constructor_method(constructor, reflection, association_proxy_class) define_method("#{constructor}_#{reflection.name}") do |*params| attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] association = instance_variable_get("@#{reflection.name}") if association.nil? association = association_proxy_class.new(self, reflection) instance_variable_set("@#{reflection.name}", association) end if association_proxy_class == HasOneAssociation association.send(constructor, attributees, replace_existing) else association.send(constructor, attributees) end end end def create_has_many_reflection(association_id, options, &extension) options.assert_valid_keys( :class_name, :table_name, :foreign_key, :exclusively_dependent, :dependent, :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, :extend ) options[:extend] = create_extension_module(association_id, extension) if block_given? create_reflection(:has_many, association_id, options, self) end def create_has_one_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as ) create_reflection(:has_one, association_id, options, self) end def create_belongs_to_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :polymorphic ) reflection = create_reflection(:belongs_to, association_id, options, self) if options[:polymorphic] reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type" end reflection end def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys( :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, :select, :conditions, :include, :order, :group, :limit, :offset, :uniq, :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, :extend ) options[:extend] = create_extension_module(association_id, extension) if block_given? reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) reflection end end end end
该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类
2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:
module ActiveRecord module Associations class AssociationCollection < AssociationProxy def <<(*records) result = true load_target @owner.transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) callback(:before_add, record) result &&= insert_record(record) unless @owner.new_record? @target << record callback(:after_add, record) end end result && self end alias_method :push, :<< alias_method :concat, :<< def delete_all load_target delete(@target) reset_target! end def delete(*records) records = flatten_deeper(records) records.each { |record| raise_on_type_mismatch(record) } records.reject! { |record| @target.delete(record) if record.new_record? } return if records.empty? @owner.transaction do records.each { |record| callback(:before_remove, record) } delete_records(records) records.each do |record| @target.delete(record) callback(:after_remove, record) end end end def clear return self if length.zero? # forces load_target if hasn't happened already if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all destroy_all else delete_all end self end def destroy_all @owner.transaction do each { |record| record.destroy } end reset_target! end def create(attributes = {}) if attributes.is_a?(Array) attributes.collect { |attr| create(attr) } else record = build(attributes) record.save unless @owner.new_record? record end end protected def find_target records = if @reflection.options[:finder_sql] @reflection.klass.find_by_sql(@finder_sql) else find(:all) end @reflection.options[:uniq] ? uniq(records) : records end end end end
AssociationCollection是HasOneAssociation、HasManyAssociation、HasManyThroughAssociation、BelongsToAssociation、HasAndBelongsToManyAssociation、
BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法
3,activerecord-1.15.3\lib\active_record\reflection.rb:
module ActiveRecord module Reflection module ClassMethods def create_reflection(macro, name, options, active_record) case macro when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many reflection = AssociationReflection.new(macro, name, options, active_record) when :composed_of reflection = AggregateReflection.new(macro, name, options, active_record) end write_inheritable_hash :reflections, name => reflection reflection end end class MacroReflection attr_reader :active_record def initialize(macro, name, options, active_record) @macro, @name, @options, @active_record = macro, name, options, active_record end end class AggregateReflection < MacroReflection #:nodoc: def klass @klass ||= Object.const_get(options[:class_name] || class_name) end end class AssociationReflection < MacroReflection #:nodoc: def klass @klass ||= active_record.send(:compute_type, class_name) end end end end
该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用
4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:
module ActiveRecord module Associations class HasManyAssociation < AssociationCollection def build(attributes = {}) if attributes.is_a?(Array) attributes.collect { |attr| build(attr) } else record = @reflection.klass.new(attributes) set_belongs_to_association_for(record) @target ||= [] unless loaded? @target << record record end end def count(*args) if @reflection.options[:counter_sql] @reflection.klass.count_by_sql(@counter_sql) elsif @reflection.options[:finder_sql] @reflection.klass.count_by_sql(@finder_sql) else column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args) options[:conditions] = options[:conditions].nil? ? @finder_sql : @finder_sql + " AND (#{sanitize_sql(options[:conditions])})" options[:include] = @reflection.options[:include] @reflection.klass.count(column_name, options) end end def find(*args) options = Base.send(:extract_options_from_args!, args) if @reflection.options[:finder_sql] expects_array = args.first.kind_of?(Array) ids = args.flatten.compact.uniq if ids.size == 1 id = ids.first record = load_target.detect { |record| id == record.id } expects_array ? [ record ] : record else load_target.select { |record| ids.include?(record.id) } end else conditions = "#{@finder_sql}" if sanitized_conditions = sanitize_sql(options[:conditions]) conditions << " AND (#{sanitized_conditions})" end options[:conditions] = conditions if options[:order] && @reflection.options[:order] options[:order] = "#{options[:order]}, #{@reflection.options[:order]}" elsif @reflection.options[:order] options[:order] = @reflection.options[:order] end merge_options_from_reflection!(options) # Pass through args exactly as we received them. args << options @reflection.klass.find(*args) end end protected def method_missing(method, *args, &block) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) super else create_scoping = {} set_belongs_to_association_for(create_scoping) @reflection.klass.with_scope( :create => create_scoping, :find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false } ) do @reflection.klass.send(method, *args, &block) end end end end end end
HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法
总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类
评论 共 0 条 请登录后发表评论