##はじめに
railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。
のはずが、気づけばほぼ手順を示すようやくした記事になっていました\\\٩( 'ω' )و ////
##ユーザーの更新
ユーザー情報を編集する工程は新規ユーザー更新と似ているところが多い。
ビューファイルは newアクション
のような editアクション
を作成し、POSTリクエストに応答する createアクション
のようにPATCHリクエストに応答する updateアクション
を作成する。
違いとしては更新の場合はそのユーザー自身に限られるというところになる。
###編集フォーム
editアクション
を定義する。
ユーザーのid情報を取り出す@userを定義しておきたいから以下のようになる。
class UsersController < ApplicationController
.
.
.
def edit
@user = User.find(params[:id])
end
end
ビューファイル(edit.html.erb)はほとんどcreateアクションのもの(new.html.erb)と同様。
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
名前やアドレスは @user
の変数の属性情報から引き出される。
form_for(@user)
のコードは new.html.erbファイル
と同じだが、railsがユーザーが新規なのか、それともデータベースに存在する既存のユーザーであるかを、Active Recordのnew_record?論理値メソッドを使って区別するから問題ない。
ここまで完了すると、あとはheaderにリンクを追加すればいい。
<%= link_to "Settings", edit_user_path(current_user) %>
##updateアクション
次に、updateアクション
を定義するが、これはcreateアクションと非常に似通ったものになる。
class UsersController < ApplicationController
#省略
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
編集の場合はパスワードが空でも編集できるように、例外処理を加える必要がある。
編集の例外処理↓
# allow_nil: trueを追加
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
このバリーデーションを追加しても、has_secure_password
によりオブジェクト生成時に存在性を検証するようになっているため、空のパスワード (nil) が新規ユーザー登録時に有効になることはない。
##認可 (authentication)
認証 (authentication) はサイトのユーザーを識別することであり、認可 (authorization) はそのユーザーが実行可能な操作を管理すること。
このままだと、どのユーザーでもあらゆるアクションにアクセスできるため、誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまう。
よって、ユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する必要がある。
###ユーザーにログインを要求する
ユーザーにログインを要求するための logged_in_userメソッド
を定義し、before_actionメソッド
を利用してedit,updateアクションの処理が行われる前にログインを要求する。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
#省略
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# beforeアクション
# ログイン済みユーザーかどうか確認
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
###正しいユーザーを要求
ログインの要求は完了したから、次にユーザーが自分の情報だけを編集できるようにする。
そのためには current_userメソッド
を作成し、beforeフィルターからこのメソッドを呼び出すようにする。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
#省略
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# beforeアクション
# ログイン済みユーザーかどうか確認
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# 正しいユーザーかどうか確認
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless @user == current_user
end
end
上記のように、correct_userメソッド
で @user
を定義しているから、edit,updateアクションでは定義する必要はなくなる。
また、フィルタリングとして、一般的には
current_user?
という論理値を返すメソッドが作られることが多い。
# 渡されたユーザーがログイン済みユーザーであればtrueを返す
def current_user?(user)
user == current_user
end
よって、correct_userメソッド
をコンパクトにすると以下のようになる。
# 正しいユーザーかどうか確認
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
##フレンドリーフォワーディング
認可機能はこれで大体揃った。
しかし、保護されたページにアクセスしようとすると、問答無用で自分のプロフィールページに移動させられてしまう。
ユーザーがログインした後にはその編集ページにリダイレクトされるようにするのが望ましいから、フレンドリーフォワーディングをここで実装する。
ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要がある。
この動作を store_location
と redirect_back_or
の2つのメソッドを使って実装することができる。
これらのメソッドは Sessionsヘルパー
で定義する。
module SessionsHelper
.
.
.
# 記憶したURL (もしくはデフォルト値) にリダイレクト
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# アクセスしようとしたURLを覚えておく
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
end
store_locationメソッド
では、GETリクエストが送られたURLを session変数
の :forwarding_urlキー
に格納している。
定義した store_locationメソッド
を使って、早速 logged_in_user
を修正。
# ログイン済みユーザーかどうか確認
def logged_in_user
unless logged_in?
#追加
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
フォワーディング自体を実装するには、redirect_back_orメソッド
を使う。
リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトするような処理にすればいい。
session[:forwarding_url] || default
というredirect_back_orメソッド内の一文は、
「値がnilでなければsession[:forwarding_url]を代入し、そうでなければデフォルトのURLを使う。」
という意味となる。
また、次回ログインしたときに保護されたページに転送されてしまい、ブラウザを閉じるまでこれが繰り返されてしまう、という問題を解決するために、
session.delete(:forwarding_url)
という行も追加しておく。
ちなみに明示的にreturn文やメソッド内の最終行が呼び出されない限り、リダイレクトは発生しない。
よってredirect文の後にあるコードでも、そのコードは実行されることとなる。
redirect_back_orメソッド
を使い、createアクション
を修正していく。
class SessionsController < ApplicationController
.
.
.
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
#追加
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
.
.
.
end
##ユーザーの一覧
今回のセキュリティモデルでは、ユーザーの showページ
については、今後も (ログインしているかどうかに関わらず) サイトを訪れたすべてのユーザーから見えるようにしておき、ユーザーの indexページ
はログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。
まず、beforeフィルターのlogged_in_userに indexアクション
を追加して、このアクションを保護する。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def index
#全ユーザーを取得する
@users = User.all
end
<% provide(:title, 'All users') %>
<h1>All users</h1>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
次にusers_pathを用いてユーザー一覧用のリンク(users)を使い追加する。
<% if logged_in? %>
<li><%= link_to "Users", users_path %></li>
##ページネーション
ユーザーは表示できたが、このままではユーザーが増えると1つのページに大量のユーザーの表示されてしまう。
これを解決するために**ページネーション (pagination)**というものを利用する。
今回は will_paginateメソッド
を使用。
以下のように Gemfile
に追加する。
gem 'will_paginate', '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
$ bundle install
ページネーションが動作するには対応するアクションとビューに、コードを追加しなければならい。
<% provide(:title, 'All users') %>
<h1>All users</h1>
#追加
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
#追加
<%= will_paginate %>
一覧の上下にページネーションを表示。
この will_paginateメソッド
は、usersビューのコードの中から @usersオブジェクト
を自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成する。
ただこのままでは@users変数にはUser.allが定義されていて、will_paginateでは paginateメソッド
を使った結果が必要となる。
よって、アクションの方を修正する必要が出てくる。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
.
.
.
def index
#修正(@user = User.allから)
@users = User.paginate(page: params[:page])
end
.
.
.
end
これにより、ページネーションが正常に動作するようになる。
##ユーザーの削除
削除を実行できるのは管理者だけとして、まず管理ユーザー定義する。
###管理ユーザー
特権を持つ管理ユーザーを識別するために、論理値をとる admin属性
をUserモデルに追加する。
こうすると自動的に admin?メソッド
(論理値を返す) も使える。
$ rails generate migration add_admin_to_users admin:boolean
class AddAdminToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :admin, :boolean, default: false
end
end
default: false
という引数をadd_columnに追加する。
これは、デフォルトでは管理者になれないということを示す。
マイグレーションを実行する。
$ rails db:migrate
これでadmin属性が追加されたので、今回は最初のユーザーを管理者にすることとする。
User.create!(name: "Example User",
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
#追加
admin: true)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password)
end
###destroyアクション
destroyアクションでは、該当するユーザーを見つけてActive Recordの destroyメソッド
を使って削除し、最後にユーザーindexに移動する。
ユーザーを削除するためにはログインが必要になるから、:destroyアクションもlogged_in_userフィルターに追加しておく。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
.
.
.
def destroy
#2つのメソッドを連結
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
.
.
.
end
また、beforeフィルターを使ってdestroyアクションへのアクセスを制御する。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
.
.
.
private
.
.
.
# 管理者かどうか確認
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
仕上げにユーザー一覧に管理者だけに表示する削除リンクを追加する。
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
#追加
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
##さいごに
第10章では、ユーザーに関する機能(編集、一覧、削除など)とそれを実装するのに必要なアクションやメソッド(before、fixture)を見てきた。
比較的理解しやすく、安心。
次↓
https://qiita.com/jonnyjonnyj1397/items/108d088ea4274b05d8c6