商品を売ったり買ったりする場合にclass_nameを使ってみた

# はじめに
・商品を売ったり買ったりする場合のアソシエーションで悩みました。
・`class_name`オプションを使うことで解決できたので、備忘録として書きます。
・自分の状況に落とし込んでいるので、わかりにくかったらすみません。
・間違いなどあればご指摘いただけますと幸いです。
・ミニアプリを作る手順は[こちらの記事](https://qiita.com/fishmans0120/items/e1959abb6032549e75ae)を見てください。

# 前提条件
・ユーザーがいます（usersテーブル）
・商品があります（itemsテーブル）
・商品が出品できる状況です（データ送信できる）
・1人のユーザーは複数の商品を出品（購入）することができます
・1つの商品は1人のユーザーに所属しています
→ユーザーと商品は1対多の関係です
# class_nameって？
`関連名を変更するために、クラス名を明示的に表示するオプション`です。
たぶんどういう意味かあんまりわからないと思うので、具体例で説明します。

まず、以下のテーブルを見てください。
`user has_many :items`
`item belongs_to :user`
こんな関係性になってます。
・usersテーブル

| id | name | 
|-----------|------------|
|1|山田|
|2|田中|
|3|鈴木|

・itemsテーブル

| id | name | user_id　|
|-----------|------------|------------|
|1|イモ|1|
|2|サケ|1|
|3|フグ|2|

山田さんはイモとサケを出品しています。
田中さんはフグを出品しています。

今、山田さんが出品したitemを全て取得することはできます。
イモを出品した人を取得することもできます。

```terminal:ターミナルでコンソールを開いて１行ずつ入力してみてください
user = User.find(1)
user.items
```

でも、誰がイモを買うのか？
このままでは判断できません。

1つのitemに対して買う人が1人いる。
今はitemsテーブルの`id = 1`（イモ）を出品した人が山田ということしか取得できません。

買った人を取得するためには、出品した人と買った人を参照できるようにする必要があります。
つまり、`外部キー制約のついたカラムを置く必要があります`。

・itemsテーブル

| id | name | 外部キー1　| 外部キー2　|
|-----------|------------|------------|------------|
|1|イモ|1|3|
|2|サケ|1|3|
|3|フグ|2|1|

例えば、
`外部キー1`に`出品する人`、
`外部キー2`に`買う人`、
を持ってくればいいですね。

### 外部キー制約のカラムの認識は
「参照先のモデル名（小文字）」 + 「_id」
で認識されます。

わかりやすくするため、デフォルトの名前である`user_id`を使わないようにします。

・itemsテーブル

| id | name | seller_id　| buyer_id　|
|-----------|------------|------------|------------|
|1|イモ|1|3|
|2|サケ|1|3|
|3|フグ|2|1|

`integer型`で`seller_idカラム`と`buyer_idカラム`を追加しましょう。

### ただしこのままでは認識されません
`Userモデル`と`Itemモデル`のアソシエーションに`foreign_keyオプション`を使って、`外部キーであることを明示的に宣言する`必要があります。

### ではこうしましょう
```ruby:user.rb
has_many :items, foreign_key: 'seller_id'
has_many :items, foreign_key: 'buyer_id'
```

```ruby:user.rb

belongs_to :user, foreign_key: 'seller_id'
belongs_to :user, foreign_key: 'buyer_id'

```

いつもは`foreign_key: true`ですけど今回は違います。
こういう名前にしたい！という名前をオプションで宣言します。
これで外部キーとして認識されました。

### ですが、これではエラーになります
認識はされたものの、ユーザーに関連する`items`にアクセスする際、`関連名が同一になっている（items）`ため、`出品したものを取得したいのか、買ったものを取得したいのかがわからない`という状況になってしまっています。

→この状態でUserモデルのインスタンスに関連するitemsを取得しようとしてもエラーが出ます

### エラーを解決するためにclass_nameオプションを使います
ここで`class_nameオプション`が出てきます。

出品したものか、買ったものなのか、
あるいは
出品した人なのか、買った人なのか、
を判断するために`class_nameオプション`を使って、同一の関連名を変更します！！！
### ここ大事！！！
Rails 5.1からの変更点として、belongs_toを指定した時、自動的にrequired: trueオプションが追加されpresenseのバリデーションが走るようになったようです（https://github.com/rails/rails/issues/34454 )。
そのため、今回のケースでは`buyer_idのほうにoptional: trueをかけてpresenseのバリデーションを追加しないように指定する`ことが必要です。
そうしないと、ユーザー登録や商品購入の際にbuyerを入力してくださいと言われちゃいます。

```ruby:user.rb
# sold_itemsは出品された商品にアクセスする関連名、bought_itemsは買った商品にアクセスする関連名です
# class_name: '関連するモデルのクラス名'
has_many :sold_items, class_name: 'Item', foreign_key: 'seller_id'
has_many :bought_items, class_name: 'Item', foreign_key: 'buyer_id'
```

```ruby:item.rb

  # sellerは出品した人にアクセスする関連名、buyerは買った人にアクセスする関連名です
belongs_to :seller, class_name: 'User', foreign_key: 'seller_id'
belongs_to :buyer, class_name: 'User',foreign_key: 'buyer_id', optional: true
```
ということで、
usersテーブルの`id: 1`の山田が出品した商品と買った商品を取得することができるようになりました。

```terminal:ターミナルでコンソールを開いて１行ずつ入力してみてください
user = User.find(1)
User.sold_items
User.bought_items
```

itemsテーブルの`id: 1`のイモを出品した人と買った人を取得することができるようになりました。

```terminal:ターミナルでコンソールを開いて１行ずつ入力してみてください
item = Item.find(1)
Item.seller
Item.buyer
```

# 実際にミニアプリを作って試してみる時の注意点
・コントローラーへの記述でprivateメソッドの中にストロングパラメーターの記述をすると思いますが、出品者のidを取得する場合は`.merge(seller_id: current_user.id)`と記述すると良いかと思います。購入者の場合はseller_idをbuyer_idに変更すれば良いかと思います。

# まとめ
・売る人と買う人がいる場合はclass_nameオプションを使うと実装しやすいと思います。
・ミニアプリを作る手順は[こちらの記事](https://qiita.com/fishmans0120/items/e1959abb6032549e75ae)を見てください。

# 参考
【Rails】アソシエーションを図解形式で徹底的に理解しよう！
https://pikawaka.com/rails/association#class_name%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3
Ruby on Rails アソシエーションの応用 class_name 【一つのモデルを複数に分岐する】
https://qiita.com/mylevel/items/421cc1cd2eb5b39e20ad