rails7.1から単数形で定義されている関連付けに対して`where`で複数形で指定して絞り込みする方法ができなくなるので注意する

# 具体例
以下の様に`User`が中間テーブルを介して1つの`Role`を持つ定義をしている場合

```ruby
class Role < ApplicationRecord
end

class User < ApplicationRecord
  has_one :user_role
  has_one :role, through: :user_role
end

class UserRole < ApplicationRecord
  belongs_to :user
  belongs_to :role
end
```

上記のモデル定義に対して特定の`Role`を持っている`User`を絞り込む際に関連付けの定義名`role`を使ってデータ取得を行おうとすると以下の様にエラーが発生します。

```ruby
role = Role.first
User.joins(:role).where(role: role).to_sql

"SELECT `users`.* FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `user_roles`.`user_id` WHERE `role`.`id` = 64"

#=>#<ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'role.id' in 'where clause'> rescued during inspection
```

`where`で指定された`role`を元にクエリを生成し`role`テーブルが存在しないためエラーになっています。
この振る舞い自体は`rails7.0`も`rails7.1`も同様です。

## rails 7.0までの回避方法
`rails 7.0`では、`key`に`role`ではなく`roles`を指定する事で`WHERE roles.id = ?`が発行しデータ取得を行うことができました。

```ruby
role = Role.first
User.joins(:role).where(roles: role).to_sql

"SELECT `users`.* FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `user_roles`.`user_id` WHERE `roles`.`id` = 64"
```

ただし、この方法は意図した機能として提供されているわけではなくたまたま動いていた事象に近い様です。
本来`roles`は関連付けで定義していないが、`roles`の単数形である`role`が関連付けに定義されているため`where`メソッド内の処理で`roles`も関連付けの指定だと判断されていました。

## rails 7.1

> 本来`roles`は関連付けで定義していないが、`roles`の単数形である`role`が関連付けに定義されているため`where`メソッド内の処理で`roles`も関連付けの指定だと判断されていました。

`rails 7.1`の場合は上記のロジックが削除されたので、`rails7.0`で動作していた回避策は動作しなくなりました。`roles`を指定すると関連付けではなく`users`テーブルのカラムとして検索するクエリが発行されるため、`WHERE users.roles = ?`となりカラムが存在しないエラーが発生します。

```ruby
role = Role.first
User.joins(:role).where(roles: role).to_sql

"SELECT `users`.* FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `users` ON `users`.`id` = `user_roles`.`user_id` WHERE `users`.`roles` = 67"

#<ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'users.roles' in 'where clause'> rescued during inspection
```

# 対応
## 1. `where(role: { id: role.id })`とする

発行される`SQL`は`rails7.0`と`rails7.1`間で同様です。
意図したデータを取得することができます。

```ruby
# rails 7.0
role = Role.first
User.joins(:role).where(role: { id: role.id }).to_sql
#=> "SELECT `users`.* FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `roles` `role` ON `role`.`id` = `user_roles`.`role_id` WHERE `role`.`id` = 5"

# rails 7.1
role = Role.first
User.joins(:role).where(role: { id: role.id }).to_sql
#=> "SELECT `users`.* FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `roles` `role` ON `role`.`id` = `user_roles`.`role_id` WHERE `role`.`id` = 5"
```
## 2. `config`の設定値で互換性を保つ

`rails7.1`では互換性を保つための設定が用意されているので使用する事で`rails7.0`までの振る舞いを維持できます。

```ruby:config/application.rb
Rails.application.config.active_record.allow_deprecated_singular_associations_name = true
```

`Rails.application.config.active_record.allow_deprecated_singular_associations_name = true`にすると、エラーではなく以下の`DEPRECATION WARNING`の出力を行い`rails7.0`以前の振る舞いを維持します。

```ruby
role = Role.first
User.joins(:role).where(roles: role)

#=> <ActiveSupport::DeprecationException: DEPRECATION WARNING: Referring to a singular association (e.g. `user`) by its plural name (e.g. `users`) is deprecated.
#
# To convert this deprecation warning to an error and enable more performant behavior, set config.active_record.allow_deprecated_singular_associations_name = false.
```

# 変更されたPRについて

https://github.com/rails/rails/pull/45020

https://github.com/rails/rails/pull/45163

https://github.com/rails/rails/pull/45344


元々`where`で`hash`が入力された場合に関連付けの指定なのか判断する際に以下の2つをチェックしていました。

1. `key`が関連付けの定義に存在するか
2. `key`を`singularize`した値が関連付けの定義に存在するか

2の`singularize`処理のパフォーマンスが悪い事がわかったため、**2. `key`を`singularize`した値が関連付けの定義に存在するか**自体を実行しない様にする事で、`singularize`の呼び出しを無くしパフォーマンスを向上させた様です。尚、この対応で2倍程のパフォーマンスの向上が計測できたとのことです。