1限目be動詞とは

はじめに

プログラミング学習において、英語の必要性を感じたため英語を学習しています.

英語アレルギーですが、英語力を身に着けるためアウトプットします.

概要

be動詞とは

使い方(法則)

これを見ればもう迷わないフローチャート

be動詞とは

is、am、areのことを言う。日本語でいうと述語
主語によってbe動詞が変化する.
文の中では主語の直後に来る.

使い方

①使い方(法則) 主語 + be動詞
文の中では主語の直後に来る.(再記)

英語
日本語訳
I am a new student. 私は新しい生徒です.
You are my friend. あなたは私の友達です.
Mike is in Tokyo now. マイクは今、東京にいる.
My bag is on the desk. 私のカバンは机の上にある.

フローチャート

f:id:takishita0:20210227175109p:plain

学習に使っているサイト

https://english.005net.com/yoten/be.php

サンプルデータbuild、createどちらを使うか

概要、目的

サンプルデータの使い分け
テスト作成時に手こずったのでアウトプットも兼ねて

前提

使用するアプリのログイン機構はsorceryを使用
テストはrspec
FactoryBotを使用

今回使用するコード

# spec/factories/users.rb

FactoryBot.define do
  factory :user do
    sequence(:email){|n| "test@#{n}example.com" }
    password { 'password' }
    password_confirmation { 'password' }
  end
end
_____________________________________________________________________
# spec/features/users_spec.rb

RSpec.feature "Users", type: :feature do
  describe 'ログイン前' do

    # 変化させる箇所 create,buildした時の違い
    # ①let(:user) { create(:user) }
    # ②let(:user) { build(:user) }

    context 'フォームの入力値が正常' do
      it  'ユーザーの新規登録作成が成功する' do
        visit root_path
        click_link 'SignUp'
        fill_in 'Email', with: user.email
        fill_in 'Password', with: user.password
        fill_in 'Password confirmation', with: user.password_confirmation
        
        # ③byegug デバック時に使用

        click_button 'SignUp'
        expect(page).to have_content 'User was successfully created.'
      end
    end
  end
end

実現したいこと

図1のフォームが正常に動作し、ログインできるかのテスト

新規登録フォームが表示されています.
図1.新規登録フォーム

create,buildの使い分け

今回はサンプルデータをcreate,buildしてみてどのような違いがあるか、試していきます.

createしてテストすると...

結論から言うとcreateではテストはパスしません.
最初私はcreateしたデータで、テスト項目に当てはめればいいと思い、テストを作成しました.
今後テストを書いていく上で、同じ失敗を繰り返さないためにもアウトプットします.
上のコード①を有効にしてテストを実行します.

テスト結果

 $ bundle exec rspec

1) Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する
     Failure/Error: expect(page).to have_content 'User was successfully created.'
       expected to find text "User was successfully created." in "Login SignUp\nSignUp\n3 errors prohibited this user from being saved:\nPassword is too short (minimum is 3 characters) Password confirmation can't be blank Email has already been taken\nEmail\nPassword\nPassword confirmation\nBack"
     # ./spec/features/users_spec.rb:15:in `block (4 levels) in <main>'

Finished in 0.44038 seconds (files took 1.64 seconds to load)
18 examples, 1 failure, 8 pending

Failed examples:

rspec ./spec/features/users_spec.rb:7 # Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する

原因

どうなっているか上のコード③を有効にして再度テストを実行します.
userそのものは存在してるようです.

(byebug) user
#<User id: 1, email: "test@1example.com", crypted_password: "$2a$04$gk9IxtIsYvQIjxcluZv8R.iEycIUUU0DzF6EJMu62XC...", salt: "vD43wtyV-oauxVTaFAT_", created_at: "2021-02-26 02:49:46", updated_at: "2021-02-26 02:49:46">

各カラムに値があるか見ていきます.

(byebug) user.email
"test@1example.com"
(byebug) user.password
nil
(byebug) user.password_confirmation
nil
(byebug)

デバックの結果passwordカラムの値がnilのため、テスト時にnilの値が入力されたためにユーザー登録ができないことがわかります.
エラーメッセージを再度見直すと、パスワードが空だと言われています.

1) Users ログイン前 フォームの入力値が正常 ユーザーの新規登録作成が成功する
     Failure/Error: expect(page).to have_content 'User was successfully created.'
       expected to find text "User was successfully created." in "Login SignUp\nSignUp\n3 errors prohibited this user from being saved:\nPassword is too short (minimum is 3 characters) Password confirmation can't be blank Email has already been taken\nEmail\nPassword\nPassword confirmation\nBack"
     # ./spec/features/users_spec.rb:15:in `block (4 levels) in <main>'

ではなぜpasswordが空なのでしょうか?

コンソールでcreateした時と、buildした時の違いを見てます.

#create時

[1] pry(main)> create_user=FactoryBot.create(:user)
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test@1example.com"], ["LIMIT", 1]]
  User Create (2.1ms)  INSERT INTO "users" ("email", "crypted_password", "salt", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["email", "test@1example.com"], ["crypted_password", "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue"], ["salt", "_uQ2mDDruScW1H6sxDy7"], ["created_at", "2021-02-26 03:20:14.766456"], ["updated_at", "2021-02-26 03:20:14.766456"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User:0x00007fa18bb05610
 id: 7,
 email: "test@1example.com",
 crypted_password:
  "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue",
 salt: "_uQ2mDDruScW1H6sxDy7",
 created_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00,
 updated_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00>

#build時
[4] pry(main)> build_user=FactoryBot.build(:user)
=> #<User:0x00007fa18b341b40
 id: nil,
 email: "test@2example.com",
 crypted_password: nil,
 salt: nil,
 created_at: nil,
 updated_at: nil>

create時にはcrypted_passwod、saltの値が入っていて、build時にはnilということがわかります.
次にFactoryBotで定義されている値が入っているか確かめてます.
FactoryBotのコード(再掲載)

FactoryBot.define do
  factory :user do
    sequence(:email){|n| "test@#{n}example.com" }
    password { 'password' }
    password_confirmation { 'password' }
  end
end

# ここからコンソール上のコードと比べながら
# create_user = FactryBot.create(:user)
# build_user = FactryBot.build(:user)

# create時
[7] pry(main)> create_user.email
=> "test@1example.com"
[8] pry(main)> create_user.password
=> nil
[9] pry(main)> create_user.password_confirmation
=> nil

#build時
[10] pry(main)> build_user.email
=> "test@2example.com"
[11] pry(main)> build_user.password
=> "password"
[12] pry(main)> build_user.password_confirmation
=> "password"
[13] pry(main)>

テスト失敗時デバックした時と同じように、createするとpasswod,password_confirmationがnilになっています.
反対にbuildではFactryBotで定義した通りに、password,password_confirmationカラムに値が入っています.
create時はpasword,password_confirmationがnilになり、build時には値が入っていることがわかりました.
ではなぜcreate時はnilになるのでしょうか?
再度こちらのコードを見てみましょう.

#create時

[1] pry(main)> create_user=FactoryBot.create(:user)
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test@1example.com"], ["LIMIT", 1]]
  User Create (2.1ms)  INSERT INTO "users" ("email", "crypted_password", "salt", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["email", "test@1example.com"], ["crypted_password", "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue"], ["salt", "_uQ2mDDruScW1H6sxDy7"], ["created_at", "2021-02-26 03:20:14.766456"], ["updated_at", "2021-02-26 03:20:14.766456"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User:0x00007fa18bb05610
 id: 7,
 email: "test@1example.com",
 crypted_password:
  "$2a$10$FooeA7t.Wprve0Z7JhgZsuryfRypjCT7dcIUjJzSIwWvronJ7Q2ue",
 salt: "_uQ2mDDruScW1H6sxDy7",
 created_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00,
 updated_at: Fri, 26 Feb 2021 03:20:14 UTC +00:00>

#build時
[4] pry(main)> build_user=FactoryBot.build(:user)
=> #<User:0x00007fa18b341b40
 id: nil,
 email: "test@2example.com",
 crypted_password: nil,
 salt: nil,
 created_at: nil,
 updated_at: nil>

create時にはpassword,password_confirmationカラムの値がnilに対して,crypted_passwordというカラムに値が代入されています.
これはpasswordをハッシュ化した値が代入されています.
ハッシュ化することで、悪意を持ったユーザーからpasswordを盗まれないようにするためです.
つまりnilになるのはsorceryのコードが関係しています.
corceryではユーザーが入力したpassword,password_confirmationカラムの値を同一か確認し、さらにpasswordをハッシュ化しcrypted_passwordに保存しています.
crypted_passwordに値が代入されたタイミングでpassword,password_confitmationにnilをセットしています.
nilをセットしているのは、悪意を持ったユーザーから身を守るためです.(直接データベースからカラムにアクセスされた際の対策)
以上がcreate時にpassword,password_confirmationがnilになり、テストがパスしない原因です.
最後にテストをbuildしたサンプルデータでテストをパスしましょう.

buildしてテスト

テストコード①を無効、②を有効にして、テストを実行すると成功します.
ログイン機構などをgemを利用していると、このようなことがあるので注意しましょう.
またそもそも今回のテストは新規登録が成功するかのテストなので、createしたユーザーでテストをするのはテストの意味が薄れてしまう.(実際のアプリの動作とは異なる)ので書いているテストが、実際のアプリの挙動と同じかどうかを確認しながら書きましょう.

FactoryBot関連付け注意点、余計なデータが作成されてしまう仕組み

概要

関連付け、複数のサンプルデータの定義の仕方 + 呼び出し方

前提条件

・gemインストール済み.
・FactoryBotサンプルデータの定義の仕方、呼び出し方がわかる.
・↓このブログの内容が理解できる(5分ほどで読めます.)

takishita0.hatenablog.com

FactoryBotの関連付け(注意点)

なお関連付けは以下の通りとする.
f:id:takishita0:20210221151553p:plain

図1.モデルER図


  • 余計なデータを作成してしまう例
    FactoryBot定義
spec/factories/
├── notes.rb
├── projects.rb
└── users.rb

#user.rb
FactoryBot.define do
  factory :user, aliases: [:owner] do
    first_name "Aaron"
    last_name  "Sumner"
    sequence(:email) { |n| "tester#{n}@example.com" }
    password "dottle-nouveau-pavilion-tights-furze"
  end
end

#project.rb
FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description "A test project."
    due_on 1.week.from_now
    association :owner
  end
end

#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    #FactoryBot.create(:user)と等価
    association :user
  end
end

noteに関するテスト

require 'rails_helper'

RSpec.describe Note, type: :model do
  it 'noteのサンプルデータをFactoryBotを使用して呼び出してみる' do
    note = FactoryBot.create(:note)
    #関連付けを使用してオブジェクトを呼び出してみる
    puts "#{note.project.user_id}"
    puts "#{note.user_id}"
  end
end

上のテストで期待することはどちらも同じuser_idが返ってくることです.

$ bin/rspec

Note
1
2

テスト結果からわかることは、userが2人分作成されていることです. なぜ2人作成されてしまうのでしょうか? コードを順に見ていきましょう.

_____________________________________________________________________
①
require 'rails_helper'

RSpec.describe Note, type: :model do
  it 'noteのサンプルデータをFactoryBotを使用して呼び出してみる' do
    note = FactoryBot.create(:note)
    #関連付けを使用してオブジェクトを呼び出してみる
    puts "#{note.project.user_id}"
    puts "#{note.user_id}"
  end
end
_____________________________________________________________________
②,③
#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    #FactoryBot.create(:user)と等価
    association :user
  end
end
____________________________________________________________________
④,⑤
#project.rb
FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description "A test project."
    due_on 1.week.from_now
    association :owner
  end
end

① FactoryBot.create(:note)が実行されると、spec/factories/notes.rbの内容が実行されます.
② message "My important note"が属性値として、代入されます.
③ association :projectでspec/factories/projectsが呼び出されます.つまりFactoryBot.create(:project)が実行されます.
④ name,description,due_onそれぞれカラムの値が設定されます.
⑤ association :ownerが実行されると、spec/factories/usersが呼び出されます.
つまりFactoryBot.create(:users)が実行され、一人目のuserが作成されました.(コードは省略します.)
※association :userではなく、association :ownerなのはモデルでuser_idではなく、owner_idになるように設定しているからです.
ここは今回の内容と関係ないので、association: ownerはassociation: userと同じことをしているという理解で大丈夫です.
下のコードが設定をしているコードです.

class Project < ApplicationRecord
  belongs_to :owner, class_name: 'User', foreign_key: :user_id
end

すこし脱線しましたが、②のコードをもう一度見てみましょう.
association :userが記述されています.
このコードが実行されると、userが当たらに作成されますよね.
しかしすでに、⑤によってuserは作成されています.そうです!これが2人のuserが作られてしまう仕組みです.
2人のユーザが作られないようにするには,したコードのようにすれば防げます.

#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    # 上のassociation :projectで作成されたユーザを渡してあげる
    user: { project.owner }
  end
end

テスト実行

$ bin/rspec

Note
1
1

期待通りになりました.

まとめ

FactoryBotを使用すると、気づかないうちに余計なデータが作成されてしまう.
余計なデータが作成されてしまうとテストに時間がかかるため、適宜チェックしよう.

【rspec】FactoryBot サンプルデータの定義の仕方

概要

 テストで使用するサンプルデータの定義の仕方 + 呼び出し方

前提条件

・gemはインストール済み.
rspecを使用する.
・config/application.rb内generatetorの設定欄に、fixtures: falseが記述されていないこと.
・もしくは下のコードの通りにすること.

Bundler.require(*Rails.groups)

module Projects
  class Application < Rails::Application
    config.load_defaults 5.1

    config.generators do |g|
      g.test_framework :rspec,
        # ビュースペックを作成しない.
        view_specs: false,
        # ヘルパーファイル用のスペックを作成しないこと.
        helper_specs: false,
        # ルーティング用のスペックを作成しない.
        routing_specs: false
    end
  end
end

ファイル生成

・文法  $ bin/rails g factory_bot:model モデル名  
・例:  $ bin/rails g factory_bot:model user  

・生成されるファイルの場所.

spec/factories/
├── notes.rb
├── projects.rb
└── users.rb

定義の仕方

・ジェネレーターで作成されたファイルに属性値などを定義する.

FactoryBot.define do
  # aliases: [:owner]はまだわからなくて良い、次回以降の記事で解説
  factory :user, aliases: [:owner] do
    first_name "Aaron"
    last_name  "Sumner"  
    # 呼び出されるたびにnの数が増えていくため、emailがユニークになる.
    sequence(:email) { |n| "tester#{n}@example.com" }
    password "dottle-nouveau-pavilion-tights-furze"
  end
end

・テスト内で呼び出す

   # FactoryBot使用しない場合
   user = User.create(
      first_name: "Joe",
      last_name:  "Tester",
      email:      "joetester@example.com",
      password:   "dottle-nouveau-pavilion-tights-furze",
    )
    # FactoryBot使用する場合
    FactoryBot.create(:user)
    FactoryBot.build(:user)
    # 特定の属性値のみ変更したい場合
    FactoryBot.create(:user, email: nil)
    FaactryBot.build(:user, name: 'foobar')

今日はここまで
作成時間30分 次回以降 関連付け、より複雑なサンプルデータ