web-dev-qa-db-ja.com

Rails 4-アクティブレコードクエリでincludes()とjoins()にエイリアス名を付ける方法

たとえば、エイリアス名を付けるにはどうすればよいですか。 includes()?以下が与えられます:

  • ユーザー:アクティブレコードモデル
  • 学生:アクティブレコードモデル、ユーザー(STI)から継承
  • 教師:アクティブレコードモデル、ユーザー(STI)から継承
  • プロジェクト:アクティブレコードモデル

ここにいくつかの例があります:

最初のケース(より多くの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は、SQLの_teachers_projects_にエイリアス名_:teachers_を自動的に使用します。 SQLで_teachers_projects_の代わりにエイリアス名teachersを使用できるように、これを上書きするにはどうすればよいですか? _: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')
_

2番目のケース(1つの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

テストアプリ

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

https://github.com/phlegx/Rails_query_alias_names

16
phlegx

Arelには実際にはエイリアスメソッドがあります。

student_alias = Student.arel_table.alias(:my_student_table_alias)

警告:これには、さらに手作りのアレルを使用し、手動で結合する必要があります。また、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をパラメーターとして使用してClassメソッドに入れると、コントローラーで少し乱雑に見えるため、より整理されて再利用可能になります。

3
neongrau

この問題に対して別のアプローチを取ります。.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

これは、arelテーブルを検索するためのベースオブジェクトとして current_scope を使用します。そのオブジェクトでsourceを呼び出して、 Arel::SelectManager を取得します。これにより、#leftの現在のテーブルが得られます。そこには2つのオプションがあります。Arel::Table(エイリアスなし、テーブル名は#nameにあります)があるか、Arel::Nodes::TableAliasにエイリアスが#rightにあるかのいずれかです。

これで、orderステートメント(未テスト)でのみ使用する必要があります。

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")

関連する質問:

1
dgilperez