グループやルームの管理者が誰かわかるようにする

現在、討論するためのプラットフォームWebアプリを開発してます。そこで、ユーザーAが管理者となって討論ルームを作成し、そのルームに複数のユーザーが参加するわけですが、その仕組みを作る方法についてアウトプットします。方法は知っている限り２つあります。

１つ目はルーム作成者（＝オーナー）を管理するためのテーブルを他に作る方法。
２つ目はルームテーブル内にオーナーを管理するためのカラムを追加する方法。

今回は２つ目の方法について、特に難しかったアソシエーションを中心に解説します。難しかった原因は「ユーザーとルームは多対多のアソシエーションであるのにも関わらず、オーナー管理の部分だけ見るとユーザー：ルームは１：多の関係になってしまうことです（ルームには必ず１人だけ管理者が存在し、ユーザーは複数のルームの管理者になれる）」というものです。解決方法を要約すると、「ユーザーとルームの多対多アソシエーションを作成するのに加え、ルームからユーザーへblongs_toのアソシエーションを組む」となります。

１つ目の方法の概要やメリットについては最後におまけで少しだけ言及します。
# 前提
### 機能について
あるユーザーAがいます。そのAさんがルームを作成します。ルームを作成したAさんがそのルームの管理者となります。ルームには他のユーザーが参加します。また、ユーザーAは他にもルームを作ることができます。
⇨従って、ユーザーとルームは多対多の関係となります。
### 機能を作成するためにやること
やるべきことは大雑把に整理すると２つです。１つは中間テーブルを作り、ユーザーとルームの関係を管理すること。もう１つは管理者を管理するためにルームテーブルに管理者のカラムを追加すること。
![スクリーンショット 2022-07-05 7.39.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707093/85a92fd4-71df-a396-ae33-c64bf4a4a5e4.png)

# マイグレーションファイル
今回ユーザーはdeviseで作成しています。
```ruby:user_migrate_file
class DeviseCreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :nickname, null: false
      #(中略)
  end
  #(中略)
end
```
ルームに追加しているリファレンス型カラムのownerが管理者を登録するためのカラムとなります。
```ruby:room_migrate
class CreateRooms < ActiveRecord::Migration[6.1]
  def change
    create_table :rooms do |t|
      t.string :title, null: false
      t.references :owner
      t.datetime :deadline, null: false
      t.timestamps
    end
  end
end
```
マイグレート後、アソシエーションを組みます。

# アソシエーション
ユーザーモデル内ではルームテーブルと中間テーブルに対するアソシエーションを記述します。
```ruby:models/user
class User < ApplicationRecord
   #(中略)
  #association
  has_many :user_rooms,dependent: :destroy
  has_many :rooms ,through: :user_rooms
end
```
ここからが肝で、ルームモデルには多対多のためのアソシエーションに加えて、管理者カラムのためのアソシエーションを組みます。
```ruby:models/room
class Room < ApplicationRecord
  has_many :user_rooms,dependent: :destroy
  has_many :users, through: :user_rooms
  #↓で管理者を登録。
  belongs_to :owner, class_name: "User", foreign_key: :owner_id
end
```
# 解決方法の解説
```ruby:抜粋
belongs_to :owner, class_name: "User", foreign_key: :owner_id
```
ルームの管理者（以下：owner）は１人なので、roomからownerへbelongs_toの関係があることがわかります。ownerテーブルがあればいいのですが、ownerは結局はuserと同じなので、仮にownerテーブルを作ると、テーブルが増えて手間も増えます。だから、ここでは、userテーブルを利用するけれども、テーブルの名前をuserではなくownerと読み替えさせるようにします。読み替えさせる方法がclass_nameであり、上記を再度書き換えると下記になります。
```ruby:抜粋
belongs_to :自分で設定するテーブルの読み方, class_name: "元となるテーブル名", foreign_key: :owner_id
```
"foreign_key: :owner_id"は記述しなくてもテーブルの作成やオーナー管理は問題なく行えます。しかし、記述しない場合、ユーザーを削除しようとするときに、エラー原因になります。ownerカラムはマイグレーションファイルで設定した通り、リファレンス型のカラムなので、外部キーが必要になります。ただ、外部キー設定をマイグレーションファイル内に記述すると、外部キー先との関係が多対多になっているため、これもまたエラーの原因になります。ゆえに、アソシエーション部分で、ownerのカラムが外部キーになっていることを宣言します。※owner_idと記述しているのは、実はカラム名がowner_idになっているからです。マイグレーションファイルで"t.reference :カラム名"として記述しているので、テーブル作成時にはカラム名_idとしてカラムに登録されます。

自信満々に書いているように見えますが、間違っていたらコメントくださるとありがたいです。
# おまけ
記事冒頭で紹介した解決方法「ルーム作成者（＝オーナー）を管理するためのテーブルを他に作る方法」ですが、実は自分は最初この方法でやっていました。問題なく機能は実現できていたのですが、記述の多さやふと振り返った時に可読性が悪いなどがあり、思い切ってテーブル設計全体を変更することにしました。ちなみに解決方法のイメージとしては下図みたいな感じです。
![スクリーンショット 2022-07-05 8.24.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707093/99a19221-9569-7d86-df5b-d12a4b7f6bc0.png)
roomからユーザー名を表示するのにも苦労が多く、このテーブル設計には嫌な思い出しかないです。ただ、利用するメリットがある場合もあると思ってます。例えば、管理者を複数人選択できるようにする場合などです。roomテーブル内のみで、オーナーを管理する場合、ユーザーがオーナーの人数を自由に設定できるようにすると、ルームAは管理者が２人、ルームBは管理者が３人などということが起こり得ます。owner１、owner２、owner３・・・とカラムを用意すると、どこかのセルにはnullが入ってしまうことがあるかもしれません。nullが発生してしまうと、そもそも、nullが発生しないために、RDBというDB設計を使っているのに、その意義が薄まってしまうことになります。話がそれましたが、管理者を自由に複数人選択することができる機能を実装する場合、ownerテーブルを作成する必要が出てくるかもしれません。
# 参考サイト
https://teratail.com/questions/306622

https://railsguides.jp/association_basics.html#has-many%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91%E3%81%AE%E8%A9%B3%E7%B4%B0