まずはuserリソースから
ユーザー登録機能
ログイン機能
ユーザー詳細表示機能
ユーザー編集機能
ユーザー削除機能
を実装していきます!
users_controller の実装
class UsersController < ApplicationController
include SessionsHelper
before_action :set_user, only: [:show,:edit,:update,:correct_user]
before_action :logged_in_user, only: [:index,:edit,:update]
before_action :correct_user, only: [:edit, :update]
def index
@users = User.all
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
log_in(@user)
redirect_to @user
else
render 'new'
end
end
def show
end
def edit
end
def update
if @user.update(user_params)
redirect_to @user
else
render
end
end
def destroy
user = User.find(params[:id])
user.destroy
end
private
def user_params
params.require(:user).permit(:nickname,:email,:password,:password_confirmation)
end
def set_user
@user = User.find(params[:id])
end
def logged_in_user
unless logged_in?
store_location
# ユーザーがいきたがってたページを記憶
flash[:danger] = "Please log in"
redirect_to login_url
end
end
def correct_user
redirect_to(root_url) unless current_user?(@user)
end
end
長いので、注目ポイントは フレンドリーフォワーディングを実装したlogged_in_userメソッドと、correct_userメソッドです
store_locationメソッドと、current_user?(@User
は sessions_helperメソッドに定義されてるので見ていきましょう
sessions_helper
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
# このコードを実行すると、ユーザーのブラウザ内
# の一時cokkiesに暗号化済みのユーザーidが自動で
# 作成されます
# この後のページでsession[:user_id]を使って
# 元通りにIDを取り出すことができる
end
def log_out
session.delete(:user_id)
@current_user = nil
end
# ユーザーのセッションを永続的にする
def remember(user)
user.remember
# rememberメソッド呼び出し
# つまりハッシュ化したトークンをDBに保存
cookies.permanent.signed[:user_id] = user.id
# 永続的に保存できるクッキーの保存期限示すため
# permanentを書く(本当は20年)
# 生のuser_idができるとだめなので
# signedで暗号化
cookies.permanent[:remember_token] = user.remember_token
# 期限はOK
# ランダムな文字列のトークンをクッキーに保存
end
# 永続セッションを破棄する
def forget(user)
user.forget
#DBのremember_digestからデータ破棄
cookies.delete(:user_id)
cookies.delete(:remember_token)
# クッキーからもユーザーの情報削除
end
# 現在ログインしているユーザーの情報を取得
def current_user
# DBの問い合わせの数を可能な限り小さくしたい
# logged_in?メソッドでも使われてるし、、、
if user_id = session[:user_id]
# セッションがある場合
# すなわちログインしてる時のみ
# sessionにアクセスした結果を変数に
# 入れておいてあとで使いまわした方が
# 早くなる
@current_user ||= User.find_by(id: user_id)
# find_byでデータベースにクエリを投げる
# ブラウザのセッションにあるuser_idをもとにUser定義
# find_byの実行結果をインスタンス変数に保存する
# ことで、1リクエスト内におけるデータベースへの
# 問い合わせは最初の一回だけになり、
# 以後の呼び出しではインスタンス変数の結果を
# 再利用する
# すでに@current_userが存在する場合って何?
# 一回current_userを実行したら、
# @current_userがあるのでそれを使ってね
# sessionのuser_idがあるということは
# 既にログインしてるといてDBにユーザーの情報があるはず。
# だからsessionのuser_idをDBでfind_byかければいい
elsif (user_id = cookies.signed[:user_id])
# sessionが張られてなかったらcookiesにあるかも
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
# nilガード
# クッキーのuser_idとremember_tokenが一致してる
log_in user
@current_user = user
end
end
end
def current_user?(user)
user && user == current_user
# nilガード
end
# ユーザーがログインしていればtrueを返す、
# その他ならfalse
def logged_in?
!current_user.nil?
# nilじゃなかったら
# すなわちログインしてたらfalseが
# 返ってくるけど、それだとif文とか
# 使う時にややこしいので、!
# で返り値の真偽値を逆にして
# trueを返すようにしてる
end
# 現在のユーザーをログアウトする
def log_out
forget(current_user)
# カレントユーザーのremember_digestを破棄
# トークン、user_idを破棄
session.delete(:user_id)
# セッションも破棄
@current_user = nil
# インスタンス変数の値を更新
end
# フレンドリーフォワーディングの処理
# 記憶したURL(もしくはデフォルト値にリダイレクト)
def redirect_back_or(default)
# これはいい命名!!
redirect_to(session[:forwarding_url] || default)
# store_locationでsession[:forwarding_url]を定義
session.delete(:forwarding_url)
# redirectできたらforwarding_urlって情報はいらないので
# 破棄しましょう
# セッション情報を保持したままにしておくと、次のログインの
# 時もこれを読み込んでおかしくなる
end
# アクセスしようとしたURLを覚えとく
def store_location
session[:forwarding_url] = request.original_url if request.get?
# request.original_url はリクエストされたurl
# それをセッションに保存
# if request.getでなぜGETリクエストだけ対応してるかと
# いうと、before_actionのlogged_in_userで
# updateに制限かかってるけど,PATCH users/:idを
# 保存する意味はない
# なぜならユーザーが元々いきたいのはeditとかだから
end
end
僕の下手くそなコメントだけじゃ意味わからないと思うので、rails tutorialやってみてください、、、笑
本当に色々勉強になりました、、、
sessions_controller
class SessionsController < ApplicationController
include SessionsHelper
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
# 受け取ったemailからfind_byでデータベースに
# 問い合わせてユーザー取得
if user && user.authenticate(params[:session][:password])
# まずそのユーザーがいるかnilガード
# いたらpasswordとauthenticateかける
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
# check boxがonの時は1になるので1の時は
# ? 以降がtrueの処理
# : 以降がfalseの処理
redirect_back_or user
# redirect_back_or メソッド呼び出し
# 引数にuser_url(user)を渡す
else
flash.now[:danger] = 'invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
# 二つのブラウザで同時ログアウトとかされたらバグが起こるので
# ログイン
redirect_to root_url
end
end
注目ポイントはnilガードです
nilガードとは、例えば
user = User.find_by(email: params[:session][:email].downcase)
とかでユーザーを取得できない場合に
if user && user.authenticate(params[:session][:password])
まずuserで本当にuserがいたのか確かめます
コンピューターは Aor B と条件式があったら左(A)から実行するので、
その特性を生かしたプログラミングです。面白いですね。
user.rb
class User < ApplicationRecord
attr_accessor :remember_token
# remember_tokenというメソッドを作成
# password,password_comfirmationみたいな
# 変数のように扱える
# u.remember_token的なことができる
has_many :drinks
before_save { self.email = email.downcase }
has_secure_password
validates :nickname, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 },allow_nil: true
# ユーザー更新時に空のパスワードでも大丈夫
# has_secure_passwordの方でpasswordの存在性を検証するから大丈夫
# 渡された文字列のハッシュ値を返す
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
# 永続的にログインするためにトークンをDBに保存
def remember
self.remember_token = User.new_token
# 何のトークンか分かりやすいから
# remember_tokenって名前を作った
update_attribute(:remember_digest,User.digest(remember_token))
# ハッシュ化したトークンをremember_digestに保存
end
# 渡されたトークンがダイジェストと一致したらtrue
# を返す
def authenticated?(remember_token)
return false if remember_digest.nil?
# 二種類のブラウザを使用してログアウトした場合
# cookiesのremember_tokenはあるけど、
# サーバー側でremember_digestをnilにしてるから
# nilに対して.is_password?とかやるとfor nil classエラーが起きちゃう
# remember_digestがnilの場合はfalseを返して、処理を止める
# 後置if文に当てはまる条件があれば処理を止めて!
# って場合はreturnとかで明示的に書くとif ~ else ~ end
# とか書かなくて済む
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest,nil)
end
end
これでユーザー機構に必要であろうメソッドが揃いました、、、!
どのメソッドをどのコントローラーや、ヘルパーに配置すべきかまだイマイチよく分かってないので、これから学んでいきたいですね
本当にこれでしっかり動くのか不安なので実際にビューを動かして次回試してみたいと思います、、、!