Active Storageで画像をアップロード

Active Storageとは

ファイルアップロードを簡単に実装できるgemです。
railsの標準のgemで、rails5.2から追加されました。

scaffoldアプリの作成

rails new active_storage_app

名前はactive_storage_appとしておきます。
rails newしたら

cd active_storage_app

で移動して

rails g scaffold User name:string
rails db:migrate

scaffoldで元になるアプリを作成します。

Active Storageの導入

rails active_storage:install

このコマンドでactive_storage_blobsactive_storage_attachmentsの2つのテーブルが作成されるマイグレーションファイルが生成されるので

rails db:migrate

とします。
active_storage_blobsはアップロードしたファイルを保存するテーブルで active_storage_attachmentsは中間テーブルです。
一人のユーザーに対して一枚のアバター画像をもつ1対1の関係のとき、
モデルにhas_one_attached :カラム名を記述します。

class User < ApplicationRecord
  has_one_attached :avator
end

カラム名、と言いましたが、usersテーブルにはnameカラムしか作っていませんでした。
しかし、わざわざマイグレーションファイルを作成してavatorカラムを追加して…としなくても、
has_one_attachedを書くだけで大丈夫です。

画像サイズの設定

画像以外に、画像のサイズを設定できるようにもしたいので、

rails g migration add_avator_width_to_users

マイグレーションファイルを作成し、

class AddAvatorWidthToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :avator_width, :integer
  end
end

usersテーブルにinteger型のavator_widthカラムを追加し、

rails db:migrate

とします。
画像のサイズはvalidationで100px~500pxに制限しておきます。
また、avator_widthを空欄で登録しようとすると弾かれてしまうのでallow_nil: trueも書いておきます。

class User < ApplicationRecord
  has_one_attached :avator
  validates :avator_width,  numericality: { greater_than_or_equal_to: 100, less_than_or_equal_to: 500 }, allow_nil: true
end

コントローラのストロングパラメータでは

class UsersController < ApplicationController

 # 略

    def user_params
      params.require(:user).permit(:name, :avator, :avator_width)
    end
end

avatoravator_widthをpermitしておきます。

viewを整形

viewの方も整えていきます。
画像ファイルと画像サイズのフォームを作成し

_form.html.erb

<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class='field'>
    <%= form.label :avator %>
    <%= form.file_field :avator %>
  </div>

  <div class='field'>
    <%= form.label :avator_width %>
    <%= form.number_field :avator_width %>
  </div>


  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

ユーザー詳細ページでそれを表示させます。

show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
</p>

<% if @user.avator.attached? %>
  <%= image_tag @user.avator, width: @user.avator_width %>
<% end %>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

attached?メソッドはhas_one_attachedを設定することで使えるメソッドです。
userがavatorを持っていればtrueを、持っていなければfalseを返します。
ユーザー詳細ページを見てみると

Image from Gyazo

ちゃんと画像が表示されていますね。

画像サイズを調整

ユーザー詳細ページのアバター画像には、フォームに入力した画像サイズが適用されています。
ユーザー一覧ページにもアバターを表示させたいですが、小さいサイズに調整したいです。
画像処理ツールのImageMagickImageMagickrailsで使うためのgem、mini_magickを使います。(ImageMagickのインストール方法は省略します)
Gemfileにmini_magickがコメントアウトされていると思うので、コメントアウトを外してbundle installしたら、
ユーザー一覧ページを編集して、サイズ調整した画像を表示させましょう。

<p id="notice"><%= notice %></p>

<h1>Users</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td><%= image_tag user.avator.variant(resize: '50x50').processed if user.avator.attached? %></td>
        <td><%= link_to 'Show', user %></td>
        <td><%= link_to 'Edit', edit_user_path(user) %></td>
        <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>

variant(resize: 50x50)で画像サイズを変換しています。
processedはすでにそのサイズで保存されている画像があれば変換処理は行われず、すぐにURLが返されます。
最初の呼び出しだけ多少時間がかかりますが、それ以降の呼び出しでは時間がかかることはありません。

ではユーザー一覧ページを見てみましょう。

Image from Gyazo

画像サイズが変換されて表示されていますね。

しかしもしアプリの機能が増えて色んなページで画像を表示するとなったときに書き換えるのが大変です。
Decoratorを使って表示に関する処理を切り出してみます。

gem ActiveDecoratorを導入

Gemfile

gem 'active_decorator'

としてbundle installします。
次にでUserモデルに対するdecoratorを作成します。

rails g decorator user

このコマンドでapp/decorators/user_decorator.rbというファイルが生成されました。
このファイルにviewで呼び出すメソッドを書いていきます。

module UserDecorator
  def avator_url(version = :origin)

    command = case version
              when :small
                { resize: '50x50' }
              when :midium
                { resize: '150x150' }
              when :large
                { resize: '300x300' }
              else
                false
              end

    command ? avator.variant(command).processed : avator
  end
end

viewで呼び出す際は

<%= image_tag user.avator_url(:midium) if user.avator.attached? %>

このようにすることで引数によってサイズを変えることができます。

Image from Gyazo

<%= image_tag user.avator_url(:large) if user.avator.attached? %>

Image from Gyazo

これなら引数を変えてあげるだけですね。

最後に

実際にrails newから作ってみると理解しやすかったです。
小さなアプリだとあまり恩恵がないですが規模が大きくなるほどメンテナンス性などを考える必要がありそうです。
最後まで読んでいただきありがとうございました。

参考サイト

【Rails】 Active | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Active Storage の概要 - Railsガイド

Active StorageのVariantの指定方法いろいろ - Qiita

【Ruby on Rails】Gem「ActiveDecorator」の紹介 View向けのメソッドを定義する - ざきの学習帳(旧 zackey推し )

GitHub - amatsuda/active_decorator: ORM agnostic truly Object-Oriented view helper for Rails 4, 5, and 6

rails/activestorage at main · rails/rails · GitHub