12
1

More than 1 year has passed since last update.

ActiveRecordのjoinsメソッドはシンボルの引数と文字列の引数で挙動が異なる

Last updated at Posted at 2022-12-17

先日微妙にハマってしまったRailsの小ネタです。

ActiveRecordは次のようにjoinsメソッドを使って別のモデル(テーブル)をJOINすることができます。

Task.joins(:project)

また、includesメソッドなどはシンボルを渡しても文字列を渡しても同じ挙動になります。

# includesメソッドはシンボルでも文字列でも挙動は同じ
Task.includes(:project)
Task.includes('project')

これと同じようにjoinsも同じようにシンボルでも文字列でも渡せるよね?と思ったんですが、文字列だとエラーが出ました。

Task.joins('project')
#  Task Load (0.2ms)  SELECT "tasks".* FROM "tasks" project
# .../sqlite3/database.rb:152:in `initialize': SQLite3::SQLException: no such table: tasks (ActiveRecord::StatementInvalid)
# .../sqlite3/database.rb:152:in `initialize': no such table: tasks (SQLite3::SQLException)

なぜエラーが出たのか?

joinsメソッドにシンボルを渡すとそれは関連名として扱われます。
ですが、文字列を渡すと独自のJOIN用SQLとして扱われます

# 引用: https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-joins 
User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
# SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id

そのため、文字列の"project"を渡すと予期せず無効なSQLが発行され、エラーが起きるのです。

# joinsにシンボルを渡した場合(関連名として扱われる)
puts Task.joins(:project).to_sql
# SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."id" = "tasks"."project_id"

# joinsに文字列を渡した場合(tasksテーブルにprojectという別名が予期せず付いてしまう)
puts Task.joins('project').to_sql
# SELECT "tasks".* FROM "tasks" project

うっかりミスと言えばうっかりミスなのですが、「シンボルを渡しても文字列を渡しても、内部的に型が統一されるのでどっちでもOK」というケースはRailsを使っているとよくあることなので、てっきりjoinsメソッドもそんなふうに動いてくれるもんだと勘違いしていました。

ふつうにjoinsメソッドを使うときはシンボルで関連名を指定すると思いますが、プログラム上で動的にJOINするテーブルを指定する場合に、予期せず文字列で関連名を渡したりすることがあるかもしれません。みなさんも気を付けてください。

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1