登録フォーム上でアップロードされた画像のプレビューを表示する(Rails)

https://qiita.com/AttAtD/items/6b9502c0fa75531ee400
の続き


## 画像処理するGemの追加

- Gemにmini_magickとimage_proceccerを追加

Gemfile
```ruby
gem "image_processing", "~> 1.2"
gem 'mini_magick'
```

追加後に`bundler install`を実行

- application.rbに使用するプロセッサーを登録

```ruby

config.active_storage.variant_processor = :mini_magick

```

## 投稿画面にプレビュー表示を追加する。

- app/views/posts/new.html.erbに表示エリアを追加

また、ファイル選択時にJavascriptのメソッド(image-preview#readImage)を呼び出すようにする。

```ruby

<div data-controller="image-preview">
  <div><%= f.file_field :photos, data: { "image-preview-target": "input", action: "image-preview#readImage" }%></div>

  <ul data-image-preview-target="output">
  </ul>
</div>


```

- javascripts/controller/image_preview_controller.jsを追加。

```javascript
import { Controller } from "@hotwired/stimulus"

let data = null;

export default class extends Controller {
  static targets = [ "output", "input"];
  
  connect() {
    data = new DataTransfer();
  }

  readImage(e) {
    let input = this.inputTarget;
    let output = this.outputTarget;

    if (input.files) {
      let files = Array.from(input.files);
      
      files.forEach((f) => data.items.add(f));
      for (let i = 0; i < files.length; i++) {
        this.readOne(files[i], i, output);
      }
      input.files = data.files;
    }
  }

  readOne(f, index, output) {
    let reader = new FileReader();
    reader.readAsDataURL(f);
    reader.onload = () => {
      const html= `<li>
                  <div>
                    <image src="${reader.result}"/>
                  </div>
                </li>`;
      output.insertAdjacentHTML('beforeend', html);
    };
  }
}
```

## 実行

- railsの実行

```
bundle exec rails s
```

- `http://localhost:3000/posts/new` にアクセスする

以下のようにファイルを選択するたびにリストの下に画像が追加される。

<kbd>![Images (1)](https://user-images.githubusercontent.com/220267/241564920-57406223-a361-438e-975e-8f54f8434253.png)</kbd>


今の実装だと、フォームの内容(タイトル)でエラーがあった場合、選択した画像がすべてクリアされる。
以下のようにDirectUploadを利用してフォーム送信時にアップロードを実行して、エラー時にその画像が読み込まれるようにする。

## Postモデルにアップロードした画像をキャッシュするようにする

- 画像のIDを表す変数(signed_ids)を追加する。
- アップロードした画像を読み込むメソッド(set_image_cache)を追加
- `set_image_cache` をバリデーション時に読み込むようにする。

```ruby

class Post < ApplicationRecord
  ...

  attribute :signed_ids, default: -> { [] } # ここを追加
  before_validation :set_image_cache # ここを追加

  ...
  
   # ここを追加
  def set_image_cache
    signed_ids.each do |signed_id|
      blob = ActiveStorage::Blob.find_signed(signed_id)
      # 同じ画像を2回よみこまないようにする
      photos.attach(signed_id) unless photos.find { |image| blob.checksum == image.blob&.checksum }
    end
  end

  ...

end

```


- app/views/posts/new.html.erb のfileフィールドにdirect_uploadオプションを追加

```erb
   ...
  <div>
    <%= f.file_field :photos, 
      multiple: true,
      direct_upload: true,
      data: { "image-preview-target": "input", action: "image-preview#readImage" }
    %>
  </div>
   ...
```

- app/views/posts/new.html.erb の画像プレビュー部分にキャッシュされた画像の読み込みとsigned_idsの値を出力する

```erb

...
  <ul data-image-preview-target="output">
    <% @post.photos.each do |photo|%>
    <li>
      <%= f.hidden_field :signed_ids, multiple: true, value: photo.blob.signed_id %>
      <div>
        <%= image_tag photo, style: "width:300px" %>
      </div>
    </li>
    <% end %>
  </ul>
...
```

- javascriptのモジュールにactivestorageを追加

```
% bin/importmap pin activestorage
Pinning "activestorage" to https://ga.jspm.io/npm:activestorage@5.2.8-1/app/assets/javascripts/activestorage.js
```

- app/javascript/controllers/application.js に以下を追加

```javascript
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus   = application

// For direct upload
import * as ActiveStorage from "activestorage" // ここを追加
ActiveStorage.start()　　　　　　　　　　　 　　　　　　　　　　 　　　　　　　　　　 　　　　　　　　　　 　　　// ここを追加

export { application }

```

フォーム送信時に画像がアップロードされ、エラーが発生した場合でもsigned_idsにアップロードされた画像のキーが保存されるので、エラー発生後にそれを読み込む事ができる。