同じテーブル内に複数の外部キーを設定する方法！！

##はじめに
某プログラミングスクールで、担当した実装を復習していきたいと思います。
今回は、出品・取引中・売却済みのこの3つをクリックした際に、それぞれにあった商品を
表示させる実装を行いました。

これを実装するにあたって、1つのテーブル内に複数の外部キーを設定する必要があり、
ここで詰まったため、記録として残していきます。

ちなみにこんな感じの実装をしていきます。
![c0b6f5dcc3487ef71b3605eb6e99ffe0.gif](https://i.gyazo.com/c0b6f5dcc3487ef71b3605eb6e99ffe0.gif)

##工程
今回は、工程を以下に分けて説明をしていきます。
1.実装の大まかな説明とマイグレーションファイルの作成
2.モデルの作成
3.コントローラーの作成
4.hamlでの条件分岐設定
の順で行っていきます。
少し、長いですががんばっていきましょう。

##解説
1.実装の大まかな説明とマイグレーションファイルの作成

はじめに出品中・取引中・売却済みを区別するために、
productというテーブル内に、seller_id・auction_id・buyer_idという
userと紐づく外部キーを3つ設定しました。


そして、出品中の際には、productのレコードの中から
seller_id(出品者)にだけ値が入っているレコードをDBから引っ張って来ています。

取引中の場合は、seller_id(出品者)とauction_id(取引者)がいる
productのレコードをDBから引っ張ってきています。

売却済みの場合は、seller_id(出品者)とbuyer_id(買取者)がいる
productのレコードをDBから引っ張って来ることで、
それぞれを区別してDBから取得してきています。


マイグレーションファイルはこんな感じです。
*今回の実装であれば、user側はテーブルを作成しidがあればOKだと思います。

```rb:products.rb(マイグレーションファイル)
class CreateProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :products do |t|
      t.string :name, null: false
      t.references :seller, foreign_key: {to_table: :users} 
      t.references :buyer, foreign_key: {to_table: :users}
      t.references :auction, foreign_key: {to_table: :users}
      t.timestamps
    end
  end
end
```
「詰まったポイント その1」
(1)foreign_key: {to_table: :users}
通常であれば、

```rb:
t.references :user, foreign_key: true
```
foreign_key: trueのみで外部キーを設定できるのですが、
今回のように、同じテーブル内に複数の外部キーを設定する場合、
foreign_key: trueで定義してしまうと、
カラム名がテーブル名_idになってしまうため、
複数カラムを設定したいときにうまくいかないことがありました。

そのため、{to_table: :テーブル名}で今回使用するテーブルを直接指定する必要があるようです。

「参考記事」
![Railsで同じモデルを参照する外部キーを2つ以上もつ方法](https://blog.yayawatanabe.net/entry/2018/12/17/222107)

2.モデルの作成

```product.rb
class Product < ApplicationRecord
  belongs_to :seller, class_name: "User", optional: true,foreign_key: "seller_id"
  belongs_to :buyer, class_name: "User", optional: true,foreign_key: "buyer_id"
  belongs_to :auction, class_name: "User", optional: true,foreign_key: "auction_id"
end
```

各、外部キーをuserとアソシエーションを組んでいます。

```user.rb
class User < ApplicationRecord
  has_many :saling_items, -> { where("seller_id is not NULL && buyer_id is NULL") }, class_name: "Product"
  has_many :sold_items, -> { where("seller_id is not NULL && buyer_id is not NULL && auction_id is NULL") }, class_name: "Product"
  has_many :auction_items, -> { where("seller_id is not NULL && auction_id is not NULL && buyer_id is NULL") }, class_name: "Product"
end
```

次に、user.rbに焦点を当てて説明をしていきます。

```user.rb
has_many :saling_items, -> { where("saler_id is not NULL && buyer_id is NULL && auction_id is NULL") }, class_name: "Product"
```
この1文は、出品中のアイテムをproductのレコードから取得するための記述となっています。
今回でいう、出品中の商品とは言い換えると、

「seller_id(出品者)はいるが、まだ、buyer_id(買取者)またはauction_id(取引者)はいないproductのレコード」

を取得すればいいという形となるため、
上記のwhereの記述で制限することで、:saling_itemsカラムには
出品中の商品のみが取得できるという感じです。



```user.rb
 has_many :auction_items, -> { where("seller_id is not NULL && auction_id is not NULL && buyer_id is NULL") }, class_name: "Product"
```

次に、取引中の商品の記述になります。
取引中は言い換えると

「seller_id(出品者)とauction_id(取引者)のユーザーが存在し、buyer_id(買取者)はまだ存在していないproductレコード」

という形となるため、
上記のwhereでの制限となっています。

```user.rb
has_many :sold_items, -> { where("seller_id is not NULL && buyer_id is not NULL && auction_id is NULL") }, class_name: "Product"
```

最後に、売却済みの商品の記述です。
売却済みは言い換えると

「seller_id(出品者)とbuyer_id(買取者)は存在するが、auction_id(取引者)は存在していないproductレコード」

ということになるため、
上記のwhereでの制限となっています。


これで、とりあえずはproductとuser間のアソシエーションは終了です。

「参考記事」
![【Rails】テーブル間の条件付きアソシエーションの設定【メルカリコピー作成記】](https://qiita.com/YosukeItabashi/items/2ed5cad13263410f0fbc)


3.コントローラーの作成
1.2の記述で、マイグレーションファイルとアソシエーションを組んだため、
コントローラーでその取得したデーターを取り出す記述を行っていきます。

```rb:products.controller.rb(必要な箇所のみ記載)
class ProductsController < ApplicationController
  before_action :set_current_user_products,only:[:p_transaction,:p_exhibiting,:p_soldout]
  before_action :set_user,only:[:p_transaction,:p_exhibiting,:p_soldout]


  def p_exhibiting #出品中のアクション

  end

  def p_transaction  #取引中のアクション
 
  end

  def p_soldout    #売却済みのアクション

  end

  private

  def set_current_user_products
    if user_signed_in? 
      @products = current_user.products.includes(:seller,:buyer,:auction,:product_images)
    else
      redirect_to new_user_session_path
    end
  end

  def set_user
    @user = User.find(current_user.id)
  end
end
```

*product.conrollerで行っていますが、productとuserでネストをしている場合は、
user.controllerへ上記の記載をしても大丈夫だと思います。
*current_userを使用しているため、ログインしていない場合idがないため、
エラーが出てしまうことがあります。
その際は、DBへの直打ち等でユーザーを存在させる必要があると思います。(ここはあまり自信がないので、この方法でエラーをはいてしまったら、すみません。)

【解説】

```rb:
@user = User.find(current_user.id)
```

この1行によって、まずはログインしているユーザーのレコードを
取得している形となっています。

```rb:
    if user_signed_in? 
      @products = current_user.products.includes(:seller,:buyer,:auction,:product_images)
    else
      redirect_to new_user_session_path
    end
```

この記述によって、ログインしているユーザーが所持しているproductレコードのみを取得していきます。



「詰まったポイント　その２」
(1)上記で、指定のuserやprodutのレコードの取得はできた。
だが、そもそもproductテーブルに複数のカラムを指定したが、どうやって・どのタイミングで狙ったidへいれるのかがわかりませんでした。

「解決策」

```products.controller.rb
  def new
    @product = Product.new
    @product.product_images.new
  end

  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to root_path
    else
      redirect_to new_product_path,data: { turbolinks: false }
    end
  end

  private
  def product_params
    params.require(:product).permit(:name product_images_attributes: [:image, :_destroy]).merge(seller_id: current_user.id) #productやご自身のカラムに合わせて変更してください。
  end
```

まず、seller_idとは、出品者がもつidなため、出品する段階のnew・createの段階で、
そのユーザーが持っているidをseller_idへいれることによって解決しました。

```rb:
@product.update(buyer_id: current_user.id)
```

また、buyer_idに関しては、上記の一行を購入する画面でいれることによって実装しました。


4.hamlでの条件分岐設定
最後に、コントローラーで取得してきた値を繰り返し処理する記述を加えていきます。

![c0b6f5dcc3487ef71b3605eb6e99ffe0.gif](https://i.gyazo.com/c0b6f5dcc3487ef71b3605eb6e99ffe0.gif)

上記の画像のように、productに指定したレコードがある場合と、
ない場合で表示の仕方を変更する必要があるため、以下でif文による条件分岐を行っていきます。

```haml:p_exhibiting.html.haml(一部のみ表示しています。)
- if @user.saling_items.present?
  - @user.saling_items.each do |product|
    = link_to product_path(product),data: { turbolinks: false },class:"item_content" do
      .item_content__image
        = image_tag product.product_images[0].image.to_s,size:"58x48"
      .item_content__right
        .item_content__right--name
          =product.name
        .item_content__right__good
          .item_content__right__good--goods
            = icon("far","heart")
            0
          .item_content__right__good--comment
            = icon("far","comment-alt")
            0
          .item_content__right__good--exhibition
            出品中
      = icon('fas', 'angle-right', class: 'item_content__icon')
- else
  .pmain__bottom
    = image_tag "", class: "pmain__bottom--img", size: "100x100"
    .pmain__bottom--text
      出品中の商品がありません
```

*今回は、長いため出品中のみの記載としています。

特に重要な部分を記載していきます。

```haml
- if @user.saling_items.present?
```

この一行で、userのsaling_itemsがある場合は以下に記述した

```haml
- @user.saling_items.each do |product|
```

のsaling_itemsを繰り返すようにしています。

以上です。

##最後に
長い行を読んでいただきありがとうございました。
所々、切り抜いて記事を書かせて頂いているため、間違っている箇所があった際には、
私の記述でエラーを起こしてしまい申し訳ありません。
また、間違っている箇所がありましたら、コメントをいただけると幸いです。
ご視聴ありがとうございました。