プロフィール編集機能の実装
手順
①profileコントローラーの作成
②ルーティングの設定
③アバター画像のアップロード機能
④viewの表示
1.profileコントローラーの作成
rails g controller profilesでプロフィールコントローラーを作成し、ユーザーのプロフィール画面では詳細、編集、更新ができれば良いので、show、edit、updateアクションのみを定義。
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, :avatar_cache)
end
end
set_userメソッドで現在ログインしているユーザーのidを@userに代入している。
そして、プロフィールの編集や更新する時にはこのidが必要なのでedit、updateにbefore_actionを使って読み込ませている。
プロフィール編集対象のユーザー情報(@user)をcurrent_userのインスタンスではなく、DBから取得したオブジェクトを利用する。
# BAD
def set_user
@user = current_user
end
# GOOD
def set_user
@user = User.find(current_user.id)
end
BAD例で実装すると、プロフィール名の変更に失敗した際に、画面上でプロフィールとして表示される名前が変わってしまう。
これはプロフィール編集の失敗時にcurrent_userがupdateの影響を受けてしまうため。
一度@userに格納しているのに、どうしてcurrent_userの中身が変わってしまうのかと疑問に思ったが、それはrubyでの変数の受け渡しが値渡しで、ここでは参照の値渡しであることが原因だそう。
2.ルーティングの設定
ユーザープロフィール用のURLはidを参照しないことが望ましいため、単数系リソースを使う。
複数作成できる掲示板(Board)の仕様と異なり、ユーザーに対するプロフィールは1つしか存在せず、
また他ユーザーのプロフィールを編集することはないので、idを表示させるメリットがない。
今回は一覧(index:/profiles)を表示する必要がなく、詳細ページでidを表示させると自分が何番目に作成されたユーザーか分かってしまう。
詳細ページ(show:/profiles/:id)に他ユーザーの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
3.アバター画像のアップロード機能
この記事を参考にアバター画像のアップロード機能を実装する。
4.viewの表示
<% content_for(:title, t('.title')) %>
<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"><%= t(User.human_attribute_name(:email)) %></th>
<td><%= current_user.email %></td>
</tr>
<tr>
<th scope="row"><%= t(User.human_attribute_name(:full_name)) %></th>
<td><%= current_user.decorate.full_name %></td>
</tr>
<tr>
<th scope="row"><%= t(User.human_attribute_name(:avatar)) %></th>
<td><%= image_tag current_user.avatar.url, class: 'rounded-circle', size: '50x50' %></td>
</tr>
</table>
</div>
</div>
</div>
この記事をもとにアバター画像を表示させる。
<% content_for(:title, t('.title')) %>
<div class="container">
<div class="row">
<div class="col-md-10 offset-md-1">
<h1><%= t('.title') %></h1>
<%= form_with model: @user, url: profile_path, local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control mb-3' %>
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control mb-3' %>
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control mb-3' %>
<%= f.label :avatar %>
<%= f.file_field :avatar, onchange: 'previewFileWithId(preview)', 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>
<%= f.submit class: 'btn btn-primary' %>
<% end %>
</div>
</div>
</div>
form_withのmodelを指定しただけではprofilesコントローラーのeditアクションが実行されないので、urlでパスを指定する必要がある。
この記事をもとにアバター画像を選択するパーツを表示する。
</li>
<li class="nav-item dropdown dropdown-slide">
<a href="#" class="nav-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="header-profile">
<%= image_tag current_user.avatar_url, size: '40x40', class: 'rounded-circle mr15'%>
</a>
<div class="dropdown-menu dropdown-menu-right">
<div class="dropdown-item"><%= current_user.decorate.full_name %></div>
<div class="dropdown-divider"></div>
<%= link_to (t 'profiles.show.title'), profile_path, class: 'dropdown-item' %>
<%= link_to (t 'defaults.logout'), logout_path, class: 'dropdown-item', method: :delete %>
</div>
</li>
<tr id="comment-<%= comment.id %>">
<td style="width: 60px">
<%= image_tag comment.user.avatar_url, class: 'rounded-circle', size: '50x50' %>
</td>
<td>
<h3 class="small"><%= comment.user.decorate.full_name %></h3>