相互フォローしている人限定でＤＭを送れるようにする。

地球の皆さんこんにちは。
今回は、この私が理解に苦しんだ、相互フォローしている人限定で、ＤＭのやりとりをできる方法を解説します。
私は、これまでプログラミングというものをやってこなかった.....そう、やってこなかったのです。
この苦しみ....あなたにわかりますか？わからないというなら......！くらえ！！！！

それでは、さっそく始めていきましょう。


>Rails 6.1.4
Ruby 2.6.3
Bootstrap, devise導入済み
Bookの投稿サイトを作っています

<h3>前提条件</h3>
Userモデル、コントローラ
relationshipsモデル、コントローラー(フォロー機能実装のためのもの)
以上が作成済みであること。


ちなみに私の好きな食べ物はオムライスです。


<h1>モデルを作ろう</h1>

さあ！ここで問題です。今から何を作るでしょうか！？
チッチッチッ........
ヒントは好きな食べ物です！！！
さあ！答えをどうぞ！！！






そう！！モデルを作ります！！

```ruby
rails g model Chat user_id:integer room_id:integer message:text
rails g model Room 
rails g model UserRoom user_id:integer room_id:integer
```


こうしたらカラムも一緒に作れるんですって！！奥さん！！
<h6>Roomモデル</h6>

ユーザーが会話するチャットルームの番号(ID)を管理

<h6>Chatモデル</h6>

チャットの内容を管理（あとでコントローラーも作ります）

<h6>UserRoomモデル</h6>

その名の通りユーザーとルームを結ぶ中間テーブル。

中間テーブルについては、まあ....うん....他の記事見てください


```ruby
rails db:migrate
```


忘れずにやっときましょう。



<h1>アソシエーションを設定しよう！</h1>

モデルが作れたらアソシエーションです。
ちなみに業界の方はアソシエーションを設定することを「アソシえる」というらしいです。














それではアソシえっていきましょうか！！



```ruby:user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  
  has_many :user_rooms ここから！
  has_many :chats
  has_many :rooms, through: :user_rooms　ここを追記！
  
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy

  has_many :followers, through: :reverse_of_relationships, source: :follower
  
 
  has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  
  has_many :followings, through: :relationships, source: :followed
  
  has_one_attached :profile_image

  validates :name, length: { minimum: 2, maximum: 20 }, uniqueness: true
  validates :introduction, length: { maximum: 50 }
  
  
  def get_profile_image
    (profile_image.attached?) ? profile_image : 'no_image.jpg'
  end
  
  def follow(user)
    relationships.create(followed_id: user.id)
  end

  def unfollow(user)
    relationships.find_by(followed_id: user.id).destroy
  end

  def following?(user)
    followings.include?(user)
  end
end

```
```ruby:chat.rb
class Chat < ApplicationRecord
  belongs_to :user
  belongs_to :room

  validates :message, presence: true, length: { maximum: 140 }
end

```
ついでにバリデーションもつけたよ♥


```ruby:room.rb
class Room < ApplicationRecord
  has_many :user_rooms
  has_many :chats
end

```

```ruby:user_room.rb
class UserRoom < ApplicationRecord
  belongs_to :user
  belongs_to :room
end

```
ユーザーモデルはなんかガチャガチャかいてますが、アソシエーションに関して追記するのは三行だけです。

では、関係性を見てみましょう。

<strong>ユーザー</strong>　対　<strong>チャットルーム</strong>
・ユーザーは多くの部屋に所属できる。
・チャットルームは多くのユーザー（今回はＤＭなので二人ですが）が所属している。

これは多対多ですね。
先ほども申しましたが今回はUser_Roomテーブルを中間テーブルとしています。
UserRoomテーブルの情報を参照してデータのやり取りをしていると思います。
正直理解はしていません。こんな僕をどうか嫌いにならないでください。


<strong>ユーザー</strong>　対　チャット
・一人のユーザーはたくさんチャットを投稿できる。
・一つのチャットに対するのは投稿した一人のユーザーのみ。

これは多対一です。
思い出しますね。僕もあの時一人対大勢での大暴動がありましてね。このチャットもあの時の僕と同じ気持ちなんでしょうね。

<h1>ルーティングを設定しよう</h1>

昨日まで何もなかったのに、こんなところにルーティングができてるな。興奮してきたな。

```ruby:routes.rb
resources :chats, only: [:show, :create]
```
ネストも何もさせていません。これでいいんです。そう....あれで....よかったんだ.............



<h1>コントローラーを作ろう</h1>

「コントローラー作るって言ったじゃん！嘘つき！」

おいおい。まってくれよアンジェリカ。これでいいんだろ？


```ruby:
rails g controller chats
```




皆さん念願のコントローラーを作りました。
でもこれで終わりじゃありませんよ。

コントローラーの中身を記述していきましょう！
いくぜ！俺について来い！！


```ruby:chats_controller.rb

class ChatsController < ApplicationController
  before_action :reject_non_related, only: [:show]
  def show
    @user = User.find(params[:id])
    rooms = current_user.user_rooms.pluck(:room_id)
    user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)

    unless user_rooms.nil?
      @room = user_rooms.room
    else
      @room = Room.new
      @room.save
      UserRoom.create(user_id: current_user.id, room_id: @room.id)
      UserRoom.create(user_id: @user.id, room_id: @room.id)
    end
    @chats = @room.chats
    @chat = Chat.new(room_id: @room.id)
  end
  def create
    @chat = current_user.chats.new(chat_params)
    render :validater unless @chat.save
  end

  private
  def chat_params
    params.require(:chat).permit(:message, :room_id)
  end

  def reject_non_related
    user = User.find(params[:id])
    unless current_user.following?(user) && user.following?(current_user)
      redirect_to books_path
    end
  end
end

```

はい。わけわからん。やめまーす。

なんて思っていませんか？

<h3>やる気のない奴は帰れーーーー！！！！！</h3>

```ruby:chats_controller.rb
class ChatsController < ApplicationController
  before_action :reject_non_related, only: [:show]
  
中略

  private
 

  def reject_non_related
    user = User.find(params[:id])
    unless current_user.following?(user) && user.following?(current_user)
      redirect_to books_path
    end
  end
end

```

unlessは～じゃなかった場合、trueを返して処理を行います。

この場合、現在のユーザー（わたし）が対象のユーザー（あなた）をフォローしていてかつ、対象のユーザー（あなた）が現在のユーザー（わたし）をフォローしていなかった場合、一覧画面へリダイレクトさせるという記述です。
これが言うならば相互フォローかどうかをみているわけでございますね。

これをbefore_actionのところに書いてます。
コントローラーの各アクションを行う前に相互フォローかどうかを確認してます。えらい。



```ruby:chats_controller.rb

class ChatsController < ApplicationController
  
  def show
    @user = User.find(params[:id]) #チャットする相手は誰か？
    rooms = current_user.user_rooms.pluck(:room_id)#ログイン中のユーザーの部屋情報を全て取得
    user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)#その中にチャットする相手とのルームがあるか確認

    unless user_rooms.nil?#ユーザールームが無くなかった（つまりあった。）
      @room = user_rooms.room #変数@roomにユーザー（自分と相手）と紐づいているroomを代入
    else#ユーザールームが無かった場合
      @room = Room.new #新しくRoomを作る
      @room.save #そして保存
      UserRoom.create(user_id: current_user.id, room_id: @room.id) #自分の中間テーブルを作る
      UserRoom.create(user_id: @user.id, room_id: @room.id) #相手の中間テーブルを作る
    end
    @chats = @room.chats #チャットの一覧用の変数
    @chat = Chat.new(room_id: @room.id) #チャットの投稿用の変数
  end

 
end

```


showアクションはこんな感じです。
create()はnewとsaveを同時にできるやつだと聞きました。

まあ、つまり

ユーザーIDという名の箱の中に現在のユーザーのID（もしくは相手の）を入れて

ルームIDという箱の中に二人の共通のルームIDをいれて保存。

これで新しいチャットルームが完成するんですね。

それでは大詰め。ビューいきましょう。


<h1>Viewをつくろう</h1>

コントローラー作るときに一緒に作ればよかったじゃんといわれると思います。
そんなあなた方に一言、言いたい。

すまん


```ruby:view/chats/show.html.erb
<h1 id="room" data-room="<%= @room.id %>" data-user="<%= current_user.id %>">
<%= @user.name %> さんとのチャット</h1>

<div class="message" style="width: 400px;">
  <% @chats.each do |chat| %>
    <% if chat.user_id == current_user.id %>
      <p style="text-align: right;"><%= chat.message %></p>
    <% else %>
      <p style="text-align: left;"><%= chat.message %></p>
    <% end %>
  <% end %>
</div>

<div class="errors">
  <%= render "layouts/errors", obj: @chat %>
</div>

<%= form_with model: @chat, data: {remote: true} do |f| %>
  <%= f.text_field :message %>
  <%= f.hidden_field :room_id %>
<% end %>
```


あとからエラ－文も設定します。
そしてコントローラーの時点でお気づきかもしれませんが非同期通信なのでremote trueとしてあります。
うんこぶりぶり

```ruby:view/chats/create.js.erb
$('.message').append("<p style='text-align: right;'><%= @chat.message %></p>");
$('input[type=text]').val("")
```
```ruby:view/chats/validater.js.erb
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
```

先ほどはしょったcreateアクションをあえてここで解説します。

```ruby:chats_controller.rb
def create
    @chat = current_user.chats.new(chat_params)
    render :validater unless @chat.save
end
```

もし新規投稿が保存されなかった場合validater.js.erbを探します。
逆に保存された場合create.js.erbを探します。
これが非同期の仕様です。そうです。正直わかりません。

```ruby:リンクはこうする。
<% if current_user != user && current_user.following?(user) && user.following?(current_user) %>
  <%= link_to 'chatを始める', chat_path(user.id), class: "ml-3" %>
<% end %>
```

もしログイン中のユーザーが対象のユーザーではなく、対象のユーザーをフォローしていてかつ、ぞのユーザーからフォローされていた場合リンクを出現させます。


非同期が分からないので、関係ありませんが、JS関係のファイルも情報添付します。
<details><summary>アプリケーション</summary><div>

```ruby:application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import jQuery from "jquery"
import "popper.js"
import "bootstrap"

import '@fortawesome/fontawesome-free/js/all';
import "../stylesheets/application" 

Rails.start()
Turbolinks.start()
ActiveStorage.start()

global.$ = jQuery;
window.$ = jQuery;
```
</div></details>
Gemfile、environmentなどは特に何もなし。

```ruby:エラーの部分テンプレート
<% if obj.errors.any? %>
  <div id="error_explanation">
    <h3><%= pluralize(obj.errors.count, "error") %> prohibited this obj from being saved:</h3>
    <ul>
      <% obj.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

```
<h1>おわりに</h1>

おい！何見てんだよ！見せもんじゃねえぞ！
ということで自分のために書いていました。

僕は地球に来て間もないですが、いつか来る災厄のためにプログラミング頑張ります！

ファイヤー

















はい。