ActiveRecord::Relation 连接的条件不仅仅是外键

发布于 2024-10-20 06:53:52 字数 375 浏览 0 评论 0原文

有没有一种方法可以使用 ActiveRecord::Relation 为左外连接指定多个条件?

以下面的SQL语句为例。如何使用 ActiveRecord::Relation 对象重写它?

SELECT `texts`.*, `text_translations`.translation FROM `texts` LEFT OUTER JOIN `text_translations` ON `text_translations`.`id` = `texts`.`id` AND `text_translations`.`locale` = 'en'

在ActiveRecord 3.0.3+下有什么办法可以做到这一点吗?

提前致谢。

Is there any way to specify more than one conditions for a left outer join using ActiveRecord::Relation?

Take the following SQL statement for example. How can anyone rewrite this using ActiveRecord::Relation objects?

SELECT `texts`.*, `text_translations`.translation FROM `texts` LEFT OUTER JOIN `text_translations` ON `text_translations`.`id` = `texts`.`id` AND `text_translations`.`locale` = 'en'

Is there any way to do this under ActiveRecord 3.0.3+?

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夏雨凉 2024-10-27 06:53:52

首先您应该考虑使用rails/activerecord 一致性关系。这意味着 text_translations 表中的外键应称为 text_id

像这样创建模型和关联:

class Text < ActiveRecord::Base

  # all possible translations!
  has_many :text_translations

  scope :with_translation_for, lambda { |lang| {
    :select     => "texts.*, tt.translation",
    :joins      => "LEFT OUTER JOIN text_translations AS tt ON tt.text_id = texts.id AND tt.locale = #{ActiveRecord::Base.sanitize(lang)}"
  }}


  # return nil if translation hasn't been loaded, otherwise you get a nasty NoMethod exception
  def translation
    read_attribute(:translation)
  end

end

以及

class TextTranslation < ActiveRecord::Base
  # every translation belongs to a text
  belongs_to :text

  # define a scope for the language
  scope :language, lambda { |lang| where(['locale = ?', lang]) }

end

如何使用:

texts = Text.with_translation_for('en')
texts.each do |c_text|
    unless c_text.translation.nil?
        puts c_text.translation
    else
        puts "No translation available!"
    end
end

现在谈谈优点和缺点,使用 LEFT OUTER join 的方式将加载您全部即使没有所需语言的文本翻译。缺点是您不会获得“TextTranslation”模型对象。

另一种方法是仅加载具有所需翻译的文本。您可以这样做:

texts = Text.includes(:text_translations).where(:text_translations => {:locale => 'en'})

现在 texts[i].text_translations 将返回一个数组,其中包含与语言环境“en”匹配的此文本的所有 TextTranslations 模型对象。但在语言环境“en”中没有翻译的文本将不会显示。

编辑

连接到您的评论:

在关系上使用 .join(:tablename) 的问题是,它将导致 INNER JOIN,因此这不是一个选项。您必须显式声明 LEFT 连接。另一件事是,如果您使用类似 Text.includes(:text_translations).where(['text_translations.locale = ?', 'en']) 的条件,该条件将应用于 SQL 查询,如下所示整体而不是可能的 LEFT 连接本身。您实际上可以做的是声明这样的关联:

has_many :english_translations, :class_name => 'TextTranslation', :conditions => ['locale = ?', 'en']  

这样您就可以通过急切加载来仅加载英文翻译(根本不需要任何连接):

Text.includes(:english_translations).all

检查一下:

first you should consider to use rails/activerecord conform relations. This means the foreign key in the text_translations table should be called text_id

Create your models and associations like this:

class Text < ActiveRecord::Base

  # all possible translations!
  has_many :text_translations

  scope :with_translation_for, lambda { |lang| {
    :select     => "texts.*, tt.translation",
    :joins      => "LEFT OUTER JOIN text_translations AS tt ON tt.text_id = texts.id AND tt.locale = #{ActiveRecord::Base.sanitize(lang)}"
  }}


  # return nil if translation hasn't been loaded, otherwise you get a nasty NoMethod exception
  def translation
    read_attribute(:translation)
  end

end

and

class TextTranslation < ActiveRecord::Base
  # every translation belongs to a text
  belongs_to :text

  # define a scope for the language
  scope :language, lambda { |lang| where(['locale = ?', lang]) }

end

How to use:

texts = Text.with_translation_for('en')
texts.each do |c_text|
    unless c_text.translation.nil?
        puts c_text.translation
    else
        puts "No translation available!"
    end
end

Now to the pro and cons, the way using LEFT OUTER join will load you all texts even if there isn't a translation for a text in the desired language. The con is that you won't get the "TextTranslation" model object.

Anotherway is to load only the text which have the desired translation. You can do it like:

texts = Text.includes(:text_translations).where(:text_translations => {:locale => 'en'})

now texts[i].text_translations will return an array with all TextTranslations model object for this text matching the locale 'en'. But texts without a translation in the locale "en" won't show up.

Edit

Connected to your comment:

The problem about using .join(:tablename) on a relation is that, it will result in an INNER JOIN so this is not an option. You have to explicitly declare the LEFT join. Another thing is that if you use something like Text.includes(:text_translations).where(['text_translations.locale = ?', 'en']) the condition will be applied to the SQL query as whole and not on the possible LEFT join itself. What you actually can do is to declare associations like

has_many :english_translations, :class_name => 'TextTranslation', :conditions => ['locale = ?', 'en']  

This way you can manage to load only english translations by eager loading (without any joins at all):

Text.includes(:english_translations).all

Checkt this out:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文