Note that there are some explanatory texts on larger screens.

plurals
  1. POHow on earth is this rails query working?
    primarykey
    data
    text
    <p>I have just optimised some Ruby code that was in a controller method, replacing it with a direct database query. The replacement appears to work and is much faster. Thing is, I've no idea how Rails managed to figure out the correct query to use! </p> <p>The purpose of the query is to work out tag counts for Place models within a certain distance of a given latitude and longitude. The distance part is handled by the <em>GeoKit</em> plugin (which basically adds convenience methods to add the appropriate trigonometry calculations to the select), and the tagging part is done by the <em>acts_as_taggable_on_steroids</em> plugin, which uses a polymorphic association. </p> <p>Below is the original code:</p> <pre><code>places = Place.find(:all, :origin=&gt;latlng, :order=&gt;'distance asc', :within=&gt;distance, :limit=&gt;200) tag_counts = MyTag.tagcounts(places) deep_tag_counts=Array.new() tag_counts.each do |tag| count=Place.find_tagged_with(tag.name,:origin=&gt;latlng, :order=&gt;'distance asc', :within=&gt;distance, :limit=&gt;200).size deep_tag_counts&lt;&lt;{:name=&gt;tag.name,:count=&gt;count} end </code></pre> <p>where the MyTag class implements this:</p> <pre><code> def MyTag.tagcounts(places) alltags = places.collect {|p| p.tags}.flatten.sort_by(&amp;:name) lasttag=nil; tagcount=0; result=Array.new alltags.each do |tag| unless (lasttag==nil || lasttag.name==tag.name) result &lt;&lt; MyTag.new(lasttag,tagcount) tagcount=0 end tagcount=tagcount+1 lasttag=tag end unless lasttag==nil then result &lt;&lt; MyTag.new(lasttag,tagcount) end result end </code></pre> <p>This was my (very ugly) first attempt as I originally found it difficult to come up with the right rails incantations to get this done in SQL. The new replacement is this single line:</p> <pre><code>deep_tag_counts=Place.find(:all,:select=&gt;'name,count(*) as count',:origin=&gt;latlng,:within=&gt;distance,:joins=&gt;:tags, :group=&gt;:tag_id) </code></pre> <p>Which results in an SQL query like this:</p> <pre><code>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&gt;50.693170735732 AND places.lat&lt;52.1388692642679 AND places.lng&gt;-2.03785525810908 AND places.lng&lt;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) &lt;= 50) GROUP BY tag_id </code></pre> <p>Ignoring the trig (which is from GeoKit, and results from the :within and :origin parameters), what I can't figure out about this is how on earth Rails was able to figure out from the instruction to join 'tags', that it had to involve 'taggings' in the JOIN (which it does, as there is no direct way to join the places and tags tables), and also that it had to use the polymorphic stuff. </p> <p>In other words, how the heck did it (correctly) come up with this bit:</p> <pre><code>INNER JOIN `taggings` ON (`places`.`id` = `taggings`.`taggable_id` AND `taggings`.`taggable_type` = 'Place') INNER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`) </code></pre> <p>...given that I never mentioned the taggings table in the code! Digging into the taggable plugin, the only clue that Rails has seems to be this:</p> <pre><code>class Tag &lt; ActiveRecord::Base has_many :taggings, :dependent=&gt;:destroy ... end </code></pre> <p>Anybody able to give some insight into the magic going on under the hood here?</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload