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?