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?