LoginSignup
0
0

More than 3 years have passed since last update.

個人アプリ開発日記 #3

Posted at

まずは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

これでユーザー機構に必要であろうメソッドが揃いました、、、!

どのメソッドをどのコントローラーや、ヘルパーに配置すべきかまだイマイチよく分かってないので、これから学んでいきたいですね

本当にこれでしっかり動くのか不安なので実際にビューを動かして次回試してみたいと思います、、、!

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