Подтвердить что ты не робот

Rails 4 - как предоставить имена псевдонимов include() и join() в активном запросе записей

Как я могу присвоить псевдоним, например. includes()? Приводится следующее:

  • Пользователь: активная модель записи
  • Студент: активная модель записи, наследует пользователя (STI)
  • Учитель: активная модель записи, наследует пользователя (STI)
  • Проект: модель активной записи

Вот несколько примеров:

FIRST CASE (больше ассоциаций STI)

Project.all.includes(:students, :teachers).order('teachers_projects.name ASC') # order on teachers
Project.all.includes(:students, :teachers).order('users.name ASC') # order on students

Rails автоматически использует псевдоним teachers_projects для :teachers в SQL. Как я могу перезаписать это, чтобы я мог использовать псевдоним teachers вместо teachers_projects в SQL? :students получает псевдоним users.

Эти примеры не выполняются:

Project.all.includes(:students, :teachers).order('teachers.name ASC')
Project.all.includes(:students, :teachers).order('students.name ASC')
Project.all.includes(:students, :teachers).order('students_projects.name ASC')

SECOND CASE (одна ассоциация STI)

Если в методе includes() используется только :students (без :teachers), Rails использует псевдоним имени базового класса STI users (без _projects) для :students:

Project.all.includes(:students).order('users.name ASC') # order on students

Эти примеры не выполняются:

Project.all.includes(:students).order('students.name ASC')
Project.all.includes(:students).order('students_projects.name ASC')

Вопрос

Возможно, что-то вроде:

Project.all.includes(:students).alias(students: :my_alias)

RAILS ALIAS TRACKER

https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/associations/alias_tracker.rb#L59

ТЕСТИРОВАНИЕ APP

https://gist.github.com/phlegx/add77d24ebc57f211e8b

https://github.com/phlegx/rails_query_alias_names

4b9b3361

Ответ 1

У Arel действительно есть метод псевдонима.

student_alias = Student.arel_table.alias(:my_student_table_alias)

caveat: это потребует от вас использования еще большего количества рук Arel и сделайте соединение вручную. И соединения в Arel могут немного усложниться, если вы не привыкли к ним.

student_alias_join = student_alias.create_on(
  Project.arel_table[:primary_key].eq(student_alias[:project_id])
)

Project.joins(
  Project.arel_table.create_join(
    student_alias, student_alias_join, Arel::Nodes::OuterJoin
  )
).order(...)

Что-то вроде этого должно это сделать. Разумеется, для этого метода класса с :my_student_table_alias этот параметр сделает его более аккуратным и многоразовым, поскольку это будет выглядеть немного беспорядочным в контроллере.

Ответ 2

Я собираюсь сделать еще один подход к этой проблеме: вместо того, чтобы пытаться управлять именами псевдонимов в ваших запросах с помощью метода .alias, я разрешу Rails/Arel обрабатывать это и просто посмотреть правильное имя таблицы ( с псевдонимом или без него), когда это необходимо в пределах области действия.

Добавьте этот вспомогательный метод к своей модели, который вы сможете вызвать из области, чтобы узнать, используется ли область в JOIN, которая имеет псевдонимы таблицы (несколько объединений в одной таблице) или, с другой стороны, область не имеет псевдонимов для имени таблицы.

def self.current_table_name
  current_table = current_scope.arel.source.left

  case current_table
  when Arel::Table
    current_table.name
  when Arel::Nodes::TableAlias
    current_table.right
  else
    fail
  end
end

В качестве базового объекта для поиска таблицы isl используется current_scope. Я вызываю source на этом объекте, чтобы получить Arel::SelectManager, который, в свою очередь, даст вам текущую таблицу на #left, Есть два варианта: либо у вас есть Arel::Table (нет псевдонима, имя таблицы на #name), либо у вас есть Arel::Nodes::TableAlias с псевдонимом на #right.

Теперь вам нужно использовать это только в своих операторах order (untested):

Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")

Похожие вопросы: