LoginSignup
0
0

【Rails】プロフィール編集機能の実装

Last updated at Posted at 2024-02-10

Gem carrierwave(Webアプリケーションにファイルアップロード機能を提供するGem)を使用する。

アップローダーとは

・アップローダーとはクラスオブジェクトである。
・アップローダーから生成されたオブジェクトを使って、ファイル名やファイルの保存先を取得できる。
・アップローダーにはファイルアップロードに関する設定を書ける

実装の流れ

  1. アップローダーを作成
  2. アップローダーをモデルで使用するように宣言
  3. アップロードの設定を各ファイルで行う

達成したいこと

▪️ユーザーのプロフィール編集機能を実装する
▪️編集画面からユーザーのアバターを登録出来る機能を実装する
▪️プロフィール詳細画面ではメールアドレス、氏名、アバター画像が表示されること
▪️プロフィール詳細画面に、編集画面へ遷移する「編集」ボタンが存在すること
▪️編集が完了してプロフィール画面に遷移した際に以下のメッセージが表示されること
  ・成功時・・・「ユーザーを更新しました」
  ・失敗時・・・「ユーザーを更新出来ませんでした」
▪️ヘッダーのアバター画像をクリックすると表示される「プロフィール」からプロフィール詳細画面に遷移できること
▪️編集が完了するとプロフィール詳細画面にリダイレクトされること
アバターと表示されたラベルをクリックすることで、画像ファイルの選択ができること

手順

  1. Gem carrierwaveのインストール
  2. rails g uploader コマンドでavatarアップローダーの作成
  3. アップローダーファイルapp/uploaders/avatar_uploader.rbに必要な記載(デフォルト画像、許可する画像形式)を追加
  4. マイグレーションを行い画像用のカラムをDBに追加
  5. アップローダーをカラムと関連付ける
  6. usersコントローラにストロングパラメータ、キャッシュの記載を追記
  7. ルーティングを追記(単一resource)
  8. プロフィールフォームのビューファイルに画像投稿用のフィールド&キャッシュ用のフィールドを追加する
  9. 画像投稿用カラムの項目を日本語表記にしたいので、ja.ymlファイルに翻訳を追記。
  10. 掲示板の部分テンプレートに、アップロードした画像のURLを指定

gemファイルに以下の記載を追加。

Gemfile
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を下記のように編集

app/uploaders/board_image_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ファイルに下記の記載をする

.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カラムには画像のファイル名のみが保存される。

app/models/board.rb
class User < ApplicationRecord
  mount_uploader :avatar, AvatarUploader #追記

単一ルーティングを記載

プロフィールページは常にログイン中のユーザーのプロフィールを参照し、他のidのユーザーのページを参照することはない。
このような場合は単数形リソースを使用する。

config/routes.rb
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コントローラに以下のように記載。

app/controllers/profiles_controller.rb
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を作成するのを忘れずに。

app/views/profiles/edit.html.erb
<% 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 画像アップロード機能の追加

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0