【Rails】DBの制約,ログイン機能導入偏【76日目】

【学習内容】

・DBの制約他

・ログイン機能(前編)

【DBの制約他】

(1)NOT NULL制約を加える

現状、TaskleafのTaskモデルの属性の中に「名前(:name)」がなくても登録が出来るようになっているので、NOTNULL制約をつける事で、taskテーブルのnameをnull不可に変えます。

$bin/rails g migration ChangeTasksNameNoNull

■db/migrate/XXXXXXXXXXXXX_change_tasks_name_not_null.rb

class ChangeTasksNameNotNull < ActiveRecord: :Migration[5.2]
 def change
   change_column_null :tasks, name, false
 end
end
$bin/rails db:migrate

(2)文字列カラムの長さを指定する

必要以上に長いデータが保存されるのを防ぐため、文字列カラムに対して字数制限を施します。


■マイグレーションファイル


class CreateTasks < ActiveRecord: :Migration[5.2]
 def change
   create_table :tasks do |t|
     t.string :name, limit: 30, null: false
     t.text :description
・
・
・

■マイグレーションファイル


class ChangeTasksNameLimit30 < ActiveRecord: :Migration[5.2]
 def up
   change_column :tasks, :name, :string, limit: 30
 end
 def down
   change_column :tasks, :name, :string
 end
end

def up にはバージョンを上げる処理、downにはバージョンを下げる処理を記述します。

これはadd_columnとは異なり、change_columnではバージョンを戻す際の処理をバージョンを上げる際のコードから自動生成できないため記述しています。

(3)モデルの検証(validation)を使う

制約をつけることでDB側で指定しているルールの違反をしているデータは保存されないようなしくみができました。

しかし、そのエラーが出た理由をユーザーに伝えるために、視覚的にわかりやすく伝達する必要があります。その際に使用するのがモデルの検証です。

name属性の値が入っていなければ検証エラーになるように検証を追加します。

■app/models/task.rb

class task < ApplicationRecord
 validates :name, presence: true
end

■app/controllers/tasks_controller.rb

def create
 task = Task.new(task_params)
 task.save!
 redirect_to tasks_url, notice: “タスク「#{task.name}」を登録しました”
end

上記のコードを下記のように変更します。

def create
 @task = Task.new(task_params)
 if @task.save
   redirect_to @task, notice: “タスク「#{@task.name}」を登録しました”
 else
   render :new
end
end

条件分岐を加えて、trueの場合は今までどおり、タスク一覧画面に遷移し、falseの場合は再度同じ画面に飛びます。

taskを@taskに変更した理由は、検証エラーがあってもう一度新規登録画面を表示する際にビューに検証を行った現物のTaskオブジェクトを渡す必要があるからです。

Taskオブジェクトには直前にユーザーがフォーム送信したデータが入っているので、前回操作したままの値をフォーム内に引き継ぐことが出来ます。

また、ビュー上でエラーの内容をユーザーに知らせるためには変数に格納した値を利用する必要があります。


■app/views/tasks/_form.html.slim

-if task.errors.present?
 ul#error_explanation
-task.errors.full_messages.each do |message|
   li = message
= form_with model: task, local: true do |f|
 .form-group
   = f.label :name
   = f.text_field :name, class: ‘form-control’, id: ‘task_name
 .form_group
   = f.label :description
   = f.text_area :description, rows: 5, class: ‘form-control’, id: ‘task_description
 = f.submit nil, class: ‘btn btn-primary


(4)文字列の長さの検証を行う

■app/models/task.rb

validates :name, presence: true
validates :name, length: {maximum: 30}


1行にまとめることもできますが、あえて分けて書くことで見易さが増すので、僕はこのように書きます。

(5)オリジナルの検証コードを書く

上記のようにRailsの検証ヘルパーでまかなえる条件以外、つまり検証ヘルパーだけではどうにもならない要件を満たすためには自分でオリジナルの検証コードを書く必要があります。

今回は「タスクのname属性にはカンマが含まれていてはいけない」という制約を設けます。

■app/models/task.rb

validate :validate_name_not_including_comma

private

def validate_name_not_including_comma
 errors.add(:name, ‘にカンマを含めることはできません’) if name&.include?(‘,’)
end

errorsに対してaddすることで「検証エラーを発見したらerrorsにエラー内容を格納する」ということになります。

また、nameがnilの場合に例外が発生するのを避けるために&.を利用してnilのときはこの検証を通過するようにしています。(どの道あとでnameの必須条件で引っかかることになる)

■app/models/task.rb

class Task < ApplicationRecord
 validates :name, presence: true, length: { maximum: 30 }
 validate :validate_name_not_including_comma

 private

 def validate_name_not_including_comma
   errors.add(:name, ‘にカンマを含めることはできません’) if name&.include?(‘,’)
 end
end

(6)コールバックの実装

コールバックとは、登録や削除など重要なイベントの前後に任意の処理を挟むことができる仕組みのことをいいます。

ActiveRecordモデルのコールバックは検証、登録、削除といった主なイベントに対して、「前(before)」、「後(after)」、「イベントを挟む(around)」の3つのタイミングで書けます。

今回は、「不正なデータが入ってきたら、自動的に新しいデータに変更する」という処理の記述をしていきます。

内容は、タスクの名前が入力されていないときに「名前なし」と変換するようにします。

■app/models/task.rb

class Task < ApplicationRecord
 before_validation :set_nameless_name
・
・
・
private
 def set_nameless_name
   self.name = ‘名前なし’ if name.blank?
 end


(7)ログイン機能をつくる

新規登録後、メールアドレスとパスワードの組合せによって認証を行い、ログインできるように実装していきます。

deviseを使えば簡単に実装できるのですが、今回は1からちゃんと作ります。

また、ログイン機能を実装するには、セッションやCookieの知識が不可欠です。不安な方は確認しておいたほうがいいと思います。

《参考》
セッション (session)とは
https://wa3.i-3-i.info/word1791.html
クッキー (cookie)とは
https://wa3.i-3-i.info/word1725.html

Railsではコントローラーから、sessionというメソッドを呼び出すことでセッションにアクセスできます。同時にセッションはハッシュのように扱うことが出来ます。

session[:user_id] = @user.id
@user_id = session[:user_id]

上記のような感じで値を取り出すことが出来ます。

(8)Userモデルの作成

$bin/rails g model user name:string email:string password_digest:string

なぜpasswordではなくpassword_digestという命名にしたかというと、Railsのhas_secure_passwordという機能を使うためです。


■db/migrate/XXXXXXXXXXX_create_user.rb

class CreateUsers < ActiveRecord: :Migration[5.2]
 def change
   create_table :users do |t|
     t.string :name, null: false
     t.string :email, null: false
     t.string :password_digest, null: false
     t.timestamps
     t.index :email, unique: true
   end
 end
end

indexを貼ったemailカラムについては重複をさけるためunique: trueを指定しています。


$bin/rails db:migrate

(9)パスワードを受け付けてdigestを保存する

ログイン/ログアウト機能を実現するためには最初にユーザー登録が必要です。

登録時にパスワードも登録しますが、そのパスワードをそのままテキストで保存することはセキュリティの面で安全ではありません。

よって、暗号化した上でパスワードを保存する必要があります。

そのため、Railsが標準で提供するhas_secure_passwordメソッドを利用します。

このメソッドを使うための条件は下記の2点です。
①対象のテーブルにpassword_digestというカラムがあること
②最新のハッシュ関数を提供するbcryptというgemが必要。

■Gemfile

# Use ActiveModel has_secure_password
gembcrypt’, ‘-> 3.1.7

$ bundle

これでhas_secure_passwordを使えるようになったので、対象のモデルの中に記載していきます。

■app/models/users.rb

class User < ApplicationRecord
 has_secure_password
end

これによりDBのカラムには対応しない属性が2つ追加されます。
①password属性:ユーザーが入力した生のパスワードを一時的に格納するための属性
②password_confimation属性:パスワードを再入力するときに使う

もしも①と②の値が一致していなければ検証に失敗することになります。

(10)ユーザー管理機能一式を追加する

UserをDBに登録し、管理する方法として大きく2種類に分かれます。
①未登録のユーザーが自らサインアップを行う
②管理者がユーザーを登録することでユーザーがアプリケーションを利用できるようにする

今回は②の手法を取ります。

ユーザー管理機能は、/adminで始まるURLで提供し、adminというフラグがtrueのユーザーだけが利用できるようにします。

ということで続きはまた明日にします。


この記事が気に入ったらサポートをしてみませんか?