Атрибут AREL включает в себя: find_by_sql

У меня есть довольно сложный sql-запрос, который, я уверен, я не могу выполнить с AREL (Rails 3.0.10)

Проверьте ссылку, но она имеет несколько объединений и предложение where exists, и я уверен, что это слишком сложно для AREL.

Моя проблема заключается в том, что до того, как этот запрос был настолько сложным, с AREL я мог бы использовать includes, чтобы добавить другие модели, которые мне нужны, чтобы избежать n + 1 проблем. Теперь, когда я использую find_by_sql, includes не работает. Я все еще хочу иметь возможность извлекать эти записи и присоединять их к экземплярам модели, как это делает includes, но я не совсем уверен, как этого добиться.

Может ли кто-нибудь указать мне в правильном направлении?

Я еще не пробовал присоединиться к ним в том же запросе. Я просто не уверен, как они будут сопоставлены с объектами (т.е. Если ActiveRecord правильно сопоставляет их с соответствующим классом)

Я знаю, что при использовании includes ActiveRecord фактически выполняет второй запрос, затем каким-то образом привязывает эти строки к соответствующим экземплярам из исходного запроса. Может ли кто-нибудь научить меня, как я могу это сделать? Или мне нужно присоединиться к одному и тому же запросу?

Ответ 1

Предположим, что SQL действительно не может быть сведена к Arel. Не все может, и нам действительно очень хочется сохранить наш пользовательский find_by_sql, но мы также хотим использовать его.

Затем preload_associations - ваш друг: (Обновлено для Rails 3.1)

class Person
  def self.custom_query
    friends_and_family = find_by_sql("SELECT * FROM people")
# Rails 3.0 and lower use this: 
#        preload_associations(friends_and_family, [:car, :kids])
# Rails 3.1 and higher use this: 
    ActiveRecord::Associations::Preloader.new(friends_and_family, [:car, :kids]).run
    friends_and_family
  end
end

Обратите внимание, что метод 3.1 намного лучше, b/c вы можете применять загрузку в любое время. Таким образом, вы можете получить объекты в своем контроллере, а затем перед рендерингом вы можете проверить формат и загружать больше ассоциаций. То, что происходит для меня - html не нуждается в активной загрузке, но .json делает.

Это поможет?

Ответ 3

@pedrorolo благодарит за хэдз-ап за этот запрос not exists isl, помог мне добиться того, что мне нужно. Здесь окончательное решение (они являются окончательными .exists в запросе GroupChallenge:

class GroupChallenge < ActiveRecord::Base
  belongs_to :group
  belongs_to :challenge  

  def self.challenges_for_contact(contact_id, group_id=nil)
    group_challenges = GroupChallenge.arel_table
    group_contacts = GroupContact.arel_table
    challenges = Challenge.arel_table
    groups = Group.arel_table

    query = group_challenges.project(1).
              join(group_contacts).on(group_contacts[:group_id].eq(group_challenges[:group_id])).
              where(group_challenges[:challenge_id].eq(challenges[:id])).
              where(group_challenges[:restrict_participants].eq(true)).
              where(group_contacts[:contact_id].eq(contact_id))

    query = query.join(groups).on(groups[:id].eq(group_challenges[:group_id])).where(groups[:id].eq(group_id)) if group_id

    query
  end
end

class Challenge < ActiveRecord::Base
  def self.open_for_participant(contact_id, group_id = nil)
    open.
      joins("LEFT OUTER JOIN challenge_participants as cp ON challenges.id = cp.challenge_id AND cp.contact_id = #{contact_id.to_i}").
        where(['cp.accepted != ? or cp.accepted IS NULL', false]).
      where(GroupChallenge.challenges_for_contact(contact_id, group_id).exists.or(table[:open_to_all].eq(true)))
  end
end