応用編3

2021.5.6

 

テキスト挿入時のバグ修正

テキストを入力せずにプレビューを確認すると、no implicit conversion of nil into Stringというエラーが表示される。

 

(↓元々)

def build_body(controller)
result = ''
article_blocks.each do |article_block|
result << if article_block.sentence?
sentence = article_block.blockable
sentence.body
elsif article_block.medium?
 
省略

 

デバッグを追加して確認してみた。

f:id:mmm_st:20210506195705p:plain

 

article_block.blockableは存在するが、sentencesentence.bodynilになっている。

result <<これで代入しようとしているが、resultString型でsentencObject型なので、代入できない。文字の時はいいがnilの時ブランクにしなければならない。

 

nilの時ブランクにするために ||= '' を追加

def build_body(controller)
result = ''
article_blocks.each do |article_block|
result << if article_block.sentence?
sentence = article_block.blockable
sentence.body ||= ''#追加
elsif article_block.medium?
省略

 

「 || 」自己代入演算子

ex)

a ||= XXX   → aが偽か未定義ならaにxxxを代入する

今回はnilだった時にブランクを追加

参照

Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 3.0.0 リファレンスマニュアル)

sentence .body.to_sでもnilの場合に空文字がかえってくるが、空文字を明示的に表示するには↑この方法の方が良い。

 

 

今回のRSec

反省点

❶ spec/factories/articles.rbに記載する内容にsequenceを使用するのを忘れていた。

❷ spec/system/admin_articles_previews_spec.rbの流れも分かりにくい作りにしてしまっていた。

自分→新規作成から流れでテストした

回答例→編集ページから流れでテストしていた。書く内容が少なく、流れもわかりやすかった。

 

spec/factories/articles.rb

sequence を使用して複数件作成してもエラーにならないようにする。

FactoryBot.define do
factory :article do
sequence(:title) { |n| "title-#{n}" }
sequence(:slug) { |n| "slug-#{n}" }
end
end

 

 

応用編2

2021.5.6

パンくずの設定

 

パンくずとは

パンくずリストを省略した表現で、ユーザーが今WEBサイト内のどの位置にいるのかを視覚的に分かりやすくさせるために上位の階層となるWEBページを階層順にリストアップし、リンクを設置したリストのこと。

メリット

  • ユーザーがどのページをよんでいるのか瞬時にわかるようにする
  • クローラー巡回を手助けする
  • 内部 SEOに有効

 

gretel gem

Railsでパンくずを導入したい場合はgretelというgemが便利。

 

1 Gemfileに記載して、$bundle installを実行する。

2 $rails generate gretel:install 実行する。

3 2 を実行すると、config配下にbreadcrumbs.rbというファイルが生成される。

   breadcrumbs.rbにパンくずの定義を一元化してまとめる

4 viewファイルから<% breadcrumb :hogehoge %>

 このような形でパンくずリストを呼び出す。

参照

gretel/README.md at master · kzkn/gretel · GitHub

【Rails】gretelを使ってパンくずリストを作成しよう | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

 

今回だったら

layout/admin_htnl.slimlにbreadcrumbsを記述styleなども指定する。

main.content-wrapper
section.content-header
h1
= yield 'content-header'
== breadcrumbs style: :ol, class: 'breadcrumb'

section.content
= render 'layouts/flash_message'
= yield

 

2 breadcrumbs.rbにパンくずを定義する

省略
crumb :admin_tags do
link 'タグ', admin_tags_path
parent :admin_dashboard
end

crumb :edit_admin_tag do |tag| #(tag)でidが持って来られるようにする
link 'タグ編集', edit_admin_tag_path(tag)
parent :admin_tags
end

 

3 呼び出したいviewに記述する

= content_for 'content-header' do
| タグ編集

- breadcrumb :edit_admin_tag, @tag #(@tagでid渡す)

.box
= render 'form', tag: @tag


 

今回のRSpec

1 spec/factories/tags.rbにtagのデータ作成

FactoryBot.define do
factory :tag do
type { 'Tag' }
name { 'テストタグ' }
slug { 'test-tag' }
end
end

 

2 spec/system/admin_tags_spec.rb を作成

全部書くと長いので、覚えておきたい点のみ

require 'rails_helper'

RSpec.describe 'AdminArticles', type: :system do
let(:admin) { create :user, :admin }
before do
login(admin)
end
describe 'タグ一覧画面' do
it '「Home > タグ」が正常に表示される' do
visit admin_tags_path
within('.breadcrumb') do ❶
 
expect(page).to have_content('Home'), '「Home」というパンくずが表示さ
れていません'❷
expect(page).to have_content('タグ')
end
end
end

❶within()を使用してページの特定の領域に制限する。

今回は.breadcrumbで指定。.でclass#でidなど指定する。

参照

capybara/README.md at master · teamcapybara/capybara · GitHub

expect(page).to have_content('Home'), '「Home」というパンくずが表示さ
れていません'
これを指定することで、エラーだった際にターミナルに表示され、分かりやすくなる。

 

続き
describe 'タグ編集画面' do
let!(:tag) {create :tag } ❸
it '「Home > タグ > タグ編集 」が正常に表示される' do
visit edit_admin_tag_path(tag)
within('.breadcrumb') do
expect(page).to have_content('Home')
expect(page).to have_content('タグ')
expect(page).to have_content('タグ編集')
end
end
end
end

 ❸ここでlet!を使用して、先にtagを作成しておかないと、visitで(tag)を使用しidを反映させて遷移するときにundefined local variable or method `tag' というエラーが出てしまう。

 

 

応用編1

2021.05.04

 

応用編1

slim

・< >や閉じタグなどを削り、最低限必要なものだけを残した、非常にシンプルなテンプレート言語

・軽量

HTMLがより簡潔に記述できる

 

導入方法

Gemfileにslim-railsを記述bundle install を実行後viewファイルの拡張子をhtml.slimにする。

slim-railsを導入後、ジェネレーター経由で生成されるviewファイルの拡張子はhtml.erbからhtml.slimに変更される

 

slimの記述方

slimはテンプレートエンジンなので、最終的にはHTMLで出力される。

READMEに日本語訳され書いてあるので確認する。

slim/README.jp.md at master · slim-template/slim · GitHub

 

<流れ>

エラー

Nil location provided. Can't build URl.

表示されているのが、

medium = local_assigns[:medium]

.media-image
= image_tag medium.image_url(:lg) #ここでエラーが出ている

 

デバッグを使用して確認する。

表示されているのが、

medium = local_assigns[:medium]

.media-image
- byebug
= image_tag medium.image_url(:lg) #ここでエラーが出ている

 

f:id:mmm_st:20210505220834p:plain

medium.image_url(:lg)

=> nil

ここが原因?

urlがない時にnilになっている。→urlがない時の流れを記述しないと、Nil location provided. Can't build URl.が出てしまう。

 

↓このように、if文でmadium.image_urlがある時にのみにする。

medium = local_assigns[:medium]

- if medium.image_url
.media-image
= image_tag medium.image_url(:lg)

 

RSpec作成

今回作成した部分についてテストを作成していく。

factories/users.rbにFactoryBotのデータを作成する。traitも使用していく。

# admin権限を設定しないこと。default値のroleであるwriterを設定するのが望ましい。
FactoryBot.define do
factory :user do
sequence(:name, "admin-1") #sequenceは常にユニークな値がはいる。
create_listだと常にユニークとは限らない。
password { 'password' }
password_confirmation { 'password' }
role { :writer } #admin権限を設定しないこと。意図せずadmin権限を持った
ユーザーでテストしないように。
default値のroleであるwriterを設定するのが望ましい。

trait :admin do
sequence(:name, "admin-1")
role { :admin }
end

trait :editor do
sequence(:name, "editor-1")
role { :editor }
end

trait :writer do
sequence(:name, "writer-1")
role { :writer}
end
end
end

 

FactoryBot省略、spec/support配下のモジュールを追記する

config.include FactoryBot::Syntax::Methods #FactoryBot省略

config.include LoginMacros #spec/support配下のモジュールを読み込む

 

spec/support配下にloginモジュールを追加する

(spec/suport/login_macros.rb)に記述し、使えるようにする。

module LoginMacros
def login(user)
visit admin_login_identifier_path
fill_in 'user[name]', with: user.name
click_button '次へ'
fill_in 'user[password]', with: 'password'
click_button 'ログイン'
end
end

 

spec_helper.rbにSeedFu.seedを追記する

自動テストは副作用をなくす意味でもDBがクリーンな状態で実行される。テストで挿入されたデータはテスト終了後に削除する(される)のが一般的。しかしマスターデタについては存在することを前提に実行したい。RSpecによる自動テストの場合、実行前に一度SeedFu.seedを実行するように記述する。fixtures以下のファイルを読み込む。

=end
config.before :suite do
SeedFu.seed
end

参照

Railsアプリのマスターデータ管理 Seed Fu ベタープラクティス - ナガモト の blog

 

spec/sysytem配下にファイルを作成して、テストを作成する。

require 'rails_helper'

RSpec.describe 'AdminArticlePreview', type: :system do
let(:admin) { create :user, :admin } #adminのユーザー作成
describe '記事作成画面で画像を追加' do
context '画像を選択せずにプレビューを確認' do
it '正常に表示される' do
login(admin) #↑で作成したユーザーでログイン
click_link '記事'
visit admin_articles_path
click_link '新規作成'
fill_in 'タイトル', with: 'テスト'
fill_in 'スラッグ', with: 'test'
click_button '登録する'
click_link 'ブロックを追加する'
click_link '画像'
click_link 'プレビュー'
switch_to_window(windows.last) #タブが新しく作成される
expect(page).not_to have_content("Nil location provided. Can't build URI"),
'エラーページが表示されています' #回答見て追記。これも記述したほうがよかった。
expect(page).to have_content 'テスト'
end
end
end
end

 

 

 

 

 

応用編 環境構築

2021.4.29

Redisとは

・無料で使用できるDB管理システムの一つ

・高速にデータを処理することができる

参照

初心者による初心者のためのRedis解説 - Qiita

 

Homebrewを使用してRedisをインストールする

Homebrew: OS環境におけるいわゆるパッケージマネージャー

 

Homebrewのバージョン確認

brew -v

Redis インストール

brew install redis

起動

redis-server 

接続

redis-cli

 

参照

Homebrewのインストール - Qiita

MacにRedisをインストールする - Qiita

Redisとは?RailsにRedisを導入 - Qiita

Redisの特徴と活用方法について

Redisサーバー、Redisクライアントの起動と終了 - Qiita

 

 

SQLite

クライアントサーバー型ではないDB = ローカルでしか使用できない

参照

【まだ途中】SQLite, Redis の Insert 比較 (By PHP)【ツッコミ歓迎】 - 自分用備忘録

 

Node.js

Node.js = 12.14.0設定

サーバーサイドで動くJavaScript(クリックすると写真が大きくなったえい、横にスライドしたり...etc)

使用する理由

「クライアントもサーバーサイドも同じ言語で書くことができたら楽じゃない?」

参照

初心者向け!3分で理解するNode.jsとは何か?

 

nodenv

nodeのバージョン管理をする。

参照

nodenv/README.md at master · nodenv/nodenv · GitHub

 

nodeのインストール(12.14.0の場合)

$nodenv install 12.14.0

#nodeのバージョン固定(アプリケーションディレクトリで)

$nodenv local 12.14.0

#nodeのバージョン確認

$node -v

=>12.14.0

 

 

$ bundle exec 

これをつけて実行すると、そのRailsプロジェクトのGemfileで指定した環境で実行することができる。

$ rails db:create

Railsプロジェクトのconfigディレクトリ中にあるdatabase.ymlを読み込み、そのファイルに基づいてDBを作成する。

$rails db:reset

DBの作成、スキーマ(db/schema.rb)の読み込み、シートデータを用いてDBの初期化を実行する。db/migrate/**.rbは使用されない。

参照

bundle execとかdb:createとかが何をしているのか - Qiita

rails db:migrate:resetできなかったのでrails db:resetした - Qiita

 

 

$ bundle exec rails db:seed_fu

db/seed.rbにシートデータを入れておけば、コマンドを叩けば勝手にシートデータを作成してくれる。

1 Gem seed-fuを導入する

2 dbディレクトリ配下にfixtureディレクトリを作成

3 db/fixture配下にシードファイルを作成する。環境によって変更する際は、db/fixture配下にもう一つディレクトリを作成する。

4 $ rails db:seed_fu コマンド実行(特定のディレクトリ配下の内容のみ対象とすることも可能)

5 DBにデータ投入される

seed-fu/README.md at master · mbleigh/seed-fu · GitHub

seed-fuを使って開発・テスト・本番それぞれの環境でのシードデータを作成する | もふもふ技術部

 

Webpacker

Webpackを使用してRuby on Rails上でJavaScript開発をするために必要な一連のまとまりを、標準で実装することができるgemパッケージ のこと。

 

PATHについて

読めばわかるMACでのPATH設定を完全理解 | アールエフェクト

 

springについて

$ spring rspec spec/[対象ファイル]

変更を加えるときにサーバーを再起動する必要がなくなる

参照

Rails/Spring サポート | RubyMine

 

./bin/webpack-dev-server

これを実行しながらターミナルで別タブを開いてbundle exec rails server を実行するとJS側の変更をリロードしてくれるので、いちいちゼロからビルドしなおさないで済むので開発が快適になる。

参照

Railsアプリでwebpackerを使う場合はbin/webpack-dev-serverコマンド実行しておかないと生産性落ちるぞ! - カクカクしかじか

 

これ以外にもコマンド実行しつつ環境構築完了した。

 

 

RSpec編4

2021.4.29

時間がかかってしまった...。

調べ方が難しかった。

trait使い方

参照

FactoryBot Traits - Qiita

FactoryBotでtraitを使おう - Qiita

今回の場合

(spec/factories/task.rb)に定義する

FactoryBot.define do
factory :task do
title { 'Task' }
status { :todo }
from = Date.parse("2019/08/01")
to = Date.parse("2019/12/31")
deadline { Random.rand(from..to) }

trait :done do
status { :done }
completion_date { Time.current.yesterday }
end
end
end

 

通常の書き方statustodoの通常のtaskになる。

let(:task) { create(:task, project_id: project.id) }

 

traitを活用した書き方statusdonecompletion_dateTime.current.yesterdayのtaskが作成される。

*第一引数に:task第二引数にtraitで定義したもの=:doneを追加する。

let(:task_done) { create(:task, :done, project_id: project.id)}

 

randメソッド

FactoryBot.define do
factory :task do
title { 'Task' }
status { rand(2) } #0以上2未満の整数を返す
省略

参照

【Ruby】randメソッドの使い方を学んでランダムなデータを作成しよ | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

 

short_timeメソッド

時刻表示の実装に違いがあり、エラーが出た。

参照

RSpecの日付表示を、viewで使っているメソッドに合わせて修正 - Kuni-Blog

【Rails】Time.currentとTime.nowの違い - Qiita

 

(spec/system/task_spec.rb)

 (元々の入力)

expect(find('.task_list')).to have_content(Time.current.strftime('%Y-%m-%d')

strftimeメソッド

【Rails】strftimeの使い方ついて徹底解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

日時データを指定したフォーマットデータを指定したフォーマットで文字列に変換できるメソッド

これを使用してもテストを通すことはできるのだが、viewの表示だ変更されたときにエラーになってしまうので、使用しない。

→viewで使用しているメソッドを使用して変更する

 

(views/tasks/index.html.erb)

<tbody class='task_list'>
<% @tasks.each do |task| %>
<tr>
<td><%= task.title %></td>
<td><%= task.status %></td>
<td><%= short_time(task.deadline) if task.deadline? %></td>
<td><%= link_to 'Show', [@project, task] %></td>
<td><%= link_to 'Edit', edit_project_task_path(@project, task) %></td>
<td><%= link_to 'Destroy', [@project, task], method: :delete, data:
{ confirm: 'Are you sure?' } %></td>
</tr>
<% end %>

 viewではshoet_timeメソッドを使用しているので、それを使用して修正する。

(spec/system/task_spec.rb)(変更後)

context '正常系' do
it 'Taskを編集した場合、一覧画面で編集後の内容が表示されること' do
# FIXME: テストが失敗するので修正してください
visit edit_project_task_path(project, task)
fill_in 'Deadline', with: Time.current
click_button 'Update Task'
click_link 'Back'
expect(find('.task_list')).to have_content(short_time(Time.current))
expect(current_path).to eq project_tasks_path(project)
end

 

しかし、テストを実行すると、失敗する。

ApplicationHelperを読み込んでいなかったためshort_timeメソッドが使用できない。

(spec/rails_helper.rb)

RSpec.configure do |config|
省略
 
config.include ApplicationHelper #追加
end

ApplicationHelperを追加したので、テストが通るようになる。

 

confirmダイアログのテストをする

(views/projects/index.html.erb)

<td><%= link_to 'Destroy', project, method: :delete,
data: { confirm: 'Are you sure?' } %></td>

↓confirmダイアログでOKを選択する

 
page.driver.browser.switch_to.alert.accept

参照

キャンセルなどの方法もここに載っている

【Rails】Selenium/RSpecでconfirmダイアログのテストをする - Qiita

 

expect(find())について

要素を検索する。

Capybaraチートシート - Qiita

 

今回の場合、expect(find('.task_list'))

it 'Taskが削除されること' do
visit project_tasks_path(project)
click_link 'Destroy'
page.driver.browser.switch_to.alert.accept
expect(find('.task_list')).not_to have_content task.title
expect(Task.count).to eq 0
expect(current_path).to eq project_tasks_path(project)
end

(views/tasks/index.html.erb)のクラス='task_list'を指定

クラスないから指定しないと、noticeが検出されてしまう。

<tbody class='task_list'>
<% @tasks.each do |task| %>
<tr>
<td><%= task.title %></td>
<td><%= task.status %></td>
<td><%= short_time(task.deadline) if task.deadline? %></td>
<td><%= link_to 'Show', [@project, task] %></td>
<td><%= link_to 'Edit', edit_project_task_path(@project, task) %></td>
<td><%= link_to 'Destroy', [@project, task], method: :delete, data:
{ confirm: 'Are you sure?' } %></td>
</tr>
<% end %>

 

これも読んだほうがいい

RUNTEQ(ランテック) - Webエンジニアの採用基準可視化サービス | RUNTEQ(ランテック)

 

target="blank"のテスト

(views/projects/show.html.erb)

<%= link_to 'View Todos', project_tasks_path(@project),
target:'_blank', rel: 'noopener' %>

target:'_blank' リンクを新しいウィンドウで表示する

rel: 'noopener' 一種のセキュリティ対策。target:'_blank' がついたときに自動で付くようになっている

参照

target=”_blank” の正しい使い方講座 |SEO Japan by アイオイクス

aタグに付いているrel="noopener"って何? | ocws BLOG

今回この形でエラーが出てしまった。

新しいウィンドウで表示することを記入していないから。

it 'Project詳細からTask一覧ページにアクセスした場合、Taskが表示されること' do
# FIXME: テストが失敗するので修正してください
visit project_path(project)
click_link 'View Todos'
expect(page).to have_content task.title
expect(Task.count).to eq 1
expect(current_path).to eq project_tasks_path(project)
end

(変更後)

it 'Project詳細からTask一覧ページにアクセスした場合、Taskが表示されること' do
# FIXME: テストが失敗するので修正してください
visit project_path(project)
click_link 'View Todos'
switch_to_window(windows.last) #追記 これで別のタブに移動する。
expect(page).to have_content task.title
expect(Task.count).to eq 1
expect(current_path).to eq project_tasks_path(project)
end

 

参照

Capybaraと仲良くなる(タブ・ウィンドウの操作について) - Qiita

 

associationを使用し、省略する

 

1 FactoryBotにassociationを記述

FactoryBot.define do
factory :task do
title { 'Task' }
status { rand(2) }
from = Date.parse("2019/08/01")
to = Date.parse("2019/12/31")
deadline { Random.rand(from..to) }
association :project

2  データを作成

project_idにproject.idが自動で入るようになるので、省略可能

let(:task_done) { create(:task, :done, project_id: project.id )}

RSpec.describe 'Task', type: :system do
let(:task) { create(:task) }
let(:task_done) { create(:task, :done)}

 

参照

【FactoryBot】associationの使い方 - Qiita

 

コミットの際はPrefixをつける

コミットの際に他の人からわかりやすいように、 Prefixをつけるようにする。

参照

誰にとってもわかりやすいGitのコミットメッセージを考える | Tips Note by TAM

 

 

 

RSpec編3

2021.4.21

ログイン処理を共通化する

supportディレクトリ作成

% mkdir support

supportディレクトリ下にファイル作成・編集

% touch login_macoros.rb

(spec/support/login_macros.rb)

module LoginMacros
def login_as(user)
visit root_path
click_link 'Login'
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password'
click_button 'Login'
end
end

rails_helper.rbを編集

supportディレクトリを読み込む
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
#(support/login_macros.rb)で作成したモジュールを使用できるようにする
RSpec.configure do |config|
config.include LoginMacros #追記

 

メソッドを使用する

RSpec.describe 'UserSessions', type: :system do
let(:user) { create(:user) }
省略
context 'ログアウトボタンをクリック' do
it 'ログアウト処理が成功する' do
login_as(user)
click_link 'Logout'
expect(page).to have_content 'Logged out'
expect(current_path).to eq root_path
end
end
end

 

let let! 違い

let 定義した定数が初めて使われたときに評価される。遅延評価。

この場合、login_as(user)userが出てきたときに、usercreateされる。

RSpec.describe 'UserSessions', type: :system do
let(:user) { create(:user) }
 
describe 'ログイン後' do
context 'ログアウトボタンをクリック' do
it 'ログアウト処理が成功する' do
login_as(user)
click_link 'Logout'
expect(page).to have_content 'Logged out'
expect(current_path).to eq root_path
end
end
end
end

 

 

let! 各テストのブロックの実行前に定義した定数を作成する。事前評価。

↓がletだと、事前に定数が定義されていないのでエラーになる。

describe 'タスクの削除' do
let!(:task) { create(:task, user: user) }

it 'タスクの削除が成功する' do
visit tasks_path
click_link 'Destroy'
expect(page.accept_confirm).to eq 'Are you sure?'
expect(page).to have_content 'Task was successfully destroyed.'
expect(current_path).to eq tasks_path
expect(page).not_to have_content task.title
end
end

 

まとめ

let →定義した定数が初めて出てきたときにcreateされる
let!→各ブロック実行前に定数をcreateする

参照

rspec 「let」と「let!」の違い - Qiita

 

create_listについて

RSpecテストを書いているときに、複数のインスタンスをまとめて作成することができる。第1引数元になるファクトリ第2引数作成する数を指定する。

context 'タスクの一覧ページにアクセス' do
it '全てのユーザーのタスク情報が表示される' do
task_list = create_list(:task, 3)
visit tasks_path
expect(page).to have_content task_list[0].title
expect(page).to have_content task_list[1].title
expect(page).to have_content task_list[2].title
expect(current_path).to eq tasks_path
end
end

参照

RSpecでFactoryBotから複数のインスタンスをまとめて作成する【create_listを使用】 - Qiita

 

RSpec実行時のテストケースを:focus指定で限定できるように設定する

特定のexample(it)のみ実行していときに使用する。

使い方

(rails_helper.rb)

focus: trueを使用できるようにする。

RSpec.configure do |config|
config.filter_run_when_matching :focus #追記
end

(もともと)

it 'マイページへのアクセスが失敗する' do
visit user_path(user)
expect(page).to have_content ('Login required')
expect(current_path).to eq login_path
end

① itをfocusに変更

focus 'マイページへのアクセスが失敗する' do
visit user_path(user)
expect(page).to have_content ('Login required')
expect(current_path).to eq login_path
end

②さらに短縮。focusの頭文字のfをとったfitという別名を使用する。

特定のテストを実行したいときにfをタイプし、終わったら消す。

仕様テストのグループ(describe, context)についても、同様のショートカットが可能。fdescribe fcontext の形。

fit 'マイページへのアクセスが失敗する' do
visit user_path(user)
expect(page).to have_content ('Login required')
expect(current_path).to eq login_path
end

 

参照

RSpecで特定のテストを実行する方法

 

RSpec編2

2021.4.20

FactoryBot

FactoryBot: テストデータの作成を手伝ってくれるgem

インスタンスメソッドを作成するメソッドはbuild  create がある。

(spec/factories/tasks.rb)

FactoryBot.define do
factory :task do
sequence(:title, "title_1")
#❶title_1やtitle_2のように連番を持つデータを定義することができる。
content { "content" }
status { :todo }
#enumで定義したところから持ってきている
deadline { 1.week.from_now }
association :user
#❷アソシエーションを設定する。
関係するuserオブジェクトも自動的に作成してくれる
end
end

sequence: unique制約のあるカラムにつける。連番を使用して重複しないデータを定義する。

sequence(:title) {|n| "title_#{n}"} #元々はこの形
sequence(:title, "title_1")

ブロックを渡さずに第二引数を渡すとループの度に、.next が呼ばれるようになっている。

.next は、Ruby自体の機能だが、String#nextは末尾を増やす機能がある。

参照

FactoryBot (旧FactoryGirl) の sequence と .next - Qiita

 
❷associationを定義することでuserの記述を省略できる
association :user

 

user = FactoryBot.create(:user)
task = FactoryBot.build(:task, user: user)
#この2行が↓この1行で済む
task = FactoryBot.build(:task)

 

(spec/model/task_spec.rb)

require 'rails_helper'

RSpec.describe Task, type: :model do
describe "validation" do
it 'is valid with all attributes' do
 
task = build(:task)
expect(task).to be_valid
expect(task.errors).to be_empty
end

it 'is invalid without title' do
task_without_title = build(:task, title: "")
expect(task_without_title).to be_invalid
expect(task_without_title.errors[:title]).to eq ["can't be blank"]
end

it 'is invalid without status' do
task_without_status = build(:task, status: nil)
expect(task_without_status).to be_invalid
expect(task_without_status.errors[:status]).to eq ["can't be blank"]
end

it 'is invalid with a duplicate title' do
task = create(:task)
task_with_duplicated_title = build(:task, title: task.title)
#❸createで作成保存したtaskのtitleを持ってきている。
expect(task_with_duplicated_title).to be_invalid
expect(task_with_duplicated_title.errors[:title]).to eq ["has already been taken"]
end

it 'is valid another title' do
task = create(:task)
task_with_another_title = build(:task, title: 'another_title')
expect(task_with_another_title).to be_valid
expect(task_with_another_title.errors).to be_empty
end
end
end

 

<ポイント>

❶(spec/rails_helper.rb)

config.include FactoryBot::Syntax::Methods

 こう記述すると、FactoryBotを省略して使用することができる。

(spec/model/task_spec.rb)

task = FactoryBot.build(:task)
task = FactoryBot.build(:task, title: "")
task = build(:task)
task_without_title = build(:task, title: "")

参照

Factory Botのメソッド利用時に、"FactoryBot."を省略できる話 - Qiita 

task_without_title や task_with_duplicated_title のようにわかりやすく定義する。

 

task = create(:task) ①
task_with_duplicated_title = build(:task, title: task.title)

taskというローカル変数をcreateで作成保存する。

 title: task.title ここの部分で、①で作成したtitleを取得して、重複データを作成している。

 

 

<エラー>

 ArgumentError:'1' is not a valid status

statusのenumを、指定したものではなく、数字で指定すると出る。

今回の場合、このように指定しているので、todo doing done のどれかをテストデータで指定する必要がある。

enum status: { todo: 0, doing: 1, done: 2 }

参照

Rspecのエラー|ArgumentError is not a valid status