34
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Railsでモデルを複数使用しチャット(DM)機能を作成する

Last updated at Posted at 2018-12-28

目標

UserとShopがお互いにチャットでやり取りをできるようにする

モデル設計

Userモデル:Deviseでログイン機能を実装

Shopモデル:Deviseでログイン機能を実装:

Roomモデル:どのUserがどのShopとチャットをしているのかという情報が格納される。UserとShopの中間テーブル

Messageモデル:どのルームでUserまたはShopのどちらが何を発信したのかという情報が格納される

開発

プロジェクトの作成

ターミナル
$ rails new chat_sample

Userモデルの作成

Gemfile

ログイン機能を実装するため、Gemfileに以下を追加してください。

Gemfile
gem 'devise'

gemを追加したので以下のコマンドを入力してください。

ターミナル
$ bundle install

Railsのプロジェクトにdeviseをインストールさせます。

ターミナル
$ rails g devise:install

Userモデルをdeviseを用いて作っていきます。

ターミナル
$ rails g devise user name:string age:integer
$ rails g devise:controllers users
$ rails g devise:views users
$ rails db:migrate

Shopモデルの作成

同様にShopモデルを作成します。

ターミナル
$ rails g devise shop name:string genre:string
$ rails g devise:controllers shops
$ rails g devise:views shops
$ rails db:migrate

deviseのカスタマイズ

devise.rb

devise.rbの以下の部分を変更しよう

devise.rb
# 以下を消す
# config.scoped_views = false

# 以下を追加
config.scoped_views = true

これによりdeviseのビューを変更することができるようになりました。

新規登録時にemail, password以外も登録できるようにする

まずはuserの新規登録時にnameageを登録できるようにする。

users/registrations/new.html.erb

views/users/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

<!--省略-->
  <!--ここから追加部分 -->
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <div class="field">
    <%= f.label :age %><br />
    <%= f.number_field :age %>
  </div>
  <!--ここまで追加部分 -->
<!--省略-->

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "users/shared/links" %>

shops/registrations/new.html.erb

views/shops/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

<!--省略-->
  <!--ここから追加部分 -->
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <div class="field">
    <%= f.label :genre %><br />
    <%= f.text_field :genre %>
  </div>
  <!--ここまで追加部分 -->
<!--省略-->

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "shops/shared/links" %>

applicatoin_controller.rbに以下を追加してください

applicatoin_controller.rb

applicatoin_controller.rb
# 省略
before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    if resource_class == User
      devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :age])
    elsif resource_class == Shop
      devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :genre])
    else
      super
    end
  end
# 省略

これによりUserがログインした時はnameカラムとageカラムが同時に登録されるようになりました。
また、Shopがログインした時はnameカラムとgenreカラムが同時に登録されます。

他のモデルの作成

ターミナル
$ rails g model room user:references shop:references
$ rails g model message room:references is_user:boolean content:text
$ rails db:migrate

アソシエーションの作成

user.rb

models/user.rbに以下を追加

user.rb
# 以下を追加
  has_many :rooms

shop.rb

models/shop.rbに以下を追加

shop.rb
# 以下を追加
  has_many :rooms

room.rb

models/room.rbに以下を追加

room.rb
# 以下を追加
  has_many :messages

コントローラーの作成

全体のトップページとなるtops_controllerを作成する.

ターミナル
$ rails g controller tops index

またチャット画面を作成、表示するためにrooms_controllerも作成する

ターミナル
$ rails g controller rooms show

また、メッセージを送るためにmessages_controllerも作成する

ターミナル
$ rails g controller messages

ルーティングの作成

routes.rb

config/routes.rbを以下のように変更する。

routes.rb
Rails.application.routes.draw do
  root 'tops#index'
  devise_for :shops
  devise_for :users
  resources :rooms, :only => [:show, :create] do
    resources :messages, :only => [:create]
  end
end

トップのビューの作成(/tops/index.html.erb)

ログイン用のリンクの作成

tops/index.html.erb

views/tops/index.html.erbを以下のように変更する。

index.html.erb
<h1>チャットサンプル</h1>
<h3>ユーザー</h3>
<%= link_to "ログイン", new_user_session_path %>
<%= link_to "新規登録", new_user_registration_path %>
<h3>ショップ</h3>
<%= link_to "ログイン", new_shop_session_path %>
<%= link_to "新規登録", new_shop_registration_path %>

$ rails sで確認すると、ユーザーとショップそれぞれで「ログイン」、「新規登録」のリンクが貼られているはずです。

ログインしている状態によって見た目を変更する

userとshopがログインするので使っている人の状態は以下の3つが考えられます。
① userとしてログインしている
② shopとしてログインしている
③ どちらでもログインしてない

これをビューで分けることによりログインしている状態で適切な情報が見られるようになります
・ userがログインしている時にはuserのログアウト用のリンク
・ shopがログインしている時にはshopのログアウト用のリンク
・ どちらもログインしてない時にはログイン用のリンク
を表示させます。

tops/index.html.erb

tops/index.html.erb
<h1>チャットサンプル</h1>
<!--Userでログインしてたら-->
<% if user_signed_in? %>
  ~<%= current_user.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_user_session_path ,:method => :delete %>

<!--Shopでログインしてたら-->
<% elsif shop_signed_in? %>
  ~<%= current_shop.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_shop_session_path ,:method => :delete %>

<!--ログインしてなかったら-->
<% else %>
  <h3>ユーザー</h3>
  <%= link_to "ログイン", new_user_session_path %>
  <%= link_to "新規登録", new_user_registration_path %>
  <h3>ショップ</h3>
  <%= link_to "ログイン", new_shop_session_path %>
  <%= link_to "新規登録", new_shop_registration_path %>
<% end %>

相手の一覧表示機能をつける

次に相手の情報を一覧表示させていきます。

・ userがログインしている時にはshopの名前
・ shopがログインしている時にはuserの名前
を一覧表示させられるようにしていきます。

tops_controller.rb

tops_controller.rb
class TopsController < ApplicationController
  def index
    if user_signed_in?
      @shops = Shop.all
    elsif shop_signed_in?
      @users = User.all
    end
  end
end

userがログイン→Shopの全情報をとってきて@shopsにいれる
shopがログイン→Userの全情報をとってきて@usersにいれる

tops/index.html.erb

これをビューで表示させましょう。tops/index.html.erbを以下のように変更してください。

tops/index.html.erb
<h1>チャットサンプル</h1>
<!--Userでログインしてたら-->
<% if user_signed_in? %>
  ~<%= current_user.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_user_session_path ,:method => :delete %>
  <br>
  <h2>ショップ一覧</h2>
  <br>

  <% @shops.each do |shop| %>
    <%= shop.name %>
  <% end %>
<!--Shopでログインしてたら-->
<% elsif shop_signed_in? %>
  ~<%= current_shop.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_shop_session_path ,:method => :delete %>
  <br>
  <h2>ユーザー一覧</h2>
  <br>
  <% @users.each do |user| %>
    <%= user.name %>
  <% end %>

<!--ログインしてなかったら-->
<% else %>
  <h3>ユーザー</h3>
  <%= link_to "ログイン", new_user_session_path %>
  <%= link_to "新規登録", new_user_registration_path %>
  <h3>ショップ</h3>
  <%= link_to "ログイン", new_shop_session_path %>
  <%= link_to "新規登録", new_shop_registration_path %>
<% end %>

$ rails sをターミナルに入力し、ブラウザでlocalhost:3000を入力して確認してみてください。
eachメソッドでuser, shopの情報を順に表示させました。

チャットルーム機能実装

ここからはtopページから実際のチャットページに飛べるようにしていきます(userやshopの詳細ページに一回飛んで、そこからチャットルームに飛ぶ方が実際はいいですww今回は簡易的なのでそこは省略ということで)

tops_controller.rb

tops_controller.rb
class TopsController < ApplicationController
  def index
    if user_signed_in?
      @shops = Shop.all
      rooms = current_user.rooms
      #自分が入ってるroomの相手のidを格納する
      @shop_ids = []
      rooms.each do |r|
        @shop_ids << r.shop_id
      end

    elsif shop_signed_in?
      @users = User.all
      rooms = current_shop.rooms
      #自分が入ってるroomの相手のidを格納する
      @user_ids = []
      rooms.each do |r|
        @user_ids << r.user_id
      end
    end
  end
end

自分が入ってる Room の相手の情報を配列に格納することで、viewで誰とのチャットなのか見られるようにします

rooms_controller.rb

rooms_controller.rb
class RoomsController < ApplicationController
  def show
    @room = Room.find(params[:id]) #ルーム情報の取得
    @message = Message.new #新規メッセージ投稿
    @messages = @room.messages #このルームのメッセージを全て取得
    if user_signed_in?
      if @room.user.id == current_user.id
        @shop = @room.shop
      else
        redirect_to "/"
      end
    elsif shop_signed_in?
      if @room.shop.id == current_shop.id
        @user = @room.user
      else
        redirect_to "/"
      end

    else
      redirect_to "/"
    end
  end

  def create
    if user_signed_in?
      #userがログインしてたらuser_idを, shopがログインしてたらshop_idを@roomにいれる
      @room = Room.new(room_shop_params)
      @room.user_id = current_user.id
    elsif shop_signed_in?
      @room = Room.new(room_user_params)
      @room.shop_id = current_shop.id
    else
      redirect_to "/"
    end

    if @room.save
      redirect_to :action => "show", :id => @room.id
    else
      redirect_to "/"
    end
  end

  private
  def room_shop_params
    params.require(:room).permit(:shop_id)
  end

  def room_user_params
    params.require(:room).permit(:user_id)
  end
end

showでは新規メッセージ投稿ができたり、メッセージの閲覧ができるようにします
createでは適切な user_id と shop_id が入るようにしています

messages_controller.rb

messages_controller.rb
class MessagesController < ApplicationController
  def create
    @room = Room.find(params[:room_id])
    @message = Message.new(message_params)
    #メッセージがuserによるものだったらis_user=true, shopによるものだったらis_user=false
    if user_signed_in?
      @message.is_user = true
    elsif shop_signed_in?
      @message.is_user = false
    end
    @message.room_id = @room.id
    if @message.save
      redirect_to room_url(@room)
    else
      redirect_to room_url(@room)
    end
  end

  private
  def message_params
    params.require(:message).permit(:content)
  end
end

createではメッセージを作成するのですが、userとshopのどちらが話した内容なのかという情報も格納します。

tops/index.html.erb

tops/index.html.erb
<h1>チャットサンプル</h1>
<!--Userでログインしてたら-->
<% if user_signed_in? %>
  ~<%= current_user.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_user_session_path ,:method => :delete %>
  <br>
  <h2>ショップ一覧</h2>
  <br>

  <% @shops.each do |shop| %>
    <%= shop.name %>
    <!-- もしチャットルームがあったらそのチャットページへ。なければ新たなチャットルームを作成 -->
    <% if @shop_ids.include?(shop.id) %>
      <br>
      <%= link_to "チャットへ", room_path(current_user.rooms.find_by(shop_id: shop.id)) %><br>
    <% else %>
      <%= form_for Room.new do |f| %>
        <%= f.hidden_field :shop_id, :value => shop.id %>
        <%= f.submit %>
      <% end %>
    <% end %>
  <% end %>
<!--Shopでログインしてたら-->
<% elsif shop_signed_in? %>
  <h2>ユーザー一覧</h2>
  <br>
  ~<%= current_shop.name %>がログインしてます~
  <%= link_to "ログアウト", destroy_shop_session_path ,:method => :delete %>
  <br>
  <% @users.each do |user| %>
    <%= user.name %>
    <!-- もしチャットルームがあったらそのチャットページへ。なければ新たなチャットルームを作成 -->
    <% if @user_ids.include?(user.id) %>
      <br>
      <%= link_to "チャットへ", room_path(current_shop.rooms.find_by(user_id: user.id)) %><br>
    <% else %>
      <%= form_for Room.new do |f| %>
        <%= f.hidden_field :user_id, :value => user.id %>
        <%= f.submit %>
      <% end %>
    <% end %>

  <% end %>

<!--ログインしてなかったら-->
<% else %>
  <h3>ユーザー</h3>
  <%= link_to "ログイン", new_user_session_path %>
  <%= link_to "新規登録", new_user_registration_path %>
  <h3>ショップ</h3>
  <%= link_to "ログイン", new_shop_session_path %>
  <%= link_to "新規登録", new_shop_registration_path %>
<% end %>

rooms/show.html.erb

rooms/show.html.erb
<h1>チャットサンプル</h1>
<% if user_signed_in? %>
  <h3><% @shop.name %>とのチャットルーム</h3>
  <div class="chat-field">
    <% @messages.each do |m| %>
      <!-- メッセージがUserによるものだったら -->
      <% if m.is_user %>
        <!-- メッセージを右に寄せる -->
        <div class="right-message">
          <%= m.content %>
        </div>
      <!-- メッセージがShopによるものだったら -->
      <% else %>
        <!-- メッセージを左に寄せる -->
        <div class="left-message">
          <%= m.content %>
        </div>
      <% end %>
    <% end %>
  </div>
<% elsif shop_signed_in?%>
  <h3><% @user.name %>とのチャットルーム</h3>
  <div class="chat-field">
    <% @messages.each do |m| %>
      <!-- メッセージがUserによるものだったら -->
      <% if m.is_user %>
        <!-- メッセージを左に寄せる -->
        <div class="left-message">
          <%= m.content %>
        </div>
      <!-- メッセージがShopによるものだったら -->
      <% else %>
        <!-- メッセージを右に寄せる -->
        <div class="right-message">
          <%= m.content %>
        </div>
      <% end %>
    <% end %>
  </div>
<% end %>

<%= form_for [@room, @message] do |f| %>
  <%= f.text_field :content %>
  <%= f.submit "メッセージを送る"%>
<% end %>

誰が話しているのかによってトーク内容を左に表示させるか右に表示させるかを変化させます(LINEやMessengerのイメージ)

application.css

application.css
.left-message{
  text-align: left;
}

.right-message{
  text-align: right;
}

まとめ

userとshopの2つの視点から考えながらチャット機能を作成しました。
参考にしてみてください

34
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?