ActiveRecord :: Relation连接的条件多于外键

时间:2011-03-05 03:25:49

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

有没有办法使用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 +下执行此操作?

提前致谢。

1 个答案:

答案 0 :(得分:1)

首先你应该考虑使用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

检查出来: