Ruby on Railsでユーザ登録/ログイン機能を自作する方法
先日Ruby on Railsチュートリアルを完了しました。 疲れた。。。
それで、ちょっとごちゃごちゃしてきたので、ユーザ登録〜ログイン機能までの実装のみを抜き出して自分なりに整理してみました。
ちなみに今回長くなるのでバリデーションやテストは省略しています。
準備
ディレクトリ構成
編集するファイルは以下のような感じです。
app/
controllers/
application_controller.rb
sessions_controller.rb
users_controller.rb
helpers/
sessions_helper.rb
models/
user.rb
views/
layouts/
application.html.erb
sessions/
new.html.erb
users/
edit.html.erb
index.html.erb
new.html.erb
show.html.erb
config/
routes.rb
テーブル
テーブルは以下のようにUser
モデルのみ利用します。
ちなみにイカの頭以下の図はRails ERDを用いて生成しました(なんつー誤字だ……)。
上記を準備するコマンド
とりあえずパパパパーンッと上記を準備します。
$ rails new Login
$ cd Login
$ rails g controller Sessions
$ rails g controller Users
$ rails g model User name:string email:string password_digest:string remember_token:string
$ touch app/views/sessions/new.html.erb
$ touch app/views/users/edit.html.erb
$ touch app/views/users/index.html.erb
$ touch app/views/users/show.html.erb
$ touch app/views/users/new.html.erb
$ bundle exec rake db:migrate
$ vi Gemfile # bcrypt-ruby をコメントアウト
$ bundle install
各ファイル
各ファイルの中身と、コメントをちらほら記します。 それではディレクトリ構成の上から順に(適当)。
application_controller.rb
コントローラ全体からSessionsHelper
を呼び出すことになるので、ここでこれをinclude
しておきます。
以上。
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
sessions_controller.rb
Sessionコントローラはその名のとおりセッションを司ります。
RESTな考えでいくと、new
はログインフォーム、create
はログイン処理、destroy
はログアウトですね。
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to user
else
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
end
users_controller.rb
こちらはよくあるUserコントローラです。
今回はedit
とupdate
のみ権限を課しているので、before_action
でそれっぽいことをします。
class UsersController < ApplicationController
before_action :signed_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def edit
end
def create
@user = User.new(user_params)
if @user.save
sign_in @user
redirect_to @user
else
render 'new'
end
end
def update
if @user.update_attributes(user_params)
redirect_to @user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def correct_user
@user = User.find(params[:id])
redirect_to root_url unless current_user?(@user)
end
end
sessions_helper.rb
今回のメインっぽい部分。
ログイン時にトークンを更新して、以降は該当するトークンをfind
してcurrent_user
にセットします。
この辺でインスタンスメソッドとクラスメソッドの理解がだいぶ深まりました。。。
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user=(user)
@current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
@current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
def signed_in?
!current_user.nil?
end
def signed_in_user
redirect_to signin_url unless signed_in?
end
end
user.rb
Userモデル。
このhas_secure_password
がいろいろよしなにやってくれます。
というかやってくれ過ぎでしょ。。。
後ろ2つはインスタンスに関係ない部分なのでクラスメソッドとして実装しています。
class User < ActiveRecord::Base
before_save { email.downcase! }
has_secure_password
def self.new_remember_token
SecureRandom.urlsafe_base64
end
def self.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
end
application.html.erb
ここからはただのViewです。
application_controller.rb
でSessionsHelper
をinclude
したので、signed_in?
で便利に振り分けることができます。
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body>
<ul>
<li><%= link_to 'ユーザ一覧', users_path %></li>
<% if signed_in? %>
<li><%= link_to 'プロフィール', current_user %></li>
<li><%= link_to 'ログアウト', signout_path, method: :delete %></li>
<% else %>
<li><%= link_to 'ログイン', signin_path %></li>
<li><%= link_to 'アカウント作成', signup_path %></li>
<% end %>
</ul>
<hr>
<%= yield %>
</body>
</html>
sessions/new.html.erb
何の変哲もないViewです。
Sessionはモデルがないので、form_for
がちょっと特殊です。
<h1>ログイン</h1>
<%= form_for(:session, url: sessions_path) do |f| %>
<table border="1">
<tr>
<th><%= f.label :email, 'メールアドレス' %></th>
<td><%= f.text_field :email %></td>
</tr>
<tr>
<th><%= f.label :password, 'パスワード' %></th>
<td><%= f.password_field :password %></td>
</tr>
</table>
<%= f.submit 'ログインする' %>
<% end %>
users/edit.html.erb
これもただのViewです。
<h1>アカウント編集</h1>
<%= form_for(@user) do |f| %>
<table border="1">
<tr>
<th><%= f.label :name, '名前' %></th>
<td><%= f.text_field :name %></td>
</tr>
<tr>
<th><%= f.label :email, 'メールアドレス' %></th>
<td><%= f.text_field :email %></td>
</tr>
<tr>
<th><%= f.label :password, 'パスワード' %></th>
<td><%= f.password_field :password %></td>
</tr>
<tr>
<th><%= f.label :password_confirmation, '確認' %></th>
<td><%= f.password_field :password_confirmation %></td>
</tr>
</table>
<%= f.submit 'アカウントを編集する' %>
<% end %>
users/index.html.erb
ただ一覧を表示するだけです。
link_to
でuser
を渡すとshow
してくれるらしいです。
この辺のRailsのノリについていけない。。。
<h1>ユーザ一覧</h1>
<ul>
<% @users.each do |user| %>
<li><%= link_to user.name, user %></li>
<% end %>
</ul>
users/new.html.erb
そのうちPartialの記事も書きます。。。
<h1>アカウント作成</h1>
<%= form_for(@user) do |f| %>
<table border="1">
<tr>
<th><%= f.label :name, '名前' %></th>
<td><%= f.text_field :name %></td>
</tr>
<tr>
<th><%= f.label :email, 'メールアドレス' %></th>
<td><%= f.text_field :email %></td>
</tr>
<tr>
<th><%= f.label :password, 'パスワード' %></th>
<td><%= f.password_field :password %></td>
</tr>
<tr>
<th><%= f.label :password_confirmation, '確認' %></th>
<td><%= f.password_field :password_confirmation %></td>
</tr>
</table>
<%= f.submit 'アカウントを作成する' %>
<% end %>
users/show.html.erb
自分のプロフィールページのみ、編集リンクを表示するようにします。
<h1>ユーザ情報</h1>
<table border="1">
<tr>
<th>名前</th>
<td><%= @user.name %></td>
</tr>
<tr>
<th>メールアドレス</th>
<td><%= @user.email %></td>
</tr>
</table>
<% if current_user?(@user) %>
<p><%= link_to '編集', edit_user_path %></p>
<% end %>
routes.rb
この辺は無抵抗に書きます。
Login::Application.routes.draw do
resources :users, only: [:index, :show, :new, :edit, :create, :update]
resources :sessions, only: [:new, :create, :destroy]
match 'signup', to: 'users#new', via: 'get'
match 'signin', to: 'sessions#new', via: 'get'
match 'signout', to: 'sessions#destroy', via: 'delete'
root 'users#index'
end
おわりに
以上です。
便利なGemにdevise
とかcancan
とかあるらしいですが、私みたいなシロウトはまず自分で書いて理解しなければならないと思うのです。。。