resourceを使ったプロフィール編集機能の実装

ルーティングの設定

今回、プロフィール詳細画面と編集画面へのurlは/profile, /profile/editと 現在ログインしているユーザーの詳細と編集画面だけ表示できればいいので、urlでユーザーのidを参照する必要がない。このような時に使うのが単数型のリソース routes.rbに

#routes.rb
resource :profile, only: %i[show edit update]

を記載、bundle exec rails routesでルーティングを確認すると

ターミナル
edit_profile GET    /profile/edit(.:format)                                                                  profiles#edit
                  profile GET    /profile(.:format)                                                                       profiles#show
                          PATCH  /profile(.:format)                                                                       profiles#update
                          PUT    /profile(.:format)                                                                       profiles#update

とidを使わないルーティングを生成できるし、urlヘルパーも使えるようになる ちなみに複数リソース(resources)と単数リソース(resource)もルーティング先のコントローラはデフォルトでリソース名の複数形名称である

コントローラー

bundle exec rails g controller profilesを実行してprofilesコントローラーの作成 コードは以下のようになる

#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.message.updated', item: User.model_name.human)
     else
       flash.now['danger'] = t('defaults.message.not_updated', item: User.model_name.human)
       render :edit
     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)
   end
 end

set_user@user = current_userとしてしまうとプロフィール名変更に失敗したときに、画面上で名前が変わってしまう。これは@userにはcurrent_userの保存されている場所の値(メモリ番地)が代入されており、これによりcurrent_userの値を参照しているので、@userをupdateしてしまうと@userを通してcurrent_userの値が影響を受けてしまうためである。なので、@userにはdbから取得したオブジェクトを利用している

アバターカラムの追加

今回は詳細ページにユーザーにアバター画像を追加するのでアバター画像のカラムを用意する カラム名avatarにするのでbundle exec rails g uploader Avatarを実行してアバター画像の設定ファイルを生成したらbundle exec rails g migration add_avatar_to_users avatar:stringを実行して、bundle exec rails db:migrateを実行してavatarカラムを追加 user.rbにmount_uploader :avatar, AvatarUploaderを記載して設定ファイルを適用させる 設定ファイルは以下の感じ

#avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  def default_url
    'sample.jpg'
  end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_whitelist
    %w[jpg jpeg gif png]
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end

ビュー

ユーザー詳細ページと編集ページのビューは以下のように記載

#show.html.erb
<% content_for :title do %>
  プロフィール
<% end %>

<div class="container">
  <div class="row">
      <div class="col-md-6 offset-md-1 col-lg-6 offset-lg-2">
        <h1><%= t '.title' %></h1>
      </div>
      <div class="col-md-4 offset-md-1 col-lg-2 offset-lg-1">
        <%= button_to t('.button'), {controller: 'profiles', action: 'edit' },  method: :get, class: "btn btn-success float-right" %>
      </div>
      <div class="col-md-11 offset-md-1 col-lg-9 offset-lg-2">
        <table class="table">
          <tr>
            <td>
              <strong><%= User.human_attribute_name(:email) %></strong>
            </td>
            <td><%= current_user.email %></td>
          </tr>
          <tr>
            <td>
              <strong><%= t('.name') %></strong>
            </td>
            <td><%= current_user.decorate.full_name %></td>
          </tr>
          <tr>
            <td>
              <strong><%= User.human_attribute_name(:avatar) %></strong>
            </td>
            <td><%= image_tag current_user.avatar.url,class: 'rounded-circle mr15',width: '40',height: '40' %></td>
          </tr>
        </table>
      </div>
  </div>
</div>

#edit.html.erb
<% content_for :title do %>
  プロフィール編集
<% end %>

<div class="container">
  <div class="row">
    <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
      <h1><%= t '.title' %></h1>
      <%= form_with model: current_user, url: profile_path, method: :patch, local: true do |form| %>
        <%= render 'shared/error_messages', object: form.object %>
        <div class="form-group">
          <%= form.label :email, User.human_attribute_name(:email) %>
          <%= form.text_field :email, class: "form-control" %>
        </div>
        <div class="form-group">
          <%= form.label :last_name, User.human_attribute_name(:last_name) %>
          <%= form.text_field :last_name,class: "form-control" %>
        </div>
        <div class="form-group">
          <%= form.label :first_name, User.human_attribute_name(:first_name) %>
          <%= form.text_field :first_name,class: "form-control" %>
        </div>
        <div class="form-group">
          <%= form.label :avatar, User.human_attribute_name(:avatar) %>
          <%= form.file_field :avatar,class: "form-control" %>
        </div>
        <div class="actions">
          <%= form.submit t('defaults.update'),class: "btn btn-primary" %>
        </div>
      <% end %>
    </div>
  </div>
</div>

レイアウトは

CSS · Bootstrap

Bootstrapの使い方 導入方法と基本・レスポンシブデザインを徹底解説 - WEBST8のブログ

あたりを参考に作成

参考文献

Rails のルーティング - Railsガイド