36
18

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 5 years have passed since last update.

DMM WEBCAMPAdvent Calendar 2018

Day 18

【rails】gemを使わずにログイン機能を実装!

Last updated at Posted at 2018-12-18

#目次
・はじめに
・そもそもなぜログイン機能は必要なの?
・実際に作ってみる
・modelを作成
・カラムを追加
・データを入れる
・viewを作成
・controllerの実装
・まとめ
#はじめに
多くのウェブアプリケーションにはログイン機能が実装されていますが、皆さんはログイン機能を実装したことはあるでしょうか?また、実装する際にはどのように実装していますか?

deviseという非常に便利なログイン機能実装のためのgemがあるので、自分で実装したことはないという方も多いかもしれません。実際に私も今までdeviseに頼りっぱなしでログインの仕組みについて深く考えたことはありませんでした。

確かに、gemを使いこなせれば開発のスピードが格段に上がるので利用しない手はないでしょう。しかし、railsについてきちんと理解する前にあまりにgemに頼りすぎてしまうと自分の技術も向上していかないというのもまた事実だと思います。

そこで今回は普段何気なく使っているログイン機能はどのような仕組みで動いているのかを考え、gemに頼らず自力で実装してみようと思います。

#そもそもなぜログイン機能は必要なの?
そもそもの話なのですが、ログイン機能はどうして必要なのでしょうか?もし不要なのであればわざわざ時間をかけて実装するのは面倒なので、まずはユーザーにログインをさせる理由を考えようと思います。

ログインの必要性を考えるために、逆にログイン機能がなかったらどうなるか想像してみてください。あるユーザーがサイトを閲覧していて、そのユーザーがマイページを確認したいという状況を考えます。そのユーザーのマイページを表示させるためには、当然ながらそのユーザーの情報を引っ張ってくるために、今まさにサイトを閲覧しているその人のidを取得する必要があります。ですが、そのidはどうすれば取得できるのでしょうか。ここに来て初めてdeviseのcurrent_userのありがたみを感じると思います。

HTTPというプロトコルはステートレスなプロトコルと言われているように今までのリクエストの情報を全く利用できないため、こちら側で工夫してあげないと今誰がサイトを閲覧しているのかということがわからないのです。その問題を解決するためにあるのがセッションというもので、情報をユーザーのパソコンのウェブブラウザに記憶してくれます。これを使って今閲覧のユーザのidを判別するのですが、このような仕組みがログインになります。

#実際に作ってみる
色々聞いたり調べたりしても自分で手を動かしてみないと理解もしにくいと思うので、早速コードを記述していきましょう!今回はとりあえずログイン機能の基本を理解することが目標なので、すでに登録してあるユーザーが名前とパスワードでログインをすることを考えます。わかりやすくするためにパスワードの暗号化などは割愛します。

#modelを作成
まずは下準備としてUserモデルを作成しましょう。

command
$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
command
$rails db:migrate

#データを入れる
今回はログイン機能を作るのが目的のためユーザーの登録画面は作りません。ただ、ユーザーの登録情報がないとログインが成立しないので、データベースにユーザーの情報を直接書き込みましょう。ターミナルで下記のコマンドを打ち込んでください。

command
$ rails c

次に下記のコマンドを打ち込んで、名前:userA パスワード:AAAAAAのuserAさんを作りましょう。

command
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さん
も作っておきましょう。作り終えたら下記のコマンドを打ってデータベースを確認してみましょう。

command
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画面です。ユーザビリティは全くのゼロですがお許しください。
まずはログイン画面から。

login.html.erb
<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画面

show.html.erb
<h1>ログイン成功後に飛ぶユーザのshow画面</h1>
<%= @current_user.name %>
<%= @current_user.email %>
<%= link_to 'ログアウト', logout_path, method: :delete %>

2行目と3行目に@current_userという記述がありますが、これは後でcontrollerで定義していきます。
一番最後の行にログアウトをするためのリンクも用意しておきましたが、これについても後ほど機能を実装していきます。
#controllerの実装
まずはログイン後に表示するshow画面から。画面を表示するだけなので特に記述することはありません。

users_controller.rb
class UsersController < ApplicationController

	def show
	end

end

次にapplication_controllerです。

application_controller.rb
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をみてみましょう。

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_controllerrequire_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の中身を空にしています。

#まとめ
以上で、非常に簡単にではありますがログイン・ログアウト機能の実装ができました。このままではセキュリティ面での不安がかなり残るのですが、今回はひとまずここまでとします。最後まで読んでいただきありがとうございました!

36
18
4

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
36
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?