はじめに
railsでグループチャット機能の実装にはあらゆる手段がありますが、特に数ある機能の中でも難易度が非常に高く、実装したかったけど諦めてしまった。。という方を見かけましたので、railsの基礎とちょっとした応用で出来るグループチャット機能を実装してみました。
なお本機能実装には、usersコントローラー、Userモデルを作成していることが前提となります。また、現在メインで使用しているコントローラー/モデルとは別にこの記事通り作成を進めてください。
必要なモデルとテーブルを設計する
既にあるuserモデルに加え下記のモデルを作成していきます
Groupモデル
・グループ名や説明を管理する
・保存するカラム
→name(string):グループ名
Messageモデル
・チャットメッセージを管理
・保存するカラム
→content(text):メッセージの内容
→user_id(integer):投稿者(ユーザー)を関連付ける
→group_id(integer):メッセージが所属するグループを関連付ける
GroupUserモデル
・ユーザーとグループの多対多の関係を管理
・保存するカラム
→user_id(integer):ユーザー
→group_id(integer):グループ
モデルとアソシエーション
モデルの作成とマイグレーション
rails g model Group name:string
rails g model Message content:text user:references group:references
rails g model GroupUser user:references group:references
rails db:migrate
モデルのアソシエーションを設定
Userモデル
has_many :group_users
has_many :groups, through: :group_users
has_many :messages
Groupモデル
has_many :group_users
has_many :users, through: :group_users
has_many :messages
validates :name, presence: true
Messageモデル
belongs_to :user
belongs_to :group
validates :content, presence: true
GroupUserモデル
belongs_to :user
belongs_to :group
コントローラーの作成と記述
コントローラーの作成
rails g controller Groups
rails g controller Messages
コントローラーの記述
※アクションについて
GroupsController
・index:グループチャット一覧を表示
・new/create:新規グループ作成
・show:グループ内のメッセージを表示
MessageController
・create:メッセージを送信
class GroupsController < ApplicationController
before_action :authenticate_user!
def index
@groups = current_user.groups
end
def show
@group = Group.find(params[:id])
@messages = @group.messages.includes(:user)
@message = Message.new
end
def new
@group = Group.new
end
def create
@group = Group.new(group_params)
if @group.save
@group.users << current_user # 作成者をグループに追加
redirect_to @group, notice: 'グループが作成されました'
else
render :new, status: :unprocessable_entity
end
end
private
def group_params
params.require(:group).permit(:name, user_ids: [])
end
end
class MessagesController < ApplicationController
before_action :authenticate_user!
before_action :set_group
def create
@message = @group.messages.new(message_params)
@message.user = current_user
if @message.save
# ActionCableブロードキャスト予定
redirect_to @group, notice: 'メッセージが送信されました'
else
@messages = @group.messages.includes(:user)
render 'groups/show', status: :unprocessable_entity
end
end
private
def set_group
@group = Group.find(params[:group_id])
end
def message_params
params.require(:message).permit(:content)
end
end
ルーティングの設定
#省略
resources :groups do
resources :messages, only: [:create]
end
#省略
ビューの作成
index(グループ一覧を表示するページ)の作成
<h1>グループチャット</h1>
<%= link_to 'グループ作成', new_group_path, class: 'btn btn-primary' %>
<ul>
<% @groups.each do |group| %>
<li><%= link_to group.name, group_path(group) %></li>
<% end %>
</ul>
show(メッセージを作成するページ)の作成
<h1><%= @group.name %></h1>
<div id="messages">
<% @messages.each do |message| %>
<p>
<strong><%= message.user.email %>:</strong>
<%= message.content %>
</p>
<% end %>
</div>
<%= form_with(model: [@group, @message], local: true) do |f| %>
<div>
<%= f.text_area :content, rows: 3, placeholder: 'メッセージを入力してください' %>
</div>
<div>
<%= f.submit '送信', class: 'btn btn-success' %>
</div>
<% end %>
new(グループを新規作成するページ)の作成
<h1>グループ作成</h1>
<%= form_with(model: @group, local: true) do |f| %>
<div>
<%= f.label :name, 'グループ名' %>
<%= f.text_field :name %>
</div>
<div>
<%= f.submit '作成', class: 'btn btn-primary' %>
</div>
<% end %>
お疲れ様です!今の段階でこの機能のほとんどが終了です!ここまで完了出来たら試しに、グループチャットを新規作成し、メッセージを投げてみましょう!上手く表示されれば続きを、うまくできなければ振り返ってみましょう!
参加・退出機能の追加
先ほどメッセージを送信してみたかと思いますが、チャットの参加/退出が現状できなくなっていますのでこちらを実装してより完璧にしていきましょう。
GroupUsersControllerを作成
新しいコントローラーを作成して、参加と退出を管理する
rails generate controller GroupUsers
GroupUsersControllerの実装
class GroupUsersController < ApplicationController
before_action :authenticate_user!
before_action :set_group
# グループに参加
def create
@group.users << current_user unless @group.users.include?(current_user)
redirect_to @group, notice: 'グループに参加しました。'
end
# グループから退出
def destroy
@group.users.delete(current_user)
redirect_to groups_path, notice: 'グループから退出しました。'
end
private
def set_group
@group = Group.find(params[:group_id])
end
end
ルーティングの設定
resources :groups do
resources :messages, only: [:create]
resource :group_users, only: [:create, :destroy] # 参加と退出のルートを追記
end
Viewの記述
groups/index.html.erbのグループそれぞれに参加/退出ボタンを追加する
<h1>グループチャット</h1>
<%= link_to 'グループ作成', new_group_path, class: 'btn btn-primary' %>
<ul>
<% Group.all.each do |group| %>
<li>
<%= link_to group.name, group_path(group) %>
<% if current_user.groups.include?(group) %>
<%= button_to '退出', group_group_users_path(group), method: :delete, class: 'btn btn-danger btn-sm' %>
<% else %>
<%= button_to '参加', group_group_users_path(group), class: 'btn btn-success btn-sm' %>
<% end %>
</li>
<% end %>
</ul>
groups/show.html.erbに参加者一覧を追加する
<h1><%= @group.name %></h1>
<p>現在の参加者:</p>
<ul>
<% @group.users.each do |user| %>
<li><%= user.email %></li>
<% end %>
</ul>
<div id="messages">
<% @messages.each do |message| %>
<p>
<strong><%= message.user.email %>:</strong>
<%= message.content %>
</p>
<% end %>
</div>
<%= form_with(model: [@group, @message], local: true) do |f| %>
<div>
<%= f.text_area :content, rows: 3, placeholder: 'メッセージを入力してください' %>
</div>
<div>
<%= f.submit '送信', class: 'btn btn-success' %>
</div>
<% end %>
お疲れ様でした!これにて終了です。動かして確認してみましょう。下記は追加で実装したい人向けになっていますので、より高い完成度を目指したい方はチャレンジしてみてください!
追加機能
メッセージ送信時に自動でグループに参加する
MessagesController を修正
messages_controller.rbでメッセージ作成時に自動でグループに参加させる処理を追加する
class MessagesController < ApplicationController
before_action :authenticate_user!
before_action :set_group
def create
# もし現在のユーザーがグループに参加していなければ参加させる
@group.users << current_user unless @group.users.include?(current_user)
@message = @group.messages.new(message_params)
@message.user = current_user
if @message.save
# ActionCableなどリアルタイム更新処理を後で追加可能
redirect_to @group, notice: 'メッセージが送信されました'
else
@messages = @group.messages.includes(:user)
render 'groups/show', status: :unprocessable_entity
end
end
private
def set_group
@group = Group.find(params[:group_id])
end
def message_params
params.require(:message).permit(:content)
end
end
これにより、ユーザーがメッセージを送信する際、自動的にそのグループに参加します。
グループに参加していないとメッセージが送信できない仕様にする
MessagesControllerに条件を追加
参加していないユーザーがメッセージを送信しようとした場合にリダイレクトするように変更
class MessagesController < ApplicationController
before_action :authenticate_user!
before_action :set_group
before_action :ensure_group_membership
def create
@message = @group.messages.new(message_params)
@message.user = current_user
if @message.save
redirect_to @group, notice: 'メッセージが送信されました'
else
@messages = @group.messages.includes(:user)
render 'groups/show', status: :unprocessable_entity
end
end
private
def set_group
@group = Group.find(params[:group_id])
end
def ensure_group_membership
unless @group.users.include?(current_user)
redirect_to @group, alert: 'グループに参加してください。'
end
end
def message_params
params.require(:message).permit(:content)
end
end
Viewで「参加」ボタンを表示する
参加していないユーザーに対して「参加ボタン」を表示するようにする
groups/show.html.erbのフォームの前に、下記を追加
<% unless @group.users.include?(current_user) %>
<%= button_to '参加する', group_group_users_path(@group), class: 'btn btn-success', method: :post %>
<p>グループに参加してからメッセージを送信してください。</p>
<% return %>
<% end %>
このコードを追記することにより、ユーザーがグループに参加していない場合に「参加ボタン」を表示し、メッセージ入力フォームを非表示にする。