Gem carrierwave(Webアプリケーションにファイルアップロード機能を提供するGem)を使用する。
アップローダーとは
・アップローダーとはクラスオブジェクトである。
・アップローダーから生成されたオブジェクトを使って、ファイル名やファイルの保存先を取得できる。
・アップローダーにはファイルアップロードに関する設定を書ける
実装の流れ
- アップローダーを作成
- アップローダーをモデルで使用するように宣言
- アップロードの設定を各ファイルで行う
達成したいこと
▪️ユーザーのプロフィール編集機能を実装する
▪️編集画面からユーザーのアバターを登録出来る機能を実装する
▪️プロフィール詳細画面ではメールアドレス、氏名、アバター画像が表示されること
▪️プロフィール詳細画面に、編集画面へ遷移する「編集」ボタンが存在すること
▪️編集が完了してプロフィール画面に遷移した際に以下のメッセージが表示されること
・成功時・・・「ユーザーを更新しました」
・失敗時・・・「ユーザーを更新出来ませんでした」
▪️ヘッダーのアバター画像をクリックすると表示される「プロフィール」からプロフィール詳細画面に遷移できること
▪️編集が完了するとプロフィール詳細画面にリダイレクトされること
アバターと表示されたラベルをクリックすることで、画像ファイルの選択ができること
手順
- Gem carrierwaveのインストール
- rails g uploader コマンドでavatarアップローダーの作成
- アップローダーファイルapp/uploaders/avatar_uploader.rbに必要な記載(デフォルト画像、許可する画像形式)を追加
- マイグレーションを行い画像用のカラムをDBに追加
- アップローダーをカラムと関連付ける
- usersコントローラにストロングパラメータ、キャッシュの記載を追記
- ルーティングを追記(単一resource)
- プロフィールフォームのビューファイルに画像投稿用のフィールド&キャッシュ用のフィールドを追加する
- 画像投稿用カラムの項目を日本語表記にしたいので、ja.ymlファイルに翻訳を追記。
- 掲示板の部分テンプレートに、アップロードした画像のURLを指定
gemファイルに以下の記載を追加。
gem 'carrierwave', '~> 2.2.2'
ターミナルで以下を実行
docker compose run web bundle install
gemを読み込むためにサーバーを再起動するのを忘れずに!
掲示板の画像用のアップローダークラスを作成するので、board_imageを指定してアップローダークラスを作成する。
rails generate uploader Avatar
app/uploaders/avatar_uploader.rbが作成される。
作成したアップローダークラス(BoardImageUploader)では、アップロードする画像の拡張子やサイズ、保存するパスを指定することができる。
app/uploaders/avatar_uploader.rbを下記のように編集
class AvatarUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# 登録時にファイル選択をしなかった場合、デフォルト画像をimageとして渡す
def default_url
'sample.jpg'
end
# 許可する画像形式を指定
def extension_allowlist
%w[jpg jpeg png gif]
end
end
デフォルトで「storage :file」が指定されているので、アップロードした画像はpublic/配下に保存される。
保存されるディレクトリは「store_dir」で設定される。
(保存されるのは画像ファイルそのものではなく参照データ)
(アップロードされた画像ファイルが"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"の形式で保存されることを示している)
(デフォルト画像はassets/images配下に設置すること)
アップロードされた画像ファイルをGithubで管理したくないので、.gitignoreファイルに下記の記載をする
# Ignore uploaded files in development
/public/uploads
アップローダーをモデル内のカラムに取り付けたいので、まずは画像保存用のカラムをDB内に用意する。
マイグレーションファイルを作成して、マイグレーションを実行。
(カラム名はboard_image。型はString)
rails g migration add_avatar_to_users avatar:string
rails db:migrate
アップローダーをカラムと関連付ける
下記のように書くことで、レコードの保存時に画像が自動的にpublid/uploads配下に保存され、DBのavatarカラムには画像のファイル名のみが保存される。
class User < ApplicationRecord
mount_uploader :avatar, AvatarUploader #追記
単一ルーティングを記載
プロフィールページは常にログイン中のユーザーのプロフィールを参照し、他のidのユーザーのページを参照することはない。
このような場合は単数形リソースを使用する。
resource :profile, only: %i[show edit update]
edit_profile GET /profile/edit(.:format) profiles#edit
profile GET /profile(.:format) profiles#show
PATCH /profile(.:format) profiles#update
PUT /profile(.:format) profiles#update
プロフィールコントローラを作成する
$ bundle exec rails g controller profiles
profilesコントローラに以下のように記載。
class ProfilesController < ApplicationController
before_action :set_user, only: %i[edit update]
def edit; end
def update
if @user.update(user_params)
redirect_to profile_path, success: t('defaults.flash_message.updated', item: User.model_name.human)
else
flash.now['danger'] = t('defaults.flash_message.not_updated', item: User.model_name.human)
render :edit, status: :unprocessable_entity
end
end
def show; end
private
def set_user
@user = User.find(current_user.id)
end
def user_params
params.require(:user).permit(:email, :last_name, :first_name, :avatar, :avatar_cache)
end
end
ストロングパラメータにavatarカラムを記載する、「avatar_cache」の記述を記載する
def user_params
params.require(:user).permit(:email, :first_name, :last_name, :avatar, :avatar_cache)
end
def update
if @user.update(user_params)
redirect_to profile_path, success: t('defaults.flash_message.updated', item: User.model_name.human)
else
flash.now['danger'] = t('defaults.flash_message.not_updated', item: User.model_name.human)
render :edit, status: :unprocessable_entity
end
end
status: :unprocessable_entityオプションをrenderメソッドに追加することで、エラーメッセージと共に「422 Unprocessable Entity」ステータスコードをクライアントに返す。これにより、クライアントはエラーが発生したとともに、そのエラー原因を理解できる。
# BAD
def set_user
@user = current_user
end
# GOOD
def set_user
@user = User.find(current_user.id)
end
上記のBAD例で実装すると、プロフィール編集の失敗時にcurrent_userがupdateの影響を受けてしまい、画面上でプロフィールとして表示される名前が変わってしまう。
プロフィール編集画面「app/views/profiles/edit.html.erb」を他のファイルを参考にしながら作成
画像投稿用のフィールド&「(カラム名)_cache」という名前のhidden_fieldを作成するのを忘れずに。
<% content_for(:title, t('.title')) %>
<div class="container">
<div class="row">
<div class="col-md-10 col-lg-8 mx-auto">
<h1><%= t('.title') %></h1>
<%= form_with model: @user, url: profile_path, method: :put do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="mb-3">
<%= f.label :email, class: "form-label" %>
<%= f.email_field :email, class: "form-control" %>
</div>
<div class="mb-3">
<%= f.label :last_name, class: "form-label" %>
<%= f.text_field :last_name, class: "form-control" %>
</div>
<div class="mb-3">
<%= f.label :first_name, class: "form-label" %>
<%= f.text_field :first_name, class: "form-control" %>
</div>
<div class="mb-3">
<%= f.label :avatar %>
<%= f.file_field :avatar, class: 'form-control mb-3', accept: 'image/*' %>
<%= f.hidden_field :avatar_cache %>
<div class='mt-3 mb-3'>
<%= image_tag @user.avatar.url,
class: 'rounded-circle',
id: 'preview',
size: '100x100' %>
</div>
</div>
<%= f.submit class: "btn btn-primary" %>
<% end %>
<div class='text-center'>
<%= link_to t('.to_login_page'), login_path %>
</div>
</div>
</div>
</div>
accept: 'image/*は、画像ファイル全般を指定している。例えば、video/*は動画ファイル全般を指す。
プロフィールページのビューファイルを作成。
<div class="container pt-3">
<div class="row">
<div class="col-md-10 offset-md-1">
<h1 class="float-left mb-5"><%= t('.title') %></h1>
<%= link_to t('defaults.edit'), edit_profile_path, class: 'btn btn-success float-right' %>
<table class="table">
<tr>
<th scope="row"><%= User.human_attribute_name(:email) %></th>
<td><%= current_user.email %></td>
</tr>
<tr>
<th scope="row"><%= User.human_attribute_name(:full_name) %></th>
<td><%= current_user.decorate.full_name %></td>
</tr>
<tr>
<th scope="row"><%= User.human_attribute_name(:avatar) %></th>
<td><%= image_tag current_user.avatar_url, class: 'rounded-circle', size: '50x50' %></td>
</tr>
</table>
</div>
</div>
</div>
該当項目に対し、ja.ymlファイルに翻訳を入力する。
アップロードしたアバターがヘッダーに表示されるよう、app/views/shared/_header.html.erbのテンプレートに、アップロードした画像のURLを指定、リンクを入力
<li class='nav-item dropdown dropdown-slide'>
<%= link_to '#', class: 'nav-link', data: { bs_toggle: 'dropdown' }, aria: { haspopup: 'true', expanded: 'false' }, id: 'header-profile' do %>
<%= image_tag current_user.avatar_url, class: 'rounded-circle mr15', width: '40', height: '40' %>
<% end %>
<div class='dropdown-menu dropdown-menu-end'>
<div class='dropdown-item'><%= current_user.decorate.full_name %></div>
<div class='dropdown-divider'></div>
<%= link_to t('header.profile'), profile_path, class: 'dropdown-item' %>
<%= link_to t('header.logout'), logout_path, class: 'dropdown-item', data: { turbo_method: :delete } %>
</div>
</li>
↓
以下の「current_user.avatar_url」が追記部分
<%= image_tag current_user.avatar_url, class: 'rounded-circle mr15', width: '40', height: '40' %>
リンクを「profile_path」に修正
<%= link_to t('header.profile'), profile_path, class: 'dropdown-item' %>
参考にしたサイト
【Rails】CarrierWaveの使い方をざっくりまとめてみた
【Rails】 CarrierWaveチュートリアル
rails 画像アップロード機能の追加