テックキャンプ現役受講生がWebアプリを開発するのDay9です。アプリ完成の目標をとりあえず6月末までとしていますので、残り1週間です。終わる気がしません。しかし、嘆いても仕方ないので、明日以降ももやるべきことをコツコツやっていくのみです。さて、今日は他者が作ったルームに、後から他のユーザーが参加する方法について「DB設計、アソシエーション、コントローラーへの記述」の3部分に分けてアウトプットします。
※当方はエンジニア初心者ですので、もっとスマートな方法があるかもしれません。その点ご容赦ください。
今回のシチュエーションの設定
登場するテーブルは4つです。Userとroom、User_room、Ownerの4つです。
Userはroomを作成することができ、Roomの中ではチャット等の会話ができる空間を想定しています。
roomを一番はじめに作ったユーザーはOwner(オーナー)として登録し、Ownerにはroomの名前変更等の権限を与えられるものとします。
User_roomはUserとroomの中間テーブルです。下図を見ていただくとイメージが湧きやすいと思います。
DB設計
マイグレーションファイルの記述
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
#以下省略
class CreateRooms < ActiveRecord::Migration[6.1]
def change
create_table :rooms do |t|
t.string :name, null: false
t.timestamps
end
end
end
class CreateUserRooms < ActiveRecord::Migration[6.1]
def change
create_table :user_rooms do |t|
t.references :user, foreign_key: true
t.references :room, foreign_key: true
t.timestamps
end
add_index :user_rooms, [:user_id, :room_id], unique: true #←この記述でルームとユーザーの組み合わせが一意であることを設定しています。
end
end
class CreateOwners < ActiveRecord::Migration[6.1]
def change
create_table :owners do |t|
t.string :name, null: false
t.references :owner, foreign_key: { to_table: :users }#ユーザーの外部キーですが、別の名前をつけてわかりやすくしています。
t.references :room, foreign_key: true
t.timestamps
end
end
end
Userの外部キーはカラムに"User_id"と登録されますが、この名前を変更することができます。OwnerテーブルではUserの外部キーに、任意の名前(Owner)をつけてわかりやすくしています。
アソシエーション
アソシエーションに関係のあるところのみ抽出して記述しています。
class User < ApplicationRecord
has_many :user_rooms
has_many :rooms ,through: :user_rooms
has_many :owners
end
class Room < ApplicationRecord
has_many :user_rooms
has_many :users, through: :user_rooms
has_one :owner
end
class UserRoom < ApplicationRecord
belongs_to :user
belongs_to :room
end
class Owner < ApplicationRecord
belongs_to :owner, class_name: "User"
belongs_to :room
end
Ownerは中間テーブルではないので、アソシエーションにご注意を。
コントローラーの記述
リクエストからは取得する情報は以下の通り
・Roomの名前
・ログインしているユーザーの情報(これはdevisを導入していると、current_userで簡単に取得可能)
ヘルパーメソッドのform_withでparams送信します
<%= form_with(model: @room, local: true) do |f| %>
<%= f.text_field :name, placeholder: "ディスカッションのテーマを入力してください",class:"input-name"%>
<%= f.submit "作成", class:"input-name submit-bottom"%>
<% end %>
コントローラー内部で三段階に分けて処理をします。
①roomのテーブルに保存
②中間テーブルに情報を保存
③保存したroomの情報をもとにOwnerテーブルにデータを保存
class RoomsController < ApplicationController
def create
@room = Room.new(room_params)
if @room.save
Owner.create(name:current_user.nickname, owner_id: current_user.id,room_id:@room.id)
redirect_to room_path(@room)
else
render 'new'
end
end
private
def room_params
params.require(:room).permit(:name).merge(user_ids: [current_user.id])
end
end
中間テーブルの保存は「user_ids:〜」という部分で可能になります。↓を参照ください。
まとめ
・”他ユーザーが作成したルームに後から参加する”という機能を実装するには中間テーブルを利用すレバOK。
・”誰がルームを作成したかわかるようにする”には新しく作成したのかわかるように、そのデータを管理するテーブルを新たに作成する。
技術的に難しいところはなく、発想次第で解ける問題でした。
反省点
ルーム作成者を明確にするには、そのデータを管理するためのテーブルを新たに作成する必要があることがわかりました。今後、機能を追加する際は、現状のテーブルだけでうまく対処しようとせず、その機能を管理するためのテーブルを作ることも視野に入れなければなりません。また、1つの送信で、2つのテーブルのデータを書き換えているので、今後form_objectを導入するべきだと思いました。
解決したかったけど、できなかったこと。
Ownerテーブルにnameというカラムを導入していますが、これはUserテーブルにあるのと同じものです。データが冗長になっていると思いました。これを解決するために、最初の方はアソシエーションでどうにはOwnerの名前をUserのnameカラムから引っ張ってこようと思っていましたが、アソシエーションによる呼び出しの仕方がわからず、どうにもできませんでした。今説明したことを抽象化すると
「親:子のアソシエーションが1対多の時、アソシエーションを利用し子から親を1つ特定する」
ということだと思います。今後も勉強して、いつか理解したいです。
参考サイト