ActiveRecord :: Relation объединяет больше условий, чем просто внешний ключ - PullRequest
0 голосов
/ 05 марта 2011

Можно ли указать более одного условия для левого внешнего соединения с помощью 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 Ответ

1 голос
/ 05 марта 2011

сначала вы должны рассмотреть использование отношений соответствия 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 вернет массив со всеми объектами модели TextTranslations для этого текста, соответствующего локали 'en'. Но тексты без перевода в локали "en" не будут отображаться.

Редактировать

На ваш комментарий:

Проблема использования .join(:tablename) в отношении заключается в том, что это приведет к ВНУТРЕННЕМУ СОЕДИНЕНИЮ, поэтому это не вариант. Вы должны явно объявить левое соединение. Другое дело, что если вы используете что-то вроде Text.includes(:text_translations).where(['text_translations.locale = ?', 'en']), условие будет применено к SQL-запросу в целом, а не к возможному левому соединению. Что вы можете сделать, так это объявить ассоциации типа

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

Таким образом, вы можете загружать только переводы на английский язык с нетерпением (без каких-либо объединений):

Text.includes(:english_translations).all

Проверьте это:

...