【Active Storage】単数画像→複数画像への移行方法
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
画像アップロード機能で少しだけ詰まったところがあるので、本記事ではそれを紹介します。
詰まった箇所は「Active Storageによる複数画像のアップロード方」です。画像アップロード方法として、自分は今までRUNTEQの基礎編課題でCarriWaveを学習していました。調べてみると、Active StorageはRails5.2よりgemではなく公式にファイルアップロード機能として追加されたようです。
今回使用するアプリはいわゆる「パルビ」と呼ばれる書籍のActive Stoageの項目を実装した状態から始めます!
パーフェクト Ruby on Rails 【増補改訂版】 (Perfect series)
現在のファイルの状況
アプリケーションはscaffoldを使用して作成されています。ファイルアップロードの機能がある箇所はユーザー作成ページです。
こちらに画像をアップしてユーザーを作成すると、ユーザー詳細ページにて画像が表示されます。
詳しくコードを見ていきます。
まずUserモデルにはhas_one_attached :portrait
とアソシエーションが指定されています。
# app/models/user.rb class User < ApplicationRecord has_one_attached :portrait end
この:portrait
は実際にはUserモデルのカラムにはありません。これはActive Storageがポリモーフィックなアソシエーションを採用しているからです。ポリモーフィックなアソシエーションは説明が大変複雑なので今後別の記事で説明する予定です。現状は下記記事を自分は参考にしているので、気になる方は下記を参照してください。
Railsのポリモーフィック関連とはなんなのか - Qiita
ここでの理解は、「ユーザーとは別のモデルであるActiveStorage::Blobが:portraitを持っており、ユーザーは:portraitと関連づけられている」という理解で大丈夫かなと思います。
ユーザー作成画面の名前入力とファイルアップロード箇所のerbの現状です。
<!-- app/views/users/_form.html.erb --> <div class="field"> <%= form.label :name %> <%= form.text_field :name %> </div> <div class="field"> <%= form.label :portrait %> <%= form.file_field :portrait %> </div>
こちらで入力されたname
とアップロードしたportrait
がパラメーターとして渡されます。user_paramsで
ストロングパラメーターを介して値を取得しています。ユーザー作成であるcreate
アクションでそれらの値を受け取って保存できればユーザー詳細ページをレンダリングします。
# app/controllers/users_controller.rb def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: "User was successfully created." } format.json { render :show, status: :created, location: @user } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @user.errors, status: :unprocessable_entity } end end end ・ ・ ・ def user_params params.require(:user).permit(:name, :portrait) end
ユーザー詳細ページです。image_tag
で画像を表示していることがわかります。
<p id="notice"><%= notice %></p> <p> <strong>Name:</strong> <%= @user.name %> </p> <p> <strong>Portrait:</strong> <%= image_tag @user.portrait.variant(resize_to_limit: [100, 100]) %> </p> <%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>
variant(resize_to_limit: [100, 100])
のメソッドはデフォルトでgemにコメントアウトされた状態で記載されているimage-processing
をインストールすることで使用できます。これにより画像のサイズを指定することができます。
# Gemfile gem 'image_processing', '~> 1.2'
以上ががざっくりとした現状のファイルの状態です。Active Storageのファイルアップロード機能は比較的簡単に実装することができます。しかし、現在のファイル状態では画像を一つしかアップロードすることができません。そこで、現在のファイル状態を変更し、複数の画像をアップロードできるように実装したいと思います。
複数の画像をアップロードできるように変更
モデルの編集
まず、複数のファイルを格納することができるように、モデルを変更します。Active Storageではファイルをそのモデルが所有する関係性をかなり直感的に表しています。has_one_attached :portrait
とあるように、Userモデルがportrait
(1つの画像)をアタッチメントとして所有していることがわかります。
では、複数の画像をアップロードする場合は?
実は、has_many_attached :portraits
と変更をするだけです。
# app/models/user.rb class User < ApplicationRecord # has_one_attached :portrait has_many_attached :portraits end
これにより、「Userモデルがportraits
(複数の画像)をアタッチメントとして所有している」ことを示しています。
アップロード画面を編集
次に、パラメーターを渡すビューを変更します。
これも直感的で、portraitをportraitsに変更します。
<div class="field"> <%= form.label :portrait %> <!-- <%= form.file_field :portrait %> --> <%= form.file_field :portraits %> </div>
ただ、これだけでは複数のファイルをパラメーターとして渡すことはできるようにしたけど、複数のファイルをアップできるようにHTMLが対応されていません。対応するためにはオプションにmultiple: true
を追記します。
<div class="field"> <!-- <%= form.label :portrait %> --> <%= form.label :portraits %> <!-- <%= form.file_field :portrait %> --> <%= form.file_field :portraits, multiple: true %> </div>
ストロングパラメーターを編集
次はストロングパラメーターです。
もうどこを変更すればいいかわかると思います。「portraitをportraitsに変更」ですね。
def user_params # params.require(:user).permit(:name, :portrait) params.require(:user).permit(:name, :portraits) end
と私も最初は思ったのですが、実は違います!!!!
正しい実装は以下のとおりです。
def user_params # params.require(:user).permit(:name, :portrait) # params.require(:user).permit(:name, :portraits) params.require(:user).permit(:name, portraits: []) end
これはアップロードされるファイルが複数であることに対応するためです。空の配列を指定しないと複数のファイルを格納する器がないということです。
詳細ページを編集
最後にユーザー詳細ページです。現状image_tag
は1つしかないので複数の画像を表示できません。複数画像を表示するために、アップロードされたportraits
の数だけimage_tag
が生成されるように繰り返し処理をします。
<p> <strong>Portrait:</strong> <% @user.portraits.each do |portrait| %> <%= image_tag portrait.variant(resize_to_limit: [100, 100]) %> <% end %> </p>
これで複数の画像が表示するように実装することができました!!
終わりに
単数から複数の画像をアップロードすること自体は難しくありません!単数形を複数形に変更するみたいな作業ですからね。
ただ、大きなアプリケーションともなると、様々なファイルで画像のカラムが使われていたら大変です。例えば、画像のバリデーションをするデコレイターがあったとすると、それが単数ようのファイルで定義されていた場合は、複数のファイルを持つ配列ではエラーが起きてしまいます。なのでアプリケーションを作成する際は、なるべく単数か複数かを明確な理由を持って決めておくと、後々変更するという手間を減らすことができるでしょう。
以上、大ちゃんの駆け出し技術ブログでした!