book投稿アプリにユーザ同士で 1 対 1 の DM ができるようにDM機能を追加します。
※ 140 字まで送信可能にする
非同期通信で作成します。
前提
・device導入 ・Bootstrap導入
・ユーザー機能実装済(Userモデル)
モデル作成
4つのモデルを使って実装します。
・Userモデル => Userの情報(devise)
・Roomモデル => 自分と相手の2人のユーザーが入ります
・Entryモデル => どのUserがどのRoomに所属しているかを判断する
・Messageモデル => UserがどのRoomでどんなメッセージを送ったか
userモデルは実装済なのであと3つ作成する
$ rails g model room
$ rails g model entry user:references room:references
$ rails g model message user:references room:references body:text
$ rails db:migrate
コントローラー作成
$ rails g controller rooms show
$ rails g controller messages
リレーション設定
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
User モデルは複数の Entry、messages レコードを持ちます。これは、1対多の関係です。また、関連するレコードはユーザーが削除されたときに一緒に削除されるように設定されています (dependent: :destroy)。
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
Room モデルは複数の Entry、messages レコードを持ちます。これは 1対多の関係です。また、関連する Entry レコードはルームが削除されたときに一緒に削除されるように設定されています (dependent: :destroy)。
belongs_to :user
belongs_to :room
Entry モデルは1つの User レコードに属しています (belongs_to :user)。また、1つの Room レコードに属しています (belongs_to :room)。
belongs_to :user
belongs_to :room
空でない&最大140文字以下であるバリデーションも追加
validates :message, presence: true, length: { maximum: 140 }
def message
body
end
Message モデルも1つの User レコードに属しています (belongs_to :user)。また、1つの Room レコードに属しています (belongs_to :room)。
Message モデルには、メッセージの本文を取得するための message メソッドが定義されています。これにより、body カラムの値が message メソッドを通じてアクセスできます。
ルーティング追加
resources :messages, only: [:create]
resources :rooms, only: [:create, :index, :show]
コントローラー
class UsersController < ApplicationController
before_action :authenticate_user!, only: [:show]
def show
@user = User.find(params[:id])
@currentUserEntry=Entry.where(user_id: current_user.id)
@userEntry=Entry.where(user_id: @user.id)
if @user.id == current_user.id
else
@currentUserEntry.each do |cu|
@userEntry.each do |u|
if cu.room_id == u.room_id then
@isRoom = true
@roomId = cu.room_id
end
end
end
if @isRoom
else
@room = Room.new
@entry = Entry.new
end
end
before_action :authenticate_user!, only: [:show]
showアクションが実行される前に、ユーザーの認証(ログイン)が行われているか確認している。ユーザーがログインしていない場合にはログインページにリダイレクトさせる。
@user = User.find(params[:id])
params[:id]で、表示するユーザーのIDを取得します。
@currentUserEntry
現在ログイン中のユーザーが関与しているエントリー(チャットルームへの参加)を取得します
@userEntry
表示するユーザーが関与しているエントリーを取得します。
if @user.id == current_user.id
表示するユーザーが自分である場合の処理を行います。
@currentUserEntry.each do |cu|
@userEntry.each do |u|
@currentUserEntry、@userEntryに格納されている要素を1つずつ取り出し、一時的な変数cu
とu
に代入して処理を行います。これにより、@currentUserEntry、@userEntryの要素を順番にアクセスできます。
if cu.room_id == u.room_id then
cuとuが持つ部屋の番号(room_id)が同じだったら、次のことをする。
@isRoom = true
@isRoomというフラグに「はい」という意味のtrueを設定します。これは、部屋が見つかったことを示すためのフラグです。
@roomId = cu.room_id
見つかった部屋の番号を@roomIdに保存します。これは、後で使うために部屋の番号を保持しておくための変数です。
if @isRoom
else
@room = Room.new
@entry = Entry.new
end
@isRoomというフラグに基づいて条件分岐を行っています。
@isRoomがtrue(部屋が見つかった場合)であれば、特定の処理を実行します。
@isRoomがfalse(部屋が見つからなかった場合)であれば、以下の処理が実行されます。
@room = Room.new:新しいRoomオブジェクトを作成し、@roomに代入します
@entry = Entry.new:新しいEntryオブジェクトを作成し、@entryに代入します。
class RoomsController < ApplicationController
before_action :authenticate_user!
def create
@room = Room.create(user_id: current_user.id)
@entry1 = Entry.create(:room_id => @room.id, :user_id => current_user.id)
@entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
redirect_to "/rooms/#{@room.id}"
end
def show
@room = Room.find(params[:id])
@rooms = Room.all
if Entry.where(user_id: current_user.id, room_id: @room.id).present?
@messages = @room.messages
@message = Message.new
@entries = @room.entries
@user = User.find(@room.user_id)
@myUserId = current_user.id
else
redirect_back(fallback_location: root_path)
end
end
end
@room = Room.create(user_id: current_user.id)
現在のユーザーのIDを持つ新しいルームを作成し、@roomに代入します。
@entry1 = Entry.create(:room_id => @room.id, :user_id => current_user.id)
お部屋に入るためのエントリーを作ります。roomのIDとエントリーした人のIDを@entryに代入する。
@entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
他の人がエントリーするために、必要な情報を集めます。それは、エントリーした人のIDとroomのIDです。それを使って、新しいエントリーを作成します。
redirect_to "/rooms/#{@room.id}"
作成したルームの詳細ページにリダイレクトします。
@room = Room.find(params[:id]):指定されたIDのroomを見つけます。
@rooms = Room.all:すべてのroomの情報を取得します。
if Entry.where(user_id: current_user.id, room_id: @room.id).present?
ログインしているユーザーが指定されたroomにエントリーしているかをチェックします。
もしエントリーしている場合:
@messages = @room.messages:そのroomのメッセージを取得します。
@message = Message.new:新しいメッセージを作成するための準備をします。
@entries = @room.entries:そのroomのエントリー情報を取得します。
@user = User.find(@room.user_id):roomを作成したユーザーの情報を取得します。
@myUserId = current_user.id:現在ログインしているユーザーのIDを@myUserIdに代入します。
エントリーしていない場合:リダイレクトして、元の場所に戻ります
class MessagesController < ApplicationController
before_action :authenticate_user!, :only => [:create]
def create
if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id]).present?
@message = Message.create(user_id: current_user.id, body: params[:message][:message], room_id: params[:message][:room_id])
redirect_to "/rooms/#{@message.room_id}"
else
redirect_back(fallback_location: root_path)
end
end:snowflake:
end
private
def message_params
params.require(:message).permit(:user_id, :body, :room_id).merge(user_id: current_user.id)
end
before_action :authenticate_user!, :only => [:create]
ユーザーがログインしているかどうかをチェックしています。
create アクションに対してのみユーザーの認証を行うように指定しています。
if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id]).present?
ログインしているユーザーが指定されたルームにエントリーしているかをチェックしています。
もしエントリーしている場合:
@message = Message.create(user_id: current_user.id, body: params[:message][:message], room_id: params[:message][:room_id])
新しいメッセージを作成し、現在のユーザーのID、メッセージの内容、およびルームのIDを設定します。
redirect_back(fallback_location: root_path)
メッセージが作成されたルームの詳細ページにリダイレクトします。
エントリーしていない場合:
リダイレクトして、元の場所に戻ります
これにより、ユーザーが指定されたルームにエントリーしている場合にメッセージが作成され、それ以外の場合はリダイレクトされるようになりま
views作成
チャットの表示とメッセージの投稿フォーム
<% if @user %>
<h1 id="room" data-room="<%= @room.id %>" data-user="<%= current_user.id %>"><%= @user.name %> さんとのチャット</h1>
<% end %>
<%= link_to "ユーザー一覧に戻る", users_path %>
<div class="message" style="width: 400px;">
<% @messages.each do |message| %>
<% if message.user_id == current_user.id %>
<p style="text-align: right;"><%= message.message %></p>
<% else %>
<p style="text-align: left;"><%= message.message %></p>
<% end %>
<% end %>
</div>
<div class="errors">
<%= render "layouts/errors", obj: @message %>
</div>
<%= form_with model: @message, data: {local: false} do |f| %>
<%= f.text_field :message %>
<%= f.hidden_field :room_id, value: @room.id %>
<%= f.submit "投稿",class: 'form-submit'%>
<% end %>
<%= form_with model: @message, data: {local: false} do |f| %> は、**remote: true**でも可
div class="message" タグ内のコードは、@messages 変数に格納されているメッセージを表示しています。each ループを使用して、各メッセージを表示しています。
div class="errors" タグ内のコードは、エラーメッセージを表示するための部分です。render メソッドを使用して、layouts/errors という部分テンプレートを表示します。
<%= form_with model: @message, data: {local: false} do |f| %>
メッセージの投稿フォームを生成しています。@message をモデルとして使用し、data: {local: false} を指定して非同期リクエストを送信するようにしています。フォームにはテキストフィールドと、投稿ボタンも表示されます。
<!--メッセージボタン-->
<% unless user.id == current_user.id %>
<% if (current_user.following? @user) && (user.following? current_user) %>
<% if isRoom == true %>
<p class="user-show-room"><a href="/rooms/<%= roomId %>" class="btn btn-primary btn-lg">chatへ</a>
<% else %>
<%= form_for room do |f| %>
<%= fields_for entry do |e| %>
<%= e.hidden_field :user_id, value: user.id %>
<% end %>
<%= f.submit "chatを始める", class:"btn btn-primary btn-lg user-show-chat"%>
<% end %>
<% end %>
<% end %>
<% end %>
<% unless user.id == current_user.id %>
ユーザーが自分自身でない場合にのみ表示されるコードブロックを示しています。
<% if (current_user.following? @user) && (user.following? current_user) %>
自分が相手をフォローしていて、相手にもフォローされている場合にのみ表示されるコードブロック(chatへボタン)を示しています。
<% if isRoom == true %>
既にチャットルームが存在する場合にのみ表示されるコードブロック
(chatを始める)を示しています。
<%= form_for room do |f| %> ... <% end %>
チャットルームへのリンクを表示しています。リンク先は /rooms/<%= roomId %> です。
<%= form_for room do |f| %> ... <% end %>
新しいチャットルームを作成するためのフォームを表示しています。
fields_for メソッドを使って entry モデルの隠しフィールドが生成されます。
部分テンプレートを使用しているのでchatボタンを表示したいところに以下のように変数を定義する。
<%= render 'info', user: @user, isRoom: @isRoom, roomId: @roomId, room: @room, entry: @entry %>
jsファイル
(非同期通信にするよう)
roomsディレクトリ内にcreate.js.erbを作成する。
$('.message').append("<p style='text-align: right;'><%= @message.body %></p>");
$('input[type=text]').val("");
完成イメージ
参考