オレマトペ

Webエンジニアを目指し学習中のデブアラサーによるほぼ擬音ブログ

Ruby on Rails チュートリアル第6章を終えて

f:id:Npakk:20200610222322p:plain

はじめに

このエントリーでは、Railsを勉強している私がRuby on Rails チュートリアルを学習した感想などを、
各章毎に述べていきたいと思います。
このチュートリアルを学ぶ方法は動画、電子書籍など色々な媒体で提供されておりますが、
ここではWebテキスト(第6版)を使用しております。

目次

第6章 ユーザーのモデルを作成する

現在のアプリケーションではユーザー登録してもその情報を保存する場所がないため、
今回はその情報を保存するためのデータ構造を作成していきます。

rails generate controller Users new

上記で、コントローラを生成できるわけですが、モデルは下記コマンドになります。

rails generate model User name:string email:string

Railsでは、コントローラー名には複数形を使い、モデル名には単数形を用いる慣習があります。
こういった点を守っておくと複数人で開発するときでも、認識齟齬が起きないのでぜひ覚えておきたいですね。

さて上記のgenerateを行うと、マイグレーションファイルが生成されるのでした。
ファイル名には生成された時間のタイムスタンプが記載されており、複数人で開発するときに競合(コンフリクト)が発生しにくい配慮がなされています。(完全になくすわけじゃない)

マイグレーション自体は、実際にデータベースに変更を加えるメソッドの集まりで、
このファイルを元にデータベースのオブジェクトを構成するわけです。

また、こうして自動生成されたマイグレーションファイルには、
指定したカラム以外にも、マジックカラムと呼ばれるレコードの登録時と更新時のタイムスタンプを値に持つカラムを自動的に加えてくれます。
このマジックカラム、筆者もSESで働いていますが、間違いなくどの案件でも使用しておりましたので、
覚えておいて損はないと思います。
いつそのレコードが登録され、更新されたかという情報は大事。

そして、このマイグレーションファイルを実行します。
すると、db/development.sqlite3というファイルが生成されます。
これがSQLiteというリレーショナルデータベースの実体なんですね。

中身を見ているとidカラムが自動的に追加されていました。
Railsがレコードを一意に識別するために追加したカラムらしい。
なんでもやってくれますなー

generateしたUserモデルのファイルを参照すると、下記のようになっています。

class User < ApplicationRecord
end

何のメソッドもないですが、ApplicationRecordを継承しています。
これによって、ApplicationRecordのもつメソッドを使うことが出来ます。 具体的には下記コマンドを使用しました。

  • new: オブジェクトの初期化
  • valid: オブジェクトの有効性mddチェック
  • save: データベースへの保存
  • create: オブジェクトの生成と保存
  • destroy: データベースから削除
  • find: データベースを検索
  • find_by: 特定の属性でデータベースを検索
  • first: データベースの最初のレコードを検索
  • all: 全てのデータベースのレコードを検索
  • update: データベースの特定レコードの値を更新

ある程度、ApplicationRecordのメソッドを確認したら、
モデルの各属性の検証のために、テストを追加します。

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end
end

上記ソースで、setupメソッドを記載していますが、この処理は、
各テストが実行される直前で実行されます。
そしてNameとEmailの初期値を決定し、@userのインスタンス変数につっこんでいます。
このインスタンス変数を宣言しておくことによって、各テスト内で使えるようになります。
各テストでは、nameとEmail属性の存在性、一意性、書式などのテストを追加していきます。

一意性の検証では、かなり多くのことを実装しないとその一意性を担保できません。
大文字小文字を区別せずに一意性の検証を行うため、下記のコードを追記します。

#user_test.rb

  test "email addresses should be unique" do
    duplicate_user = @user.dup
    duplicate_user.email = @user.email.upcase
    @user.save
    assert_not duplicate_user.valid?
  end

@user.dupで同じ属性をもつデータを複製しています。
また、複製されたデータのemail属性に、元データのemailを大文字に変換した値を設定します。
これによって、小文字の値がきても大文字したテストが行えます。
ただし、この時点ではまたvalidメソッドは大文字小文字を区別しています。
モデルのvalidateを変更する必要があります。

#model/user.rb

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

uniqueness: { case_sensitive: false }で、大文字小文字を区別せずに一意性の検証が行えます。

これで完成かと思いきや、ActiveRecordはデータベースのレベルでは一意性を保証できないらしい。
登録ボタンを2回連続で押してしまった場合、2つリクエストがサーバーに到達しますが、
どちらのリクエストにおいてもメモリー上に検証をパスするレコードが保存されます。
これを避けるためにデータベースの特定属性へインデックスを追加する必要があります。
また、インデックスを追加することによって検索の効率が上がるので一石二鳥。

次に、セキュアなパスワード認証機能を追加します。 ここはかなりむずかしいのですが、要するに
登録時にハッシュ化(不可逆なデータに変換する処理)したパスワードをデータベースに保存できるようにし、
ログイン時にユーザーから入力されたパスワードと一致するか検証したいと。

まずは、モデルにhas_secure_passwordというメソッドを追加します。 こいつのメリットは以下。

  • セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
  • 2つのペアの仮想的な属性(passwordpassword_confirmation)が使えるようになる。
    また、存在性と値が一致するかどうかのバリデーションも追加される。
  • authenticateメソッドが使えるようになる(引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド)。

ただしこいつを利用するにはモデルにpassword_digestという属性が含まれている必要があります。
ハッシュ化したパスワードを保存する受け皿が必要ということですね。
また、ハッシュ化を行うbcryptというgemをアプリに導入する必要があります。

実装後はコンソール上で仮ユーザーを作成しデータベースに保存して、has_secure_passwordのauthencticateメソッドを使って、
ハッシュ化したパスワードと引数で渡したパスワードが一致するか確認します。
無事にパスワードの一致が確認できたところで、終了。

あとがき

仕事が忙しく時間取れていないのもあると思いますが、だんだん難易度あがってきて効率も落ちて吐きそう。
インプットしつつブログでアウトプットしてますが、次々に新しい用語が出てくるので、理解が追いつかない。
地道に進めてますが、実際に自分でアプリを作ってみないと完全には身につかなさそうですねー。