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

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

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

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

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

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


#開発
##プロジェクトの作成
```terminal:ターミナル
$ rails new chat_sample
```

##Userモデルの作成
####Gemfile
ログイン機能を実装するため、`Gemfile`に以下を追加してください。

```rb:Gemfile
gem 'devise'
```

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

```terminal:ターミナル
$ bundle install
```
Railsのプロジェクトにdeviseをインストールさせます。

```terminal:ターミナル
$ rails g devise:install
```
Userモデルをdeviseを用いて作っていきます。

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

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

```terminal:ターミナル
$ 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の新規登録時に`name`と`age`を登録できるようにする。

####users/registrations/new.html.erb
```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
```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カラムが同時に登録されます。

#他のモデルの作成
```terminal:ターミナル
$ 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`を作成する.

```terminal:ターミナル
$ rails g controller tops index
```

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

```terminal:ターミナル
$ rails g controller rooms show
```
また、メッセージを送るために`messages_controller`も作成する

```terminal:ターミナル
$ 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`を以下のように変更する。

```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がログインしている時には<b>userのログアウト用のリンク</b>
・ shopがログインしている時には<b>shopのログアウト用のリンク</b>
・ どちらもログインしてない時には<b>ログイン用のリンク</b>
を表示させます。

####tops/index.html.erb
```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`を以下のように変更してください。

```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
```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
```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つの視点から考えながらチャット機能を作成しました。
参考にしてみてください