たろすの技術メモ

Jot Down the Tech

ソフトウェアエンジニアのメモ書き

画像アップロードフォームにサムネを表示する

概要

Railsで画像アップロードフォームを作成している時に、アップロードする画像を確認できるように表示する方法を調べました。

準備

Active Storageをインストール

$ rails active_storage:install
$ rails db:migrate

ModelとMigrationを作成

$ rails g model Hoge

Model

今回は画像をimageという名前で定義します。

# app/models/hoge.rb
class Hoge < ApplicationRecord
  has_one_attached :image
end

Migration

今回は画像アップロードだけなので他のカラムは作成しません。

# db/migrate/20230813122050_create_hoges.rb
class CreateHoges < ActiveRecord::Migration[7.0]
  def change
    create_table :hoges do |t|

      t.timestamps
    end
  end
end

Migrationファイルを適用させます。

$ rails db:migrate

Controller

# app/controllers/hoges_controller.rb
class HogesController < ApplicationController
  def new
    @hoge = Hoge.new
  end

  def create
    @hoge = Hoge.new(hoge_params)

    if @hoge.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def hoge_params
    params.require(:hoge).permit(:image)
  end
end

Routes

Rails.application.routes.draw do
  root to: 'general#index'

  resources :hoges, only: [:new, :create]
end

View

image_preview_controller.jsというJSファイルを使用するので、file_field要素とサムネ表示部分の要素を<div data-controller='image-preview'>要素で囲みます。

# app/views/hoges/new.html.erb

<%= form_with model: @hoge, url: hoges_path, data: { turbo: false } do |f| %>
  <div data-controller='image-preview'>
    <% if @hoge.image.attached? %>
      <%= image_tag @hoge.image, id: 'img_prev' %>
    <% else %>
      <img id='img_prev'>
    <% end %>
    <br>
    <%= f.file_field :image, accept: 'image/jpeg,image/png', id: 'img_input', data: { action: 'change->image-preview#preview', target: 'image-preview.image' } %>
  </div>
  <%= f.submit '保存' %>
<% end %>

JS

ファイル選択時に実行される関数を定義します。

// app/javascript/controllers/image_preview_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  preview(event) {
    const input = event.target
    if (input.files && input.files[0]) {
      const reader = new FileReader()
      const _this = this;
      reader.onload = (e) => {
        const image = new Image();
        image.onload = function () {
          const imagePreview = _this.element.querySelector("#img_prev")
          imagePreview.src = e.target.result
        }
        image.src = e.target.result
      }
      reader.readAsDataURL(input.files[0])
    }
  }
}

これでファイルアップロードフォームにサムネを表示できるようになります。