有没有办法使用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 +下执行此操作?
提前致谢。
答案 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
检查出来: