Rails DMで画像と動画を送信できるようにする方法

## はじめに
前回実装したDM機能にプラスアルファで「画像」と「動画」も送信できるようにしていきましょう！！
簡単です。

前回の記事はこちら→ (https://qiita.com/YukiyaOgura/items/0e4888c6aad8dc1f9bf9)

## 完成イメージ

**画像イメージ**
![0FDB5615-A974-4F67-B9A8-6C1F51F6D8EA.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333273/d0598c54-38de-4fec-0e2f-f07efc908d24.jpeg)


**動画イメージ**
![259D8AE5-6243-4B9F-A814-0885B0E6D838.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333273/c07ac580-4ec5-fd37-a27b-69b29ccc41a2.jpeg)



## 開発環境、前提条件
* AWS Cloud9
* Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
* Rails 6.1.7.3
* Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態
* Active_Storageがインストール済み
* BootStrapがインストール済み
* FontAwesomeがインストール済み
* メッセージ機能を実装ずみ(この記事では、Chat,Roomモデル)

## Chatモデルの更新
```ruby:model/chat.rb
  has_one_attached :image #追加
  has_one_attached :video #追加
  validates :message, length: { in: 1..140 }, allow_blank: true #追加
  validate :only_one_type_of_attachment, :message_or_media_present? #追加

 private #追加

  def only_one_type_of_attachment
    if [message.present?, image.attached?, video.attached?].count(true) > 1
      errors.add(:base, "You can only add a message, an image, or a video.")
    end
  end

  def message_or_media_present?
    if [message.present?, image.attached?, video.attached?].count(true) == 0
      errors.add(:base, "You must provide a message, an image, or a video.")
    end
  end
```

`has_one_attached :image`
このモデルが一つの画像ファイルを持つこととします。

`has_one_attached :video`
同様にこのモデルで一つの動画を持つこととします。

`validates :message, length: { in: 1..140 }, allow_blank: true`
文字のバリデーション。1~140文字以内、画像や動画単体で送信する可能性もあるので空のメッセージは許可しています。

`validate :only_one_type_of_attachment, :message_or_media_present?`
この行でカスタムバリデーションを追加。

`def only_one_type_of_attachment`
このメソッドは添付ファイルに関するバリデーションルールの定義をしています。
メッセージ、画像、動画のうち１つ以上が同時に存在する場合はエラーになります。つまり、メッセージ、画像、動画のいずれか１のみ許可される仕組みです。
（ここはご自身のアプリケーション設計によって変わってきます。）

`def message_or_media_present?`
このメソッドは、メッセージまたは画像もしくは動画ファイルのいずれかが存在するか確認するメソッドです。

## chats.controllerの更新
```ruby:model/user.rb
    def chat_params
      params.require(:chat).permit(:message, :room_id, :image, :video)
    end
```
`:image`, `:video`を追加してあげましょう。

## ビューの更新
![20230514-143059.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333273/0b7867ff-779b-a659-ff8b-da82b2693d5e.png)

ではここで画像と動画のフォントオーサムを使用します。
```ruby:chats.show.html.erb
 <div class="chat-form">
  <%= form_with model: @chat, data: {remote: true} do |f| %>
    <%= f.text_field :message, placeholder: "メッセージを入力してください", autocomplete: "off" %>
    <%= f.hidden_field :room_id, value: @room.id %>

    <label for="chat_image" style="cursor: pointer;">
      <i class="fas fa-image fa-lg"></i>
    </label>
    <%= f.file_field :image, id: "chat_image", style: "display:none;" %>

    <label for="chat_video" style="cursor: pointer;">
      <i class="fas fa-video fa-lg"></i>
    </label>
    <%= f.file_field :video, id: "chat_video", style: "display:none;" %>

    <%= f.submit "送信" %>
  <% end %>
</div>
```

続いてSCSS（CSS）に記述しましょう。
```ruby:application.scss
.chat-form {
  background-color: #f5f5f5;
  padding: 20px;
  position: sticky;
  bottom: 0;

  form {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
  }

  input[type="text"] {
    flex: 1;
    margin-right: 10px;
    padding: 10px;
    font-size: 1em;
    border: none;
    border-radius: 5px;
    background-color: #fff;
  }

  input[type="submit"] {
    flex: 0 0 auto;
    padding: 10px;
    font-size: 1em;
    border: none;
    border-radius: 5px;
    background-color: #29b6f6;
    color: #fff;
    cursor: pointer;

  label {
    background-color: #fff;
    padding: 10px;
    border-radius: 50%;
    margin-right: 10px;
    cursor: pointer;

    i {
      font-size: 20px;
      color: #333;
    }
  }

  input[type="file"] {
    display: none;
  }
}

```

## メッセージ表示この更新
文章だけでなく、画像や動画を表示されるように変更していきましょう！！

```ruby:model/chat.rb
  <% chats.each do |chat| %>
  <div id="<%= chat.id %>">
    <% if chat.user_id == current_user.id %>
      <div class="message self <%= 'no-bg' if chat.image.attached? || chat.video.attached? %>">
        <div class="message-body">
          <%= chat.message %>
          <% if chat.image.attached? %>
            <div class='file-container'>
              <%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>
            </div>
          <% end %>
          <% if chat.video.attached? %>
            <div class='file-container'>
              <%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>
            </div>
          <% end %>

          </div>
```

`<% if chat.image.attached? %>`
ここはchatという変数が画像を持っていたら次の行が実行されます。

`<%= image_tag(chat.image.variant(resize_to_limit: [500, 500]), width: '100%', height: 'auto', class: 'rounded') %>`
ここではchatオブジェクトのimageを表示させます。
`variant(resize_to_limit: [500, 500])`は画像ファイルの最大値を500pxにします。
アスペクト比が崩れないようになっています。
また、`width`と`height`属性で画像の大きさを指定し、classに`rounded`を指定して画像の角を丸くしています。

`<% if chat.video.attached? %>`
ここでも同様にchatヘンスが動画を持っているかの確認をします。もし動画があれば次の行が実行されます。

`<%= video_tag rails_blob_path(chat.video), controls: true, class: 'responsive-video rounded' %>`
ここではchatオブジェクトの動画を表示します。

`rails_blob_path(chat.video)`
これは動画ファイルへのパスを生成します。

`controls: true`
これは、動画のコントロールバーを表示するためのオプションで、これにより再生/停止などの操作が可能になります。


**以上で実装は完了です！後は実際に送信して問題なく動作するか確認しましょう！お疲れ様でした！！**

## さらにクオリティを向上させたい方は見てください

「送信ボタン」を進化させよう。

画像や動画ファイルは重たいので送信してる最中に時間がかかると、「え？これちゃんと送れている？」と不安になりますよね。
なので今回は「送信前は濃い青色」「送信中は薄い青色」「送信後は濃い青色」となるようにしましょう！

先ほど書いたSCSSに追記しましょう

```ruby:application.scss
  input[type="submit"] {
    flex: 0 0 auto;
    padding: 10px;
    font-size: 1em;
    border: none;
    border-radius: 5px;
    background-color: #29b6f6;
    color: #fff;
    cursor: pointer;

    &.sending { #追加
      background-color: #90caf9;
      color: #fff;
    }

    &.sent { #追加
      background-color: #90caf9;
      color: #fff;
    }
  }
```

そしてJavasprictの記述も追加しましょう。
新しくファイルを作ってもいいしHTMLに直接記述してもいいです

```ruby:chats.show.html.erb
<sprict>
  document.addEventListener('ajax:beforeSend', function(event) {
  var submitButton = event.target.querySelector('input[type="submit"]');
  submitButton.classList.add('sending');
});

document.addEventListener('ajax:complete', function(event) {
  var submitButton = event.target.querySelector('input[type="submit"]');
  submitButton.classList.remove('sending');
  submitButton.classList.add('sent');

  setTimeout(function() {
    submitButton.classList.remove('sent');
  }, 3000);
});
</sprict>
```

細かいところですが、作成していて自分が不便だなと思ったら利用するユーザーも同じ気持ちになると思うので躊躇わず実装していきましょう！！


では以上で終了です！！
コメント、いいねお待ちしてます👍