個人アプリ開発日記　#3

# まずはuserリソースから

ユーザー登録機能
ログイン機能
ユーザー詳細表示機能
ユーザー編集機能
ユーザー削除機能

を実装していきます！

## users_controller　の実装

```Ruby
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
```Ruby
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
```Ruby
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
```Ruby
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

```

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

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

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