0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsログイン機能 備忘録

Last updated at Posted at 2021-02-16

はじめに

railsチュートリアルを一周し、勉強のためアプリを作成した際の、ログイン機能の備忘録です。
内容はrailsチュートリアル6章〜8章ででてくる内容がほとんどになります。

仕様

  • ログイン機能にdeviseは使用しない
  • railsチュートリアル6章〜8章の内容
  • CSSにbootstrap使用
  • ruby 3.0.0 Rails 6.0.3.4

usersテーブル

カラム名
id integer
name string
email string
password_digest string

integer:整数
string:文字列が255字以内

バリデーション

  • nameは空白禁止、50文字以内
  • emailは空白禁止、255文字以内、かぶりなし、フォーマットはxxx@xxx.xxx
  • passwordは空白禁止、6文字以上

usersコントローラー、モデル作成

コントローラーは複数形、モデルは単数形で表記(railsの慣習)

コントローラーの作成

ターミナル
$ rails generate controller Users  

モデルの作成

ターミナル
$ rails generate model User name:string email:string
$ rails db:migrate

user.rbに以下を追記

app/models/user.rb
  validates :name, presence: true, #空白禁止
                   length: { maximum: 50 } #50文字以内
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i #大文字小文字無視のxxx@xxx.xxxの正規表現
  validates :email, presence: true, #空白禁止
                    length: { maximum: 255 }, #255文字以内
                    format: { with: VALID_EMAIL_REGEX }, #フォーマット指定
                    uniqueness:true #一意性

パスワードのハッシュ化

パスワードをそのままDBに保存するのはセキュリティの観点よりNG
そのため、ハッシュ化しDBに保存できる状態にする
ハッシュ化:不可逆性の文字列に変換すること

usersテーブルにpassword_digest(string型)を追加
マイグレーション名は自由に設定可能だが、to_usersにすることで、usersテーブルを参照
(今回のマイグレーション名:add_password_digest_to_users)

ターミナル
$ rails generate migration add_password_digest_to_users password_digest:string
$ rails db:migrate

ハッシュ関数であるbcryptを使用するため、gemを追加

Gemfile
gem 'bcrypt' #追記
ターミナル
$ bundle install

下記3点の機能を持たせるためにuser.rbにhas_secure_password追記

  • セキュアにハッシュ化されたパスワードをpassword_digestに保存 (セキュア:安全に)
  • 仮想的にpasswordとpassword_confirmationが使える(存在性と値が一致するかのバリデーションも追加)
  • authenticateメソッド追加(引数とパスワードの比較 一致→Userオブジェクト 間違い→false)

また、パスワードのバリデーションも追記
※has_secure_passwordがもつバリデーションは新規製作時のみ適応、更新時は不適応のため

app/models/user.rb
has_secure_password
validates :password, presence: true, length: { minimum: 6 } #空白禁止かつ6文字以上

コンソールにてハッシュ化されているか確認

ターミナル
$ rails console --sandbox
user1 = User.create(name: "qqq", email: "q@q.q", password:"password", password_confirmation: "password")
user2 = User.create(name: "www", email: "w@w.w", password:"password", password_confirmation: "password")
user1.password_digest
=> "$2a$12$poMxe7FIiQpZISIO51XanuJgDkwYD45hCKZHt0.M0Qx1vW5jcFYm."
user2.password_digest
=> "$2a$12$Ld2mRJgw3LVItTgfn2k8LuiRPH.RHkV71.wbfzfdxDT2p5pFgvkva"

パスワードは同じだが、password_digestの値が違うのは、
簡単に言えば、bcryptが引数+ランダムな値を元に文字列を生成しているから

ユーザー情報の表示

/users/1を開くことで、id=1のユーザー情報(名前、eメール)を画面表示出来るようにする

コンソール上でユーザーの追加(現段階ではユーザー情報が無いため)

ターミナル
$ rails console
User.create(name: "テスト太郎", email: "test@test.com", password:"password", password_confirmation: "password")
=> #<User id: 1, name: "テスト太郎", email: "test@test.com", created_at: "2021-02-14 13:11:00", updated_at: "2021-02-14 13:11:00", password_digest: [FILTERED]>

RESTに基づくルーティングの作成

config/routes.rb
resources :users
# resources :users, only: [:index, :show] ←必要なものだけを指定する場合
# resources :users, expect: [:index, :show] ←不必要なものを指定する場合

個人ページの作成、表示はnameとemailのみ

app/views/users/show.html.erb
名前:<%= @user.name %><br>
Eメール:<%= @user.email %>

showコントローラの記入
User.find(params[:id])と記入することで、送られてきたurl(/users/1)よりid=1を取得できる
(resourcesを記入したことで、/users/:idのurlが送られてきた場合、showを実行する設定のため)

app/controllers/users_controller.rb
def show
  @user = User.find(params[:id])
end

以上で、/users/1にid=1の名前とEメールが表示される

登録フォーム作成

/users/newを開くことで、新規ユーザー登録が出来る状態にする

新規登録フォームには、新規Userオブジェクトを与えないといけないため
newコントローラで与えれるように記入する

app/controllers/users_controller.rb
def new
  @user = User.new
end

form_withヘルパーメソッドを用いて、投稿フォームを作成する

app/views/users/new.html.erb
<div class="row">
  <div class="col-md-6 col-md-offset-3"> <!--12分割の4−9部分で描画-->
    <%= form_with(model: @user, local: true) do |f| %>

    <%= f.label :name, "名前" %>
    <%= f.text_field :name, class: 'form-control' %>

    <%= f.label :email, "Eメール" %>
    <%= f.text_field :email, class: 'form-control' %>

    <%= f.label :password, "パスワード" %>
    <%= f.password_field :password, class: 'form-control' %>

    <%= f.label :password_confirmation, "パスワードの再確認" %>
    <%= f.password_field :password_confirmation, class: 'form-control' %>

    <%= f.submit "ユーザー登録", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

フォームで記入した情報をDBに登録する
user_params:フォームから送られてきた情報以外の情報を受け取らない処理
(外部から隠したいため、private内に記載)

app/controllers/users_controller.rb
  def create
    @user = User.new(user_params) #@userにフォームで記入したユーザー情報を与える
    if @user.save #saveの実行
      redirect_to @user #save成功時user_url(@user)にいく
    else
      render 'new'#save失敗時、フォーム画面を再描画する
    end
  end

  private
  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end

登録失敗時、フォームにエラー文を日本語で表示させる

各フィールド毎のエラー文を、フィールド近くに表示させる状態にする

日本語化に必要なgemのインストール

Gemfile
gem 'rails-i18n'
ターミナル
$ bundle install

config/application.rbに以下を追記

config/application.rb
config.i18n.default_locale = :jack_o_lantern: 

カラム名はgemを入れただけでは日本語にはならないため、
config/localesにja.ymlを新規作成し、以下を記載

ターミナル
$ touch config/locales/ja.yml
config/locales/ja.yml
ja:
  activerecord:
    attributes:
      user:
        name: 名前
        email: Eメール
        password: パスワード
        password_confirmation: パスワードの再確認

new.html.erbにエラーメッセージを追記
(エラーメッセージはテンプレート化 f.objectで@userを取得可能)

app/views/users/new.html.erb
<%= f.label :name, "名前" %>
<%= render 'shared/error_messages', object: f.object, column: :name %> <!--追記-->
<%= f.text_field :name, class: 'form-control' %>
app/views/shared/_error_messages.html.erb
<% if object.errors.include?(column) %>
  <span style="color: red;"><%= object.errors.full_messages_for(column).first %></span>
<% end %>

登録成功時、成功したことを表示させる

createアクションにsave成功時のflashを追記する

app/controllers/users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "新規登録が成功しました" #追記
      redirect_to @user 
    else
      render 'new'
    end
  end

flashメッセージが存在する際場合にどのページでも表示されるように、
application.html.erbに以下を記載

app/views/layouts/application.html.erb
  <main>
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>
      <%= yield %>
    </div>
  </main>

ログインページの作成

新たにsessioonsコントローラ、newアクションを生成する

コンソール
$ rails generate controller Sessions new

ルーティングの追加
ログインフォーム→new ログイン処理→create ログアウト処理→destroy

config/routes.rb
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'

/loginページの作成
sessionはモデルを持たないため、送信先urlとscopeをform_withで与えている

app/views/sessions/new.html.erb
<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

    <%= f.label :email, "Eメール" %>
    <%= f.email_field :email, class: 'form-control' %>

    <%= f.label :password, "パスワード" %>
    <%= f.password_field :password, class: 'form-control' %>

    <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

コントローラ記入前に、以下の機能を使い回せるようにSessionsヘルパーに記入
①sessionヘルパーを用いて、渡されたユーザーを与えることでログイン状態にするメソッド
 ※sessionを用いると、一時cookiesに暗号化された状態で保存可能
②session[:user_id]にuser.idが保存されたいた場合、そのユーザーを返すメソッド
※1リクエストに2回以上このメソッドが呼び出されたとき、
 データベースの問い合わせが2回以上発生してしまうため、
 インスタンス変数を用いて2回目以降はインスタンス変数の中身を参照されるようにする

app/helpers/sessions_helper.rb
module SessionsHelper
  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] =user.id
  end
  # 現在ログインユーザーがいる場合、ユーザーを返す
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
     #@current_user = @current_user || User.find_by(id: session[:user_id])と同義
     #find(session[:user_id])では、データがない場合に例外を返してしまう(find_byはnilを返す)
    end
  end

コントローラはそのままではヘルパーは使えないため、以下を記述する
(viewファイルで使う場合は記載不要、modelで使いたい場合は記載必要)

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper
end

createコントローラの記入
sessionモデルが無いことよりエラーメッセージが生成されないため、
flash.nowを用いて、ログイン失敗を表現する
(render→flash.now ※現在のリクエストまで残る redirect_to→flash ※次のリクエストまで残る)

app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email])
                   #{ session: { password: "xxx", email: "xxx" } }のemailを取得
    if user && user.authenticate(params[:session][:password])
      #ユーザーがデータベースに存在し、パスワードの認証もok時の処理
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Eメールとパスワードの組み合わせが無効です'
      render 'new'
    end
  end

ログイン時と非ログイン時で表示を変更する方法

header部リンク先などを、ログインしている状態か否かで表示内容を変更できる状態にする

Sessionsヘルパーに、current_userがnilではない事で、ログイン状態か否かを判断するメソッドを追加

app/helpers/sessions_helper.rb
  # ユーザーがログイン状態か否かの確認
  def logged_in?
    !current_user.nil?
  end
end

viewファイルなどにifを用いてlogged_in?を使用し、表示内容を変更

app/views/layouts/application.html.erb
<% if logged_in? %>
  <li><%= link_to "Log out", logout_path, method: :delete %></li>
<% else %>
  <li><%= link_to "Log in", login_path %></li>
<% end %>

ユーザー登録時にログイン状態にする

Sessionsヘルパーに記入したlog_in(user)をuserコントローラのcreateアクションに追記

app/controllers/users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      log_in @user #追加
      flash[:success] = "新規登録が成功しました"
      redirect_to @user #user_url(@user)→@userで表現している
    else
      render 'new'
    end
  end

ログアウト処理

session(:user_id)の中身を消すことでログアウトを行える状態にする

Sessionヘルパーにsession(:user_id)をnilにするlog_outメソッドを記載

app/helpers/sessions_helper.rb
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end

コントローラのdestroyアクションにlog_outを記載
ログアウト後はルートパスへ飛ぶようにする

app/controllers/sessions_controller.rb
  def destroy
    log_out
    redirect_to root_url
  end
0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?