RailsとActive Storageを使った登録画面と編集画面で即時プレビュー!

# ActiveStorageで登録画面や編集画面でプレビューする方法

## 自分の環境をご紹介
**ActiveStorageのセットアップは行っている前提**
- rails7
- ruby3.0.2
- Bootstrap５とicon
- JavaScriptはバニラJSです
- AWS S3で画像を保存してます(ここはassets/imagesで保存した画像でもできます)
- ActiveStorageでhas_one_attachedで1つのモデルに1つの画像です。
## 結論からどんなのができるのか？

### 新規登録側
新規登録ではS3に保存したデフォルトの画像が最初に表示されます
デフォルトで設定しないとArgumentErrorが出ますので必ずデフォは用意してください
デフォルトは、assets/imagesで保存した画像でもできます。
![スクリーンショット 2023-06-16 23.39.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/2b86174d-d4da-15f5-b755-5300a4c2f6a8.png)


画像を選択するとプレビューがパッと変わります。

![スクリーンショット 2023-06-16 23.15.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/c24fa910-1be1-225b-1da5-ed2c4dbd64ec.png)

これの何がいいかと言いますと**個人的にかっこいいから**です。

ってのは冗談で、単純に選択した画像がその場ですぐわかるからですね！

### 編集画面
![スクリーンショット 2023-06-16 23.42.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/b38ece77-1e8c-fdd3-5199-892f8168c870.png)

もちろんこのように元々保存されている画像もプレビューできます！

画像選択後は新規登録と同じです！

## やり方について1つずつ説明します

### フォームから
一様報告しておきます、画像のinput部分のみです。

```erb:_form.html.erb
<div class='mb-3'>
  <i class="bi bi-image"></i>
  <%= f.label :image %>
  <%= f.file_field :image, accept: "image/*", id: 'image' %>
  <div class='mt-3'>
    <% if product.image.attached? %>
      <%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>
    <% else %>
      <%= image_tag(Rails.application.credentials.dig(:aws, :s3, :default_image_url), id: 'image-preview', class: 'rounded image-resize') %>
    <% end %>
</div>
```
`<%= f.file_field :image, accept: "image/*", id: 'image' %>`これで画像を選択する
部分ができます、`has_one_attached :image`で設定しているため`:image`です
`accept: "image/*"`これは、クライアント側で全ての画像形式のみ受け付けるようにしています。
動画とかはダメですよ〜！と検証ができます。
`id: 'image'`はJavaScriptでDOM操作で使います。
`<% if product.image.attached? %>`は、attached?メソッドでActiveRecordオブジェクトが
データベースに保存されているかどうかを確認してくれます。
編集用と新規用で条件分岐しています。
新規は登録されていないので`false`を返します、よって
```
<%= image_tag(Rails.application.credentials.dig(:aws, :s3, :default_image_url), id: 'image-preview', class: 'rounded image-resize') %>
```
ここのコードの処理が行われます。
`Rails.application.credentials.dig(:aws, :s3, :default_image_url)`
ここの部分はS3で保存した
画像を`rails credentials`で`credentials.yml.enc`という暗号化された設定ファイルを使って
セキュアな情報を保存しています。(そのままURLを見せないようにする対策ですね！)
`id: 'image-preview', class: 'rounded image-resize'`
一気に説明します！
idは先ほどと同じJSのDOM操作で使います。
classはBootstrapを使用し、画像に少し丸みのある角にしています。
`image-resize`は下で説明してます、カスタムCSSです！

お次はDBに保存されている方、`persisted?`メソッドで`true`を返している方ですね

編集画面の時のプレビューです。

```
<%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>
```
`product.image.variant(resize_to_limit: [200, 200])`は、productモデルから
保存された画像を200x200にリサイズしてくれます。

後はほぼ先ほどと同じです！

:::note
`<%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>`
なんでこっちもリサイズをカスタムで設定しているの？？
`variant(resize_to_limit: [200, 200]`でリサイズしてますよね？
説明しよう！
variantはActiveStorageが画像を処理する際に使用するメソッドで、
保存される画像の大きさをリサイズしてくれます
着目して欲しいところは**保存**です
画面を選択しているときは、保存されておりません。
よって選択によってプレビューしている画像も一緒の大きさにしたいのでカスタムCSS使ってます。
:::

### 保存されていない画像サイズ変更用のCSS
```css:application.css
.image-resize {
  max-width: 200px;
  max-height: 200px;
  object-fit: cover;
}
```
最大横縦幅を200pxにしています。
`object-fit: cover`で画像のアスペクト比が保持しつつ指定した
幅に気持ちよくフィットするようにしています

### JavaScriptです

```javascript:preview.js
document.addEventListener('turbo:load', () => {
  const imageInput = document.querySelector('#image');
  const imagePreview = document.querySelector('#image-preview');

  imageInput.addEventListener('change', (e) => {
    const file = e.target.files[0];
    const reader = new FileReader();

    reader.onloadend = () => {
      imagePreview.src = reader.result;
    };

    if (file) {
      reader.readAsDataURL(file);
    } else {
      imagePreview.src = "";
    }
  });
});
```

`turbo:load`にすることで,Hotwireが新しいページを読み込み終えた時に発生しその後に実行する関数を定義してます。

これは非常に大事で`turbo:load`を設定しないと
いちいち画面をリロードしないとJavaScriptが反映されません。

```javascript
const imageInput = document.querySelector('#image');
const imagePreview = document.querySelector('#image-preview');
```
この部分は先ほどのHTMLで設定した、idを指定してそれぞれの定数に保存しています

```javascript
imageInput.addEventListener('change', (e) => {
    const file = e.target.files[0];
    const reader = new FileReader();

    reader.onloadend = () => {
      imagePreview.src = reader.result;
    };
```

imageInput要素(画像を選択する方)でchangeイベントを発生したときに実行する関数を定義しています
ここでのchangeイベントは、userが新しいファイルを選択したときに発生するイベントです。

`const file = e.target.files[0];`インデックス番号0番、よって最初に選択された
ファイルを取得し`file`という定数に保存してます

`const reader = new FileReader();`
FileReaderオブジェクトは、ユーザーが選択したファイルを読み込むために使用されます。
それを`render`定数に保存

```javascript
reader.onloadend = () => {
  imagePreview.src = reader.result;
};
```
ファイルを読み込んだ後に実行する関数を書いています
`FileReaderオブジェクト`が読み込んだ結果（画像URL）を`imagePreview要素`（img要素）の
`src`属性に設定しています。これにより、選択された画像のプレビューが表示されます。
まとめると、読み込んだファイルの結果を`src`属性に設定しプレビューしてます。

`<img src=''>`srcの中身を読み込んだ画像のURLを動的に変えてるイメージです！

次はuserがファイルを選択している時としていない時の条件分岐です。

```javascript
if (file) {
  reader.readAsDataURL(file);
} else {
  imagePreview.src = "<%= Rails.application.credentials.dig(:aws, :s3, :default_image_url) %>";
}
```

簡単にいうと、選択しているとファイルのデータをURLとして読み込むように

選択していないときはデフォルト画像を表示するようにしています。

今回の場合選択していない時というのは新規登録の時です！

## 最後に
前から実装してみたい機能だったので無事に実装できてよかったです！！

お疲れ様でした！