はじめに
掲示板を作成する中でログイン機能を実装する必要がある場合があるので、初心者でも理解しやすい簡易的なログイン機能の作り方をまとめることにした。
開発の概要
アカウント作成時にusersテーブルにログインIDとパスワードを保存し、ログイン時に一致するものがあればログイン成功とする。ログイン情報はCookieに保存し、ページを切り替えてもログインが維持されるようにする。
必要なファイル
マテリアル内のbbsを複製しbbs_login
として使用
これ以降、bbs_login
をルートフォルダとして進める
usersテーブルの作成
まずconfig/database.yml
内のデータベース名を変更する(これをしないと、掲示板を既に作っている場合データベース名が被って正しくデータベースの作成ができない)
default_env: &default
adapter: postgresql
encoding: unicode
database: bbs_login #変更
development:
<<: *default
production:
<<: *default
データベース名を変更後、データベースを作成
$ rake db:create
マイグレーションファイルを作成
$ rake db:create_migration NAME=create_users
マイグレーションファイルを編集
必要なのは表示名、ログインid、パスワードの3つ
カラム名はそれぞれ name
, login_id
, pass
とする。
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :name
t.string :login_id
t.string :pass
end
end
end
マイグレーションを実行
$ rake db:migrate
マイグレーション実行後のschema.rb
(確認)
ActiveRecord::Schema.define(version: xxxx_xx_xx_xxxxxx) do
create_table "users", force: :cascade do |t|
t.string "name"
t.string "login_id"
t.string "pass"
end
end
Userモデルの作成
初期状態ではcontribution.rb
というファイルになっているので、これをuser.rb
に変更する
「ファイル名を変更」で行ってもいいが、ターミナルを用いて変更する場合には以下のコマンドを実行する。
$ mv models/contribution.rb models/user.rb
ファイル名を変更することができたら、ファイル内のクラス名も変更する。
ActiveRecord::Base.establish_connection
class User < ActiveRecord::Base #変更
end
app.rb
で読み込むファイル名も変更しておく。
require 'bundler/setup'
Bundler.require
require 'sinatra/reloader' if development?
require "./models/user.rb" #変更
以上でusersテーブルおよびUserモデルの作成が完了した。
アカウント作成画面の作成
~amazonaws.com/signupでアカウントを作成できるようにする。
まずはapp.rb
でルーティングし、テンプレートファイルとしてsignup.erb
をレンダリングする。
get "/signup" do
erb :signup
end
続いてindex.erb
を複製しsignup.erb
を作成。ターミナルを用いて実行する場合はcpコマンドを用いる。
$ cp views/index.erb views/signup.erb
signup.erb
にアカウント作成用のフォームを作成。POSTリクエストの送信先は/signupとする。
inputタグの属性を複数用いているので、詳しくは以下のサイト等を参照。
<!--略-->
<body>
<form action="/signup" method="post">
<label>ユーザー名</label>
<input type="text" name="name" required>
<label>ログインID(8文字以下、英数字で入力してください)</label>
<input type="text" name="login_id" maxlength="8" pattern="^[0-9A-Za-z]+$" required>
<label>パスワード(16文字以下、英数字で入力してください)</label>
<input type="text" name="pass" maxlength="16" pattern="^[0-9A-Za-z]+$" required>
<button type="submit">アカウント作成</button>
</form>
</body>
<!--略-->
アカウント作成機能の実装
app.rb
にリクエスト送信時の処理を記述する。
ユーザー名およびパスワードの被りは問題ないが、ログインIDの被りは許容することができないので、既に使われているログインIDを入力した場合はデータベースに登録せずに/signupへリダイレクトするようにした。
-
find_by
テーブル内のレコードから条件に一致するものを探し、見つけた場合ひとつだけ返し、見つからなかった場合はnil
を返す。今回はlogin_id
がparams[:login_id]
と一致するレコードを探している。 -
nil?
値がnil
である場合にはtrue
を返し、それ以外の場合はfalse
を返す。
post "/signup" do
if User.find_by(login_id: params[:login_id]).nil?
User.create({
name: params[:name],
login_id: params[:login_id],
pass: params[:pass]
})
redirect "/"
else
redirect "/signup"
end
end
ログイン画面の作成
今度は/loginでログインできるようにする。先程同様app.rb
でルーティングを行い、テンプレートファイルとしてlogin.erb
をレンダリングする。
get "/login" do
erb :login
end
index.erb
を複製してlogin.erb
を作成。
$ cp views/index.erb views/login.erb
login.erb
にログイン用のフォームを作成。リクエストの送信先は/loginとする。
<!--略-->
<body>
<form action="/login" method="post">
<label>ログインID</label>
<input type="text" name="login_id" required>
<label>パスワード</label>
<input type="password" name="pass" required>
<button type="submit">ログイン</button>
</form>
</body>
<!--略-->
ログイン機能の実装
まずsession
の保存先としてcookieを使用するためにapp.rb
に以下の記述をする。
require 'bundler/setup'
Bundler.require
require 'sinatra/reloader' if development?
require "./models/user.rb"
use Rack::Session::Cookie #新たに追加
続いてリクエスト送信時の処理を書く。
check
という変数を用意し、フォームに入力されたlogin_id
と値が一致するレコードがあればこの変数に格納するようにした。続くif文で、もしcheck
が空でなければcheck.pass
とparams[:pass]
を比較し、さらにこれが一致していればsession[:user]
にcheck
を格納しcookieに保存している。また、フォームに入力されたlogin_id
と値が一致するレコードが存在しないか、存在していたとしてもパスワードが間違っている場合は/loginにリダイレクトするようにした。
post "/login" do
check = User.find_by(login_id: params[:login_id])
if check
if check.pass == params[:pass]
session[:user] = check
else
redirect "/login"
end
end
ログイン状態の維持
ログインしているかどうかの判定をapp.rb
内のbeforeブロックに記述する。ログインされている場合はsession[:user]
を@user
に格納してテンプレートファイルに渡せるようにする。
before do
if session[:user]
@user = session[:user]
end
end
ログアウト機能の追加
<!--略-->
<body>
<a href="/logout">ログアウト</a>
</body>
<!--略-->
get "/logout" do
session[:user] = nil
redirect "/login"
end
マイページの作成
先程までの内容で基本的なログイン機能が完成したので、後は追加の機能を作成していく。
get "/" do
if session[:user].nil?
redirect "/login"
end
erb :index
end
<!--略-->
<body>
<!--新たに追加-->
<h1><%= @user.name %>さんのマイページ</h1>
<a href="/logout">ログアウト</a>
</body>
<!--略-->
投稿機能の実装
$ rake db:create_migration NAME=create_contributions
class CreateContributions < ActiveRecord::Migration[6.1]
def change
create_table :contributions do |t|
t.string :body
t.integer :user_id
end
end
end
schema.rb
は以下のように更新される。
ActiveRecord::Schema.define(version: xxxx_xx_xx_xxxxxx) do
enable_extension "plpgsql"
create_table "contributions", force: :cascade do |t|
t.string "body"
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "login_id"
t.string "pass"
end
end
Contributionモデル及びアソシエーションを作成。ユーザーと投稿は一対多の関係になってる。
ActiveRecord::Base.establish_connection
class User < ActiveRecord::Base
has_many :contributions
end
class Contribution < ActiveRecord::Base
belongs_to :user
end
投稿フォームの追加
<!--略-->
<a href="/logout">ログアウト</a>
<!--新たに追加-->
<form action="/new" method="post">
<input type="text" name="body" placeholder="投稿内容">
<button type="submit">投稿</button>
</form>
<!--ここまで-->
<!--略-->
リクエストに対する処理を追加する。アソシエーションの記述をしているため、通常ではContribution.create
とするところを@user.contributions.create
としており、これによってuser_id
には自動で投稿したユーザーのアカウントのid
が入る。
get "/new" do
@user.contributions.create({
body: params[:body]
})
redirect "/"
end
全投稿の表示(最新のものが上に来るように降順で並び替え)
get "/" do
if session[:user].nil?
redirect "/login"
end
@contributions = Contribution.all.order("id desc") #新たに追加
erb :index
end
</form>
<!--新たに追加-->
<% @contributions.each do |contribution| %>
<div>
<h3><%= User.find(contribution.user_id).name %></h3>
<p><%= contribution.body %></p>
</div>
<% end %>
<!--ここまで-->
</body>
まとめ
今回はCookieを用いて簡易的なログイン機能を実装した。Ruby関連の記事はRails環境であることが多く、Sinatraに関連する記事が少ないと感じたのでもしよければ参考にして欲しい。また、本格的なログイン機能を実装する場合とも基本的な部分は共通しているので、has_secure_password等を用いたログイン機能の開発の礎となれば幸いである。
パスワードの暗号化に関しては以下のgithubを参照
https://github.com/bcrypt-ruby/bcrypt-ruby