Ich habe gerade einen Ruby-Code optimiert, der in einer Controller-Methode war, und ihn durch eine direkte Datenbankabfrage ersetzt. Der Ersatz scheint zu funktionieren und ist viel schneller. Die Sache ist, ich habe keine Ahnung, wie Rails geschafft, herauszufinden, die richtige Abfrage zu verwenden!
Ziel der Abfrage ist es, die Anzahl der Markierungen für Ortsmodelle innerhalb einer bestimmten Entfernung zu einem bestimmten Breiten- und Längengrad zu ermitteln. Der Teil der Entfernung wird von der GeoKit Plugin (das im Wesentlichen Komfortmethoden hinzufügt, um die entsprechenden trigonometrischen Berechnungen zum Select hinzuzufügen), und der Tagging-Teil wird von der acts_as_taggable_on_steroids Plugin, das eine polymorphe Assoziation verwendet.
Nachstehend finden Sie den ursprünglichen Code:
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
wo die Klasse MyTag dies implementiert:
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
Dies war mein (sehr hässlicher) erster Versuch, da ich ursprünglich Schwierigkeiten hatte, die richtigen Rails-Beschwörungen zu finden, um dies in SQL zu erreichen. Der neue Ersatz ist diese eine Zeile:
deep_tag_counts=Place.find(:all,:select=>'name,count(*) as count',:origin=>latlng,:within=>distance,:joins=>:tags, :group=>:tag_id)
Das Ergebnis ist eine SQL-Abfrage wie diese:
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
Abgesehen von der Trigonometrie (die aus GeoKit stammt und aus den Parametern :within und :origin resultiert), kann ich nicht verstehen, wie Rails aus der Anweisung, 'tags' zu verbinden, herausfinden konnte, dass es 'taggings' in den JOIN einbeziehen musste (was es auch tut, da es keinen direkten Weg gibt, die Tabellen places und tags zu verbinden) und dass es das polymorphe Zeug verwenden musste.
Mit anderen Worten: Wie zum Teufel ist sie (korrekt) auf diesen Teil gekommen?
INNER JOIN `taggings` ON (`places`.`id` = `taggings`.`taggable_id` AND `taggings`.`taggable_type` = 'Place') INNER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`)
...da ich die Tagging-Tabelle im Code nie erwähnt habe! Graben in der taggable Plugin, der einzige Hinweis, dass Rails hat, scheint dies zu sein:
class Tag < ActiveRecord::Base
has_many :taggings, :dependent=>:destroy
...
end
Kann jemand einen Einblick in die Magie geben, die hier unter der Haube vor sich geht?