Help us understand the problem. What is going on with this article?

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #5 Userモデル編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい

前回:#4 System spec導入編
次回:#6 ユーザー登録画面, エラー日本語化編

今回の流れ

  1. 完成のイメージを理解する
  2. Userモデルを作る
  3. Userモデルのテストを作る
  4. Herokuのエラーを解決する
  5. showビューを仮実装する

※ この記事は、ポートフォリオを作る理由をweb系自社開発企業に転職するためとします。
※ 2020年3月30日、記事を大幅に更新しました。

完成のイメージを理解する

まずは、登録機能・ログイン機能を実装するためのUserモデルを作ります。
続いて、UserモデルのテストをModel specで作ります。
その後、マイグレーションの際のHerokuエラーを解決します。
最後に、次回以降に必要な仮ビューを作ります。

以上です。

Userモデルを作る

ユーザー情報を管理する、Userモデルを作ります。
ここでの手順は以下の通りです。

  • Userモデルを生成する
  • バリデーションを追加する
  • インデックスを追加する
  • パスワードを追加する

Userモデルを生成する

ジェネレーターでUserモデルを生成します。
現段階では、Userモデルにユーザーネームとアドレスの属性を与えます。

shell
$ rails g model User name:string email:string

この際、自動的にID属性も付与されます。
このIDは、showビューを仮実装にも使われます。

バリデーションを追加する

このままでは、どんな情報でもUser登録を受け付けてしまいます。
よって、以下のバリデーションを加えます。

  • ① ユーザーネームを必須にする
  • ② アドレスを必須にする
  • ③ 51文字以上の名前を無効にする
  • ④ 256文字以上のアドレスを無効にする
  • ⑤ アドレスでない文字列を無効にする
  • ⑥ 一意性のないアドレスを無効にする
  • ⑦ 一意性のないアドレスの大文字小文字を区別しないようにする
  • ⑧ アドレスを全て小文字にする
app/models/user.rb
class User < ApplicationRecord
  before_save :downcase_email #⑧
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i

  validates :name,
    presence: true, #①
    length: { maximum: 50 } #③
  validates :email,
    presence: true, #②
    length: { maximum: 255 }, #④
    format: { with: VALID_EMAIL_REGEX }, #⑤
    uniqueness: { case_sensitive: false } #⑥⑦

  private
    def downcase_email #⑧のメソッド
      email.downcase!
    end
end

downcase_emailメソッドは、user.rb内でのみ使います。
よって、プライベートにします。

インデックスを追加する

アドレスを検索する際は、ユーザーとの紐付けがあると高速です。
そのためのインデックスをマイグレーションに追加します。

shell
$ rails g migration add_index_to_users_email
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true
  end
end

データベースレベルでの一意性も確保するために、uniqueをtrueにしています。
最後に、データベースを更新します。

shell
$ rails db:migrate

パスワードを追加する

Userモデルにパスワードを追加します。
ここでの手順は以下の通りです。

  • has_secure_passwordを理解する
  • 必要なGemを入れる
  • password_digest属性を追加する
  • has_secure_passwordとバリデーションを追加する

has_secure_passwordを理解する

パスワードの実装は、モデルにhas_secure_passwordメソッドを加えることで可能になります。
これによって、以下の機能が使えます。

  • passwordとpassword_confirmation(再入力)の仮属性が作られる
  • セキュア化したパスワードをpassword_digest属性に保存する
  • パスワードの正誤を表すauthenticateメソッドが使える

has_secure_passwordを使うには、Gemとpassword_digest属性が必要です。

必要なGemを入れる

bcryptというGemを加えます。

Gemfile
+ gem 'bcrypt', '3.1.12'
shell
$ bundle install

password_digest属性を追加する

password_digest属性の追加には、同じくマイグレーションを使います。

shell
$ rails g migration add_password_digest_to_users password_digest:string
$ rails db:migrate

has_secure_passwordとバリデーションを追加する

Userモデルにhas_secure_passwordメソッドを追加します。
パスワードの一意性と5文字以下を無効にするバリデーションも追加します。

app/models/user.rb
class User < ApplicationRecord
  before_save :downcase_email
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i

  validates :name,
    presence: true,
    length: { maximum: 50 }
  validates :email,
    presence: true,
    length: { maximum: 255 },
    format: { with: VALID_EMAIL_REGEX },
    uniqueness: { case_sensitive: false }
  # 追加
  has_secure_password
  validates :password,
    presence: true,
    length: { minimum: 6 }

  private
    def downcase_email
      email.downcase!
    end
end

以上でモデルの作成が完了です。

Userモデルのテストを作る

Model specでUserモデルのテストを作ります。
Rails Tutorial 6.2のテスト内容とほぼ同じです。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do

  let(:user) { User.new(
    name: "Example User",
    email: "user@example.com",
    password: "foobar",
    password_confirmation: "foobar"
  ) }

  describe "User" do
    it "should be valid" do
      expect(user).to be_valid
    end
  end

  describe "name" do
    it "gives presence" do
      user.name = "  "
      expect(user).to be_invalid
    end

    context "50 characters" do
      it "is not too long" do
        user.name = "a" * 50
        expect(user).to be_valid
      end
    end

    context "51 characters" do
      it "is too long" do
        user.name = "a" * 51
        expect(user).to be_invalid
      end
    end
  end

  describe "email" do
    it "gives presence" do
      user.email = "  "
      expect(user).to be_invalid
    end

    context "254 characters" do
      it "is not too long" do
        user.email = "a" * 243 + "@example.com"
        expect(user).to be_valid
      end
    end

    context "255 characters" do
      it "is too long" do
        user.email = "a" * 244 + "@example.com"
        expect(user).to be_invalid
      end
    end

    it "should accept valid addresses" do
      user.email = "user@example.com"
      expect(user).to be_valid

      user.email = "USER@foo.COM"
      expect(user).to be_valid

      user.email = "A_US-ER@foo.bar.org"
      expect(user).to be_valid

      user.email = "first.last@foo.jp"
      expect(user).to be_valid

      user.email = "alice+bob@baz.cn"
      expect(user).to be_valid
    end

    it "should reject invalid addresses" do
      user.email = "user@example,com"
      expect(user).to be_invalid

      user.email = "user_at_foo.org"
      expect(user).to be_invalid

      user.email = "user.name@example."
      expect(user).to be_invalid

      user.email = "foo@bar_baz.com"
      expect(user).to be_invalid

      user.email = "foo@bar+baz.com"
      expect(user).to be_invalid

      user.email = "foo@bar..com"
      expect(user).to be_invalid
    end

    it "should be unique" do
      duplicate_user = user.dup
      duplicate_user.email = user.email.upcase
      user.save!
      expect(duplicate_user).to be_invalid
    end

    it "should be saved as lower-case" do
      user.email = "Foo@ExAMPle.CoM"
      user.save!
      expect(user.reload.email).to eq 'foo@example.com'
    end
  end

  describe "password and password_confirmation" do
    it "should be present (nonblank)" do
      user.password = user.password_confirmation = " " * 6
      expect(user).to be_invalid
    end

    context "5 characters" do
      it "is too short" do
        user.password = user.password_confirmation = "a" * 5
        expect(user).to be_invalid
      end
    end

    context "6 characters" do
      it "is not too short" do
        user.password = user.password_confirmation = "a" * 6
        expect(user).to be_valid
      end
    end
  end
end

テストのUserモデルにはインスタンス変数を使わず、letを使います。
望ましいテストにするため、しきい値を確認しています。

参考になりました↓
RSpec入門 - Dotinstall
RSpecで書かれたRailsチュートリアル 第6章のテストコードをレビューしてみた

Herokuのエラーを解決する

gitのpushを済ませ、Herokuを起動しようとすると以下のエラーが発生します。

shell
$ heroku run rails db:migrate
Running rails db:migrate on ⬢ lantern-lantern-app... up, run.2705 (Free)
rails aborted!
PG::ConnectionBad: could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

これはPostgreSQLの問題です。アドオンを足すことで使えます。

shell
$ heroku addons:add heroku-postgresql
$ heroku run rails db:migrate

参考になりました↓
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
【Rails】「heroku run rake db:migrate」を実行しようとすると発生するエラーについて

showビューを仮実装する

次回以降に必要な、ログイン・登録後に遷移するshowビューを仮実装します。
ここでの手順は以下の通りです。

  • RESTfulにルーティングを行う
  • showアクションを編集する
  • showビューを作る

RESTfulにルーティングを行う

まずはルーティングを行います。
もっと後になりますが、各ユーザーには様々なページを用意します。
各ユーザーのページURLをいい感じ(RESTful)に作るには、resourcesを使います。

config/routes.rb
Rails.application.routes.draw do
# 中略
  resources :users
end

何が生成されたかを、Rails Tutorial 表7.1で確認します。
そのうちの一つである、showビューを仮実装します。

showアクションを編集する

Usersコントローラーのshowアクションを編集します。
showビューは、ログイン・登録画面から送信されたIDでユーザーを特定します。
それを実装したのが、以下の記述です。

app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
  end
end

以下の部分を解説します。

@user = User.find(params[:id])

変数userに各Userモデル入れることで、各ユーザーの情報を画面に与えます。

例えば、初めての登録者のIDは1になります。
その際、ログイン・登録画面からshowビューに向けてIDが送信されます。
よって、ユーザーのshowビューのURLは/users/1になります。
ここの、/1というパラメーター部分が、/:idに当たります。

それを、railsで取得する際の書き方が、params[:id]になります。
各ユーザーを特定するには、UserモデルからIDが1のものを探します。
それを、Userモデルから探すのが、User.find(引数)になります。

showビューを作る

仮でshowビューを作ります。
先ほどshowアクションに、インスタンス変数@userを追加しました。
@user.nameのように属性をドットで繋ぐことで、ユーザーネームが呼び出せます。

app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="container">
  <div class="row">
    <div class="col bg-primary">
      form
    </div>
    <div class="col bg-secondary">
      figure
    </div>
  </div>
  <div class="row">
    <div class="col bg-warning">
      log
    </div>
  </div>
</div>

lantern_lantern_show_page_provisional.png

プレビューにあるデバッグを実装したい方は、Rails Tutorial 7.1.3をご覧ください。
(この記事では、言及しません。)

今回は以上です。


前回:#4 System spec導入編
次回:#6 ユーザー登録画面, エラー日本語化編

aokyo17
rails tutorial → ポートフォリオing. 誰もが経験した初心びくびく20代1年目.. フォローはすぐ返したい厨。
https://komucha.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away