【Twitterクローン作成】3.ログイン機能作成

プログラミング




 

 

この記事は【Twitterクローン作成】2.ユーザ登録機能の続きとなります。

 




ログイン機能の実装

Model

ログイン機能にモデルは必要ありません。railsにそもそも内蔵されているSessionというユーザのログイン状態を持続させるための機能がデフォルトで用意されているためです。

このSession機能がなければ、ページを移動するたびにログインしなければいけないという状態になります。

Sessionについて詳しく知りたい方はこちらをどうぞ!

 

Router

config/routes.rb

Rails.application.routes.draw do
  root to: 'toppages#index'

  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'

  get 'signup', to: 'users#new'
  resources :users, only: [:index, :show, :new, :create]
end

特にここでprefixを指定する必要はないのですが、signup同様URLの見映えを良くするために指定します。

 

Controller

コントローラの作成

ターミナル

$ rails g controller sessions new create destroy

 

new

app/controllers/sessions_controller.rb

def new
end

Modelではなく、Sessionの機能を使うためにインスタンスを生成しません。

 

View

<div class="text-center">
  <h1>Log in</h1>
</div>

<div class="row">
  <div class="col-md-6 col-md-offset-3">

    <%= form_for(:session, url: login_path) do |f| %>

      <div class="form-group">
        <%= f.label :email, 'Email' %>
        <%= f.email_field :email, class: 'form-control' %>
      </div>

      <div class="form-group">
        <%= f.label :password, 'Password' %>
        <%= f.password_field :password, class: 'form-control' %>
      </div>

      <%= f.submit 'Log in', class: 'btn btn-primary btn-block' %>
    <% end %>

    <p>New user? <%= link_to 'Sign up now!', signup_path %></p>
  </div>
</div>

form_for()ですが、今まではmodelのインスタンスを()の中に代入していましたが、ログイン機能はrails内蔵のSession機能を使用するためModelが存在しないのでform_for(:session, url: login_path)を使用します。

無理に理解はする必要は特になく初めはこういうものだと暗記して後から理解する方がベターだと思います。

 

ログインフォームを作成したので、ナビバーにログインリンクを作成します

app/views/layouts/_navbar.html.erb

<header>
  <nav class="navbar navbar-inverse navbar-static-top">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Twitter</a>
      </div>
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
          <li><%= link_to 'Signup', signup_path %></li>
          <li><%= link_to 'Login', login_path %></li>
        </ul>
      </div>
    </div>
  </nav>
</header>

 

create

Controller

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  def create
    email = params[:session][:email].downcase
    password = params[:session][:password]
    if login(email, password)
      flash[:success] = 'ログインに成功しました。'
      redirect_to @user
    else
      flash.now[:danger] = 'ログインに失敗しました。'
      render 'new'
    end
  end

  def destroy
  end

  private

  def login(email, password)
    @user = User.find_by(email: email)
    if @user && @user.authenticate(password)
      # ログイン成功した場合
      session[:user_id] = @user.id
      return true
    else
      # ログイン失敗した場合
      return false
    end
  end
end

private以下のloginメソッドについて
まず、 User.find_by(email: email)ユーザ登録で登録されたemailをusersのデータベースから探しています。次にhas_secure_passwordによって使用可能になった authenticate の機能を使用してpasswordが正しいかどうかを判断します。もし、emailが見つかり、且つ&&)passwordが正しいと判断された場合、session[:user_id]に@user.idが代入され、trueが返されログインが完了します。そうでなければ、falseが返されログイン失敗となります。

次にcreate内について
email = params[:session][:email]

password = params[:session][:password]

など、Sessionの機能を使う場合はこのような形でフォームに入力された値を受け取ります。Modelを使った方法とは異なるので注意しましょう。

このようにログイン機能を記述するコントローラ内は少し複雑になっています。1回目で理解しようとすると複雑すぎて難しいという場合も多いと思うので、繰り返すうちに理解するようにしましょう。最初は「こういう形なんだ」と納得して先に進んでしまった方が効率良く学習が進みます。

Session機能について詳しく知りたい方はこちらを参考にするとわかりやすいと思います。

 

ログインできるようになったので、ログインの前後でトップページの表示を変更します

app/views/toppages/index.html.erb

<div class="center jumbotron">
  <div class="text-center">
    <h1>Twitter</h1>
    <%= link_to 'Sign up now!', signup_path, class: 'btn btn-lg btn-primary' %>
  </div>
</div>

<% if logged_in? %>
  <p>ログイン完了ユーザ: <%= current_user.name %></p>
<% else %>
  <div class="center jumbotron">
    <div class="text-center">
      <h1>Twitter</h1>
      <%= link_to 'Sign up now!', signup_path, class: 'btn btn-lg btn-primary' %>
    </div>
  </div>
<% end %>

ここではまだ logged_in? と current_user は定義されていません。

 

ログイン前後でのナビバーの表示も変更します

app/views/layouts/_navbar.html.erb

           <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
          <% if logged_in? %>
            <li><%= link_to 'Users', users_path %></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><%= current_user.name %> <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><%= link_to 'My profile', user_path(current_user) %></li>
                <li role="separator" class="divider"></li>
                <li>Logout</li>
              </ul>
            </li>
          <% else %>
            <li><%= link_to 'Signup', signup_path %></li>
            <li><%= link_to 'Login', login_path %></li>
          <% end %>
        </ul>
      </div>
トップページ同様、logged_in? と current_user は定義されていないので、sessions_helperで定義します。

 

logged_in? と current_user の定義をします

app/helpers/sessions_helper.rb

module SessionsHelper
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def logged_in?
    !!current_user
  end
end

“||=”についてはRubyマニュアルの自己代入の部分に詳しく載っていますのでこちらをどうぞ

!!current_userは ! の意味が not なのでcurrent_userに値が入っていれば、

not not trueになるので trueを返します。

逆に空なら、

not not false となり、falseを返します。

こういった記述をすることで、条件分岐を使わずしてtrueとfalseを返すことができます。

 

destroy

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  def create
    email = params[:session][:email].downcase
    password = params[:session][:password]
    if login(email, password)
      flash[:success] = 'ログインに成功しました。'
      redirect_to @user
    else
      flash.now[:danger] = 'ログインに失敗しました。'
      render 'new'
    end
  end

  def destroy
     session[:user_id] = nil
     flash[:succes] = 'ログアウトしました'
     redirect_to root_url
  end

  private

  def login(email, password)
    @user = User.find_by(email: email)
    if @user && @user.authenticate(password)
      # ログイン成功の場合
      session[:user_id] = @user.id
      return true
    else
      # ログイン失敗の場合
      return false
    end
  end
end

ログアウトする際には、session[:user_id]に nil を代入するだけでログアウトできます。

 

ナビバーにログアウトリンクを追加します

app/views/layouts/_navbar.html.erb

           <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
          <% if logged_in? %>
            <li><%= link_to 'Users', users_path %></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><%= current_user.name %> <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><%= link_to 'My profile', user_path(current_user) %></li>
                <li role="separator" class="divider"></li>
                <li><%= link_to 'Logout', logout_path, method: :delete %></li>
              </ul>
            </li>
          <% else %>
            <li><%= link_to 'Signup', signup_path %></li>
            <li><%= link_to 'Login', login_path %></li>
          <% end %>
        </ul>
      </div>

 

ログイン処理

ログインしていないユーザに見せるページ、見せないページを作ります。見せないページにログインしていないユーザが訪れた場合にログインを要求します。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

    include SessionHelper

  private

  def require_user_logged_in
    unless logged_in?
      redirect_to login_url
    end
  end
end

helperはメソッドを定義するだけでは使用できません。定義したhelperを読み込むために include SessionHelper 記述しましょう。

 

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :require_user_logged_in, only: [:index, :show]
  
  def index 
        @users = User.all.page(params[:page]) 
    end

    def show 
        @user = User.find(params[:id]) 
    end

  def create
    @user = User.new(user_params)

    if @user.save
      flash[:success] = 'ユーザを登録しました。'
      redirect_to @user
    else
      flash.now[:danger] = 'ユーザの登録に失敗しました。'
      render :new
    end
  end

  private

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

 

まとめ

ログイン機能の実装で行ったこと。

まず前提としてRubyのSession機能を使うことを理解しておきましょう。

  • ログイン/ログアウト機能構築とボタンの表示
  • ログインしていないユーザに対してのログイン要求処理

次はそれぞれのユーザがツイートをできるように投稿機能を実装していきます。

【Twitterクローン作成】4.投稿機能作成