2010-01-05 3 views
1

Prise déclaration suivante d'association à titre d'exemple:Rails Associations - Séquence de rappel/Magie

class Post 
has_many :comments 
end 

Tout en déclarant les has_many: commentaires, ActiveRecord ajoute plusieurs méthodes dont je suis particulièrement intéressé par commentaires qui retourne un tableau des commentaires. J'ai parcouru le code et suivant semble être la séquence de rappel:

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 

Dans cette séquence de callbacks, où est exactement le SQL réel en cours d'exécution pour récupérer les commentaires quand je fais @ post.comments?

+0

Qu'est-ce que vous essayez de faire? – makevoid

+0

Je veux savoir comment sont implémentées les méthodes de lecture d'association par ActiveRecord. – Dharam

Répondre

0

Vous devez approfondir la définition de HasManyAssociation. Colletion_reader_method définit une méthode appelée commentaries sur votre classe Post.

Lorsque la méthode comments est appelée, elle s'assure qu'un objet proxy de la classe HasManyAssociation est stocké (vous devrez creuser dans la méthode association_instance_set pour voir exactement où elle le stocke), puis renvoie cet objet proxy.

Je présume que le SQL entre lorsque vous appelez une méthode sur le proxy, par exemple, appelez chacun, tous ou accédez à un index avec [].

+0

J'ai regardé en profondeur dans l'association_instance_set et l'association_instance_get, et je ne voyais toujours pas où AR est en train d'exécuter/d'appeler le SQL pour charger les commentaires d'un post particulier quand nous appelons @ post.comments – Dharam

+0

Je ne pense pas que cela soit juste pour appeler @ post.comments, sauf peut-être dans le constructeur de HasManyAssociation. Comme je l'ai dit association_instance_ (get/set) n'est pas d'où le SQL proviendra, il stocke juste l'objet proxy dans votre modèle. Regarder HasManyAssociation devrait être la voie à suivre. –

+0

Après un rapide coup d'œil sur la source, les directives de base de données sont divisées en deux endroits principaux selon qu'elles sont spécifiques à la base de données ou non (qu'il s'agisse de requêtes ou de directives de gestion de base de données). Gestion spécifique à la base de données et à la base de données SQL/directives se trouvent dans les adaptateurs de connexion (soit l'adaptateur abstrait et les classes associées, soit l'adaptateur spécifique à la base de données, par exemple l'adaptateur mysql). Le reste est dans la classe de base activerecord et la classe d'association bien que ceux-ci soient appelés à partir des classes de proxy d'association. –

0

Vous êtes: une requête standard AR obtenir tous les IDs des objets associés

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

mais vous activerecord est en désordre ... une nouvelle mise en œuvre (mieux sans eval) de has_many peut peut-être être utile pour vous:

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

Je n'essaie pas de ré-implémenter ce qu'AR fait déjà.Ce que j'essaie de comprendre, c'est quand vous dites @ post.comments où exactement le SQL lié à la récupération des commentaires est implémenté (quel fichier et quelle méthode). En regardant le collection_reader_method, je ne vois nulle part ce qu'il appelle le SQL, j'ai même creusé plus profond dans l'association_proxy.rb aussi – Dharam

0

dans le lecteur d'association de la ligne

association = association_proxy_class.new(self, reflection) 

dans la salle d sera responsable de l'exécution de la recherche, lorsque la variable d'instance est "demandée" et "voit" que @loaded est faux.

+0

@adamaig: Du code que j'ai collé, je peux facilement déduire que association = association_proxy_class.new (self, reflexion) est la méthode qui est responsable, mais je veux savoir comment l'exécution traverse quand @ post.comments est appelée. De ce que je vois dans le code AR, association_proxy_class.new (self, reflexion) n'est rien d'autre que HasManyAssociation.new (@post, reflexion) et comme AR est en train de definir la methode des commentaires et revoir le corps de la methode, je ne vois pas exactement AR appelle le SQL. – Dharam

0

Je ne suis pas sûr à 100% que je comprends ce que vous cherchez.

La génération sql n'est pas au même endroit en AR. Certaines choses spécifiques à la base de données se trouvent dans la base de données "connection_adapters".

Si vous cherchez comment les enregistrements se trouvent dans la base de données, regardez les méthodes "construct_finder_sql" et "add_joins" dans le module ActiveRecord :: Base.

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) 
     ... 

et

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 

J'espère que cela aide!