#目次
・はじめに
・そもそもなぜログイン機能は必要なの?
・実際に作ってみる
・modelを作成
・カラムを追加
・データを入れる
・viewを作成
・controllerの実装
・まとめ
#はじめに
多くのウェブアプリケーションにはログイン機能が実装されていますが、皆さんはログイン機能を実装したことはあるでしょうか?また、実装する際にはどのように実装していますか?
deviseという非常に便利なログイン機能実装のためのgemがあるので、自分で実装したことはないという方も多いかもしれません。実際に私も今までdeviseに頼りっぱなしでログインの仕組みについて深く考えたことはありませんでした。
確かに、gemを使いこなせれば開発のスピードが格段に上がるので利用しない手はないでしょう。しかし、railsについてきちんと理解する前にあまりにgemに頼りすぎてしまうと自分の技術も向上していかないというのもまた事実だと思います。
そこで今回は普段何気なく使っているログイン機能はどのような仕組みで動いているのかを考え、gemに頼らず自力で実装してみようと思います。
#そもそもなぜログイン機能は必要なの?
そもそもの話なのですが、ログイン機能はどうして必要なのでしょうか?もし不要なのであればわざわざ時間をかけて実装するのは面倒なので、まずはユーザーにログインをさせる理由を考えようと思います。
ログインの必要性を考えるために、逆にログイン機能がなかったらどうなるか想像してみてください。あるユーザーがサイトを閲覧していて、そのユーザーがマイページを確認したいという状況を考えます。そのユーザーのマイページを表示させるためには、当然ながらそのユーザーの情報を引っ張ってくるために、今まさにサイトを閲覧しているその人のidを取得する必要があります。ですが、そのidはどうすれば取得できるのでしょうか。ここに来て初めてdeviseのcurrent_userのありがたみを感じると思います。
HTTPというプロトコルはステートレスなプロトコルと言われているように今までのリクエストの情報を全く利用できないため、こちら側で工夫してあげないと今誰がサイトを閲覧しているのかということがわからないのです。その問題を解決するためにあるのがセッションというもので、情報をユーザーのパソコンのウェブブラウザに記憶してくれます。これを使って今閲覧のユーザのidを判別するのですが、このような仕組みがログインになります。
#実際に作ってみる
色々聞いたり調べたりしても自分で手を動かしてみないと理解もしにくいと思うので、早速コードを記述していきましょう!今回はとりあえずログイン機能の基本を理解することが目標なので、すでに登録してあるユーザーが名前とパスワードでログインをすることを考えます。わかりやすくするためにパスワードの暗号化などは割愛します。
#modelを作成
まずは下準備としてUserモデルを作成しましょう。
$rails g model User
Running via Spring preloader in process 7229
invoke active_record
create db/migrate/20181212114133_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
#カラムを追加
Userモデルは作られましたので、usersテーブルにユーザーの名前を入れるnameカラムとパスワードを入れるpasswordカラムをそれぞれinteger型で追加しましょう。
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :name
t.string :password
t.timestamps
end
end
end
$rails db:migrate
#データを入れる
今回はログイン機能を作るのが目的のためユーザーの登録画面は作りません。ただ、ユーザーの登録情報がないとログインが成立しないので、データベースにユーザーの情報を直接書き込みましょう。ターミナルで下記のコマンドを打ち込んでください。
$ rails c
次に下記のコマンドを打ち込んで、名前:userA
パスワード:AAAAAA
のuserAさんを作りましょう。
irb(main):001:0> User.create(name:"userA",password:"AAAAAA")
このように表示されれば成功です。
(0.0ms) begin transaction
User Create (3.5ms) INSERT INTO "users" ("name", "password", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "userA"], ["password", "AAAAAA"], ["created_at", "2018-12-13 09:43:58.698249"], ["updated_at", "2018-12-13 09:43:58.698249"]]
(3.2ms) commit transaction
=> #<User id: 1, name: "userA", password: "AAAAAA", created_at: "2018-12-13 09:43:58", updated_at: "2018-12-13 09:43:58">
同様にして
名前:userB
パスワード:BBBBBB
のuserBさん
名前:userC
パスワード:CCCCCC
のuserCさん
も作っておきましょう。作り終えたら下記のコマンドを打ってデータベースを確認してみましょう。
irb(main):005:0> User.all
このように表示されればしっかりとデータが作られています。
User Load (1.7ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "userA", password: "AAAAAA", created_at: "2018-12-13 09:43:58", updated_at: "2018-12-13 09:43:58">, #<User id: 2, name: "userB", password: "BBBBBB", created_at: "2018-12-13 09:56:31", updated_at: "2018-12-13 09:56:31">, #<User id: 3, name: "userC", password: "CCCCCC", created_at: "2018-12-13 09:56:48", updated_at: "2018-12-13 09:56:48">]>
#viewを作成
次にview画面を作っていきましょう。今回作るのは、すでに登録済みのユーザーがログインを行うためのログイン画面と、正しくログインできた時に飛ぶユーザーのshow画面です。ユーザビリティは全くのゼロですがお許しください。
まずはログイン画面から。
<h1>ここでログインする</h1>
<%= form_for(:session, url: login_path) do |f| %>
name<%= f.text_field :name %>
email<%= f.text_field :email %>
<%= f.submit 'ログイン' %>
<% end %>
2行目のform_for
で少し見慣れない書き方が出てきました。:session, url: login_path
という記述があります。今までであれば:session
の箇所に:@user
のようなインスタンス変数を記述していましたが,セッションにはモデルがないため、そのままではサブミットした後にどのようなURLにPOSTするのかを自動で決めることができません。なのでurl: login_path
のように明記してあげることで、URLを明確に示しています。
次にshow画面
<h1>ログイン成功後に飛ぶユーザのshow画面</h1>
<%= @current_user.name %>
<%= @current_user.email %>
<%= link_to 'ログアウト', logout_path, method: :delete %>
2行目と3行目に@current_user
という記述がありますが、これは後でcontrollerで定義していきます。
一番最後の行にログアウトをするためのリンクも用意しておきましたが、これについても後ほど機能を実装していきます。
#controllerの実装
まずはログイン後に表示するshow画面から。画面を表示するだけなので特に記述することはありません。
class UsersController < ApplicationController
def show
end
end
次にapplication_controller
です。
class ApplicationController < ActionController::Base
before_action :require_sign_in
before_action :current_user
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def require_sign_in
redirect_to login_path unless current_user
end
end
上から順に解説していきます。
before_action :require_sign_in
は何をしているのかというと、もしユーザーがサインインしていない場合にはlogin_path
にリダイレクトさせています。unless
という書き方は、そのあとに書いたものがnil、もしくはfalseだった場合に実行するというものです。
次にbefore_action :current_user
ですが、これはsessions_controller
でも出てくるのでそちらで解説します。
では、sessions_controller
をみてみましょう。
class SessionsController < ApplicationController
skip_before_action :require_sign_in, only: [:login]
skip_before_action :current_user, only: [:login]
def login
if params[:session]
@user = User.find_by(name: params[:session][:name], email: params[:session][:email])
if @user
session[:user_id] = @user.id
redirect_to user_path(session[:user_id])
else
render("sessions/login")
end
end
end
def destroy
session.delete(:user_id)
@current_user = nil
redirect_to login_path
end
private
def session_params
params.require(:session).permit(:name,:email)
end
end
こちらも上からみていきます。
1行目のskip_before_action :require_sign_in, only: [:login]
についてですが、application_controller
で require_sign_in
が定義されているため、本来ならば全てのアクションの前でrequire_sign_in
が実行されてしまいます。ただ、ログインをするページに関してはこれからログインをするところなので、サインインを要求する必要がありません。なのでlogin
アクションについてのみrequire_sign_in
を行わなくていいよ、と言っているのがこの記述です。
同様にskip_before_action :current_user, only: [:login]
も、login
の前に関しては実行しなくて良いということを言っています。では、current_user
では何を行なっているのでしょうか。先ほど解説を飛ばしていたので、このタイミングで解説します。
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
この中でまず気になるのが||=
という記述だと思います。これは@current_user
というインスタンス変数がもしnilもしくはfalseだった場合に右辺を代入するというもので、ここでは今ログインしているユーザーのidを持っているユーザーを代入しています。session[:user_id]
への代入はlogin
アクションで行なっているため、後ほど見ていきます。
また、ここでfind
ではなくわざわざfind_by
を使っている理由ですが、これはもしもsession[:user_id]
にまだ何も値が代入されていなかった場合にエラーとなるのを防ぐ為です。
これで@current_user
にログイン中のユーザーを代入することができました。
次にlogin
アクションを見ていきましょう。一つ目のif文ですが、これでパラメータが送られているか判定しています。@user
には、送られてきた名前とパスワードの組み合わせが一致するユーザがいた場合に、そのユーザー代入しています。
二つ目のif文で、先ほどの@user
にユーザーが代入されているかを判定しています。代入されていた場合には、:session[user_id]
にそのユーザーのidを代入しています。これらの条件を満たしていない場合には、もう一度ログイン画面を表示するようにしています。
最後にdestroy
アクションですが、このアクションでログアウトの機能を実装しています。session.delete(:user_id)
の部分でセッションを削除し、@current_user=nil
で@current_user
の中身を空にしています。
#まとめ
以上で、非常に簡単にではありますがログイン・ログアウト機能の実装ができました。このままではセキュリティ面での不安がかなり残るのですが、今回はひとまずここまでとします。最後まで読んでいただきありがとうございました!