ActiveRecord为has_many生成的错误的sql:通过与STI的关系

时间:2011-03-23 14:06:16

标签: ruby-on-rails ruby ruby-on-rails-3 activerecord sti

考虑这些模型:

class First < ActiveRecord::Base
  has_many :tags
  has_many :thirds, :through => :tags
end

class Second < ActiveRecord::Base
end

class Third < Second
  has_many :tags
  has_many :firsts, :through => :tags
end

class Tag < ActiveRecord::Base
  belongs_to :first
  belongs_to :third
end

换句话说,我们有一个has_many:through'tag-style'关系,但其中一个模型(Third)是继承自另一个(第二个)的STI。

假设我想进行连接,以查看First的所有实例,以获得First的某些值:

@thirds = Third.joins(:firsts).where("first.id = 2")

这将按预期工作;生成的sql(通过to_sql)为:

SELECT `seconds`.* FROM `seconds`
INNER JOIN `tags` ON `seconds`.`id` = `tags`.`third_id`
INNER JOIN `firsts` ON `firsts`.`id` = `tags`.`first_id`
WHERE `seconds`.`type` = 'Third' AND (first.id = 1)

这不适用于另一个方向:

@firsts = First.joins(:thirds).where("second.id = 2")

生成的SQL是:

SELECT `firsts`.* FROM `firsts` 
INNER JOIN `tags` ON `firsts`.`id` = `tags`.`first_id` 
INNER JOIN `seconds` ON `seconds`.`type` = 'Third'
WHERE (second.id = 2)

这会导致标记重复,因为以下事实:秒与标记表没有正确连接,如上面的第一种情况(参见每种情况下sql语句的第三行)。带有标签的所有第一个将显示在结果表中,WHERE子句完全无效。

如果需要指定别的东西,我还没有碰到它。如果有人知道如何强迫这项工作,请告诉我。否则,我假设这是Rails中的一个错误。哦,请不要建议我使用旧的model.find()方法,这些方法将被弃用。

更新:

https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6608-generated-sql-for-has_many-through-relation-wrong-when-used-with-sti

因此,根据确认这是一个错误的人,'如果你在基类中定义关联,它按预期工作。' 有谁知道这意味着什么/如何做到这一点?

1 个答案:

答案 0 :(得分:1)

您的查询几乎没有变化:

@thirds = Third.joins(:firsts).where(:firsts => {:id => 2})
@firsts = First.joins(:thirds).where(:thirds => {:id => 2})

我认为你应该尝试在你的STI模型中添加一些coomon的东西:

class Third < Second
  has_many :tags
  has_many :firsts, :through => :tags
  def self.model_name
    name = "seconds"
    name.instance_eval do
      def plural;   pluralize;   end
      def singular; singularize; end
      def i18n_key; singularize; end
      def human(*args); singularize; end
    end
    return name
  end
end

并查看这篇精彩的阅读http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html

或者您可以在该链接中阅读,您可以使用这种看起来不太清晰的方法

def self.inherited(child)
  child.instance_eval do
    def model_name
      Second.model_name
    end
  end
  super
end