2009-07-08 9 views
4

Je viens d'optimiser un code Ruby qui était dans une méthode de contrôleur, en le remplaçant par une requête de base de données directe. Le remplacement semble fonctionner et est beaucoup plus rapide. Chose est, je ne sais pas comment Rails a réussi à trouver la bonne requête à utiliser!Comment diable cette requête rails fonctionne-t-elle?

Le but de la requête est d'élaborer des décomptes de repères pour les modèles Placer à une certaine distance d'une latitude et d'une longitude données. La partie distance est gérée par le plugin GeoKit (qui ajoute essentiellement des méthodes de commodité pour ajouter les calculs de trigonométrie appropriés au select), et la partie marquage est faite par le plugin acts_as_taggable_on_steroids, qui utilise une association polymorphique.

est Ci-dessous le code d'origine:

places = Place.find(:all, :origin=>latlng, :order=>'distance asc', :within=>distance, :limit=>200) 
tag_counts = MyTag.tagcounts(places) 
deep_tag_counts=Array.new() 
tag_counts.each do |tag| 
    count=Place.find_tagged_with(tag.name,:origin=>latlng, :order=>'distance asc', :within=>distance, :limit=>200).size 
    deep_tag_counts<<{:name=>tag.name,:count=>count} 
end 

où la classe MyTag implémente ceci:

def MyTag.tagcounts(places) 
    alltags = places.collect {|p| p.tags}.flatten.sort_by(&:name) 
    lasttag=nil; 
    tagcount=0; 
    result=Array.new 
    alltags.each do |tag| 
     unless (lasttag==nil || lasttag.name==tag.name) 
     result << MyTag.new(lasttag,tagcount) 
     tagcount=0 
     end 
     tagcount=tagcount+1 
     lasttag=tag 
    end 
    unless lasttag==nil then 
     result << MyTag.new(lasttag,tagcount) 
    end 
    result 
    end 

Ce fut mon (très laid) première tentative comme je l'ai d'abord trouvé difficile à venir avec les incantations de bons rails pour cela en SQL. Le nouveau remplacement est cette ligne unique:

deep_tag_counts=Place.find(:all,:select=>'name,count(*) as count',:origin=>latlng,:within=>distance,:joins=>:tags, :group=>:tag_id) 

Quels sont les résultats dans une requête SQL comme ceci:

SELECT name,count(*) as count, (ACOS(least(1,COS(0.897378837271255)*COS(-0.0153398733287034)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+ 
COS(0.897378837271255)*SIN(-0.0153398733287034)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+ 
SIN(0.897378837271255)*SIN(RADIANS(places.lat))))*3963.19) 
AS distance FROM `places` INNER JOIN `taggings` ON (`places`.`id` = `taggings`.`taggable_id` AND `taggings`.`taggable_type` = 'Place') INNER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`) WHERE (places.lat>50.693170735732 AND places.lat<52.1388692642679 AND places.lng>-2.03785525810908 AND places.lng<0.280035258109084 AND (ACOS(least(1,COS(0.897378837271255)*COS(-0.0153398733287034)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+ 
COS(0.897378837271255)*SIN(-0.0153398733287034)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+ 
SIN(0.897378837271255)*SIN(RADIANS(places.lat))))*3963.19) 
<= 50) GROUP BY tag_id 

Ignorant la TRIG (qui est de Geokit, et les résultats de la: à l'intérieur et: paramètres d'origine), ce que je ne peux pas comprendre à ce sujet, c'est comment Rails était capable de comprendre à partir de l'instruction de rejoindre les «tags», qu'il devait impliquer des «taggings» dans le JOIN (ce qu'il fait, car il n'y a pas façon directe de rejoindre les tables de lieux et de tags), et aussi qu'il fallait utiliser le truc polymorphe.

En d'autres termes, comment le diable l'a fait (à juste titre) viennent avec ce bit:

INNER JOIN `taggings` ON (`places`.`id` = `taggings`.`taggable_id` AND `taggings`.`taggable_type` = 'Place') INNER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`) 

... étant donné que je ne parle jamais de la table dans le code des marquages! Creuser dans le plug-in tagable, le seul indice que Rails a semble être ceci:

class Tag < ActiveRecord::Base 
    has_many :taggings, :dependent=>:destroy 

... 
end 

Toute personne capable de donner un aperçu de la magie se passe sous le capot ici?

Répondre

1

Le plugin acts_as_taggable_on_steroids indique à votre modèle Place qu'il contient un grand nombre de balises Taggings. Avec cette association spécifiée, ActiveRecord sait qu'il doit joindre des marquages ​​pour accéder à la table des marqueurs. La même chose est vraie pour les relations HABTM. Par exemple:

class Person < ActiveRecord::Base 
    has_and_belongs_to_many :tags 
end 

class Tag < ActiveRecord::Base 
    has_and_belongs_to_many :people 
end 

>> Person.first(:joins => :tags) 

Cela produit l'instruction SQL suivante:

SELECT "people".* 
FROM "people" 
    INNER JOIN "people_tags" ON "people_tags".person_id = "people".id 
    INNER JOIN "tags" ON "tags".id = "people_tags".tag_id 
LIMIT 1 
Questions connexes