这个rails查询到底如何工作?

时间:2009-07-08 10:59:36

标签: ruby-on-rails

我刚刚优化了一些控制器方法中的Ruby代码,将其替换为直接数据库查询。替换似乎工作,并且更快。事实是,我不知道Rails如何设法找出正确的查询使用!

查询的目的是计算在给定纬度和经度的特定距离内放置模型的标签计数。距离部分由 GeoKit 插件处理(它基本上添加了方便的方法来为选择添加适当的三角计算),标记部分由 acts_as_taggable_on_steroids 插件完成,它使用多态关联。

以下是原始代码:

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

MyTag类实现了这个:

  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

这是我(非常难看)的第一次尝试,因为我最初发现很难找到正确的轨道咒语来在SQL中完成这项工作。新的替代品是这一行:

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

这导致SQL查询如下:

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

忽略trig(来自GeoKit,结果来自:within和:origin参数),我无法弄清楚这是如何在地球上Rails能够从指令中找出加入'标签',它必须在JOIN中涉及'标签'(它确实如此,因为没有直接的方法来连接地点和标签表),而且还必须使用多态的东西。

换句话说,他是如何(正确地)做到这一点的:

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

...鉴于我从未在代码中提到过taggings表!深入研究可标记的插件,Rails的唯一线索就是这样:

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

...
end

任何人都可以在这里深入了解引人入胜的魔力吗?

1 个答案:

答案 0 :(得分:1)

acts_as_taggable_on_steroids 插件告诉您的Place模型它有多个标签通过Taggings。通过指定此关联,ActiveRecord知道它需要连接标记才能到达标记表。 HABTM关系也是如此。例如:

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)

这会生成以下SQL:

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