Rails关联 - 回调序列/魔术

时间:2010-01-05 15:35:11

标签: ruby-on-rails activerecord

以下面的关联声明为例:

class Post
 has_many :comments
end

通过声明has_many:comments,ActiveRecord添加了几个我特别感兴趣的方法 comments ,它返回注释数组。我浏览了代码,以下似乎是回调序列:

def has_many(association_id, options = {}, &extension)
  reflection = create_has_many_reflection(association_id, options, &extension)
  configure_dependency_for_has_many(reflection)
  add_association_callbacks(reflection.name, reflection.options)

  if options[:through]
    collection_accessor_methods(reflection, HasManyThroughAssociation)
  else
    collection_accessor_methods(reflection, HasManyAssociation)
  end
end

def collection_accessor_methods(reflection, association_proxy_class, writer = true)
  collection_reader_method(reflection, association_proxy_class)

  if writer
    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 |new_value|
      ids = (new_value || []).reject { |nid| nid.blank? }
      send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
    end
  end
end

def collection_reader_method(reflection, association_proxy_class)
  define_method(reflection.name) do |*params|
    force_reload = params.first unless params.empty?
    association = association_instance_get(reflection.name)

    unless association
      association = association_proxy_class.new(self, reflection)
      association_instance_set(reflection.name, association)
    end

    association.reload if force_reload

    association
  end

  define_method("#{reflection.name.to_s.singularize}_ids") do
    if send(reflection.name).loaded? || reflection.options[:finder_sql]
      send(reflection.name).map(&:id)
    else
      send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
    end
  end
end

在这个回调序列中,当我执行@ post.comments时,究竟是用于检索注释的实际SQL?

4 个答案:

答案 0 :(得分:0)

您在此处:获取相关对象的所有ID的标准AR查询

send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)

但确定Activerecord很混乱...... has_many的重新实现(更好的没有eval)可能对你有用:

def has_many(children)
  send(:define_method, children){ eval(children.to_s.singularize.capitalize).all( :conditions => { self.class.name.downcase => name }) }
end

答案 1 :(得分:0)

在关联阅读器中的行

association = association_proxy_class.new(self, reflection)

最后将负责执行find,当实例变量被“询问”并且“看到”@loaded为false时。

答案 2 :(得分:0)

您需要深入了解HasManyAssociation的定义。

colletion_reader_method在Post类上定义了一个名为comments的方法。当调用comments方法时,它确保存储类HasManyAssociation的代理对象(您需要深入了解association_instance_set方法以查看它存储的确切位置),然后返回此代理对象。

我假设在代理上调用方法时会出现SQL,例如,调用each,all或使用[]访问索引。

答案 3 :(得分:0)

我不是100%确定我理解你在寻找什么。

sql生成不在AR中的一个位置。一些数据库特定的东西在数据库“connection_adapters”中。

如果您正在寻找在数据库中找到记录的方式,请查看ActiveRecord :: Base模块中的“construct_finder_sql”和“add_joins”方法。

    def construct_finder_sql(options)
      scope = scope(:find)
      sql  = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
      sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "

      add_joins!(sql, options[:joins], scope)
      ...

    def add_joins!(sql, joins, scope = :auto)
      scope = scope(:find) if :auto == scope
      merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
      case merged_joins
      when Symbol, Hash, Array
        if array_of_strings?(merged_joins)
          sql << merged_joins.join(' ') + " "
        else
          join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
          sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
        end
      when String
        sql << " #{merged_joins} "
      end
    end

我希望这有帮助!