こんにちは!kossyです!
アウトプットが大事だと頭ではわかっていながら、
AmazonPrimeVideoにどっぷりの正月休みでした、、、笑
もう新年明けて仕事も始まっているので、
気持ちを切り替えて粛々とブログを更新していきます。
さて、今回はRailsのテストでテストデータを簡単に生成できるGem、
factoryBotでファクトリファイルを記述する際に使用する、
{}を使った記法でどういう挙動になるのか、ブログに残してみたいと思います。
ブロックで囲むと、遅延評価される
結論から言うとタイトル通りになります。
これだけでわかれば苦労しないので、もう少し解説します。
遅延評価とは、今回のケース(FactoryBotを使う)で言えば、テストデータを生成する時に評価されると言う意味になります。
ブロック構文を使わないと、rspecの立ち上げ時に評価されますが、
{}を使って値を定義すれば、FactoryBotを使ってインスタンスを生成しようとした時に、
値が評価されます。
それでもわからねぇ()
上記のことを上司から説明されましたが、実際の動きを確認しないと腑に落ちません。
ってことで、コンソールで試してみます。
$ rails c test --sandbox
このコマンドで、テスト環境のコンソールをDBの中身を変更させずにいじることができます。
(正確に言うとexit時にrollbackが走って変更がリセットされる)
別にsandboxいらないんですけどね、、、笑
早速試してみます。
factories
# user.rb FactoryBot.define do factory :user do name { Faker::Name.name } email { Faker::Internet.email } password { 'test1234' } password_confirmation { 'test1234' } role { 0 } created_at Time.now # <- ブロック無し updated_at Time.now # <- ブロック無し end end # review.rb FactoryBot.define do factory :review do bookname { 'Perfect Ruby' } content { 'テストだよ' } language { 'Ruby' } level { '初級' } created_at { Time.now } #<- ブロックあり updated_at { Time.now } #<- ブロックあり user end end
これでコンソールで試してみます。
$ rails c test --sandbox # 以下はログ [1] pry(main)> FactoryBot.create :user (0.3ms) SAVEPOINT active_record_1 User Exists (0.7ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'danellekuhlman@goodwinfay.com' LIMIT 1 User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '増田 美緒' LIMIT 1 User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'danellekuhlman@goodwinfay.com' LIMIT 1 SQL (0.5ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('danellekuhlman@goodwinfay.com', '$2a$04$cKY02.d3veqIt4Clqc7fA.m1NhAYarMjsEbGxbK9H24urUcyVziQq', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '増田 美緒') (0.3ms) RELEASE SAVEPOINT active_record_1 => #<User id: 10, email: "danellekuhlman@goodwinfay.com", created_at: "2019-01-07 12:17:31", updated_at: "2019-01-07 12:17:31", name: "増田 美緒", role: "user"> [2] pry(main)> FactoryBot.create :user (0.3ms) SAVEPOINT active_record_1 User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'shannonveum@greenfelderbode.biz' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '林 莉子' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'shannonveum@greenfelderbode.biz' LIMIT 1 SQL (0.7ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('shannonveum@greenfelderbode.biz', '$2a$04$vr6dTtCBjX5ZHfLr1scLQumF.Fe3JbZBPo6Sg8rdcPOVeJ/LZLhOm', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '林 莉子') (0.3ms) RELEASE SAVEPOINT active_record_1 => #<User id: 11, email: "shannonveum@greenfelderbode.biz", created_at: "2019-01-07 12:17:31", updated_at: "2019-01-07 12:17:31", name: "林 莉子", role: "user"> [3] pry(main)> FactoryBot.create :user (0.2ms) SAVEPOINT active_record_1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'boyce@corkery.io' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '藤本 大樹' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'boyce@corkery.io' LIMIT 1 SQL (0.3ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('boyce@corkery.io', '$2a$04$DLgUxLniJfVlU9d/0ZtF/ev1W3q6yzXn.C85OMB5wwKytp8R6sPoe', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '藤本 大樹') (0.2ms) RELEASE SAVEPOINT active_record_1 => #<User id: 12, email: "boyce@corkery.io", created_at: "2019-01-07 12:17:31", updated_at: "2019-01-07 12:17:31", name: "藤本 大樹", role: "user">
注目してほしいのは、created_atの値です。
どのレコードも、'2019-01-07 21:17:31'で作成されています。
次はreviewの方をコンソールで試します。
$ rails c test --sandbox [4] pry(main)> FactoryBot.create :review (0.6ms) SAVEPOINT active_record_1 User Exists (1.2ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'zoila@hane.io' LIMIT 1 User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '杉山 大地' LIMIT 1 User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'zoila@hane.io' LIMIT 1 SQL (0.5ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('zoila@hane.io', '$2a$04$NGsicKSVqrz0I2CfikbozuLLcAZGlgYkiQmLu0dzzj2uKXOjKE/Vu', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '杉山 大地') (0.2ms) RELEASE SAVEPOINT active_record_1 (0.3ms) SAVEPOINT active_record_1 SQL (30.9ms) INSERT INTO `reviews` (`user_id`, `bookname`, `content`, `language`, `level`, `created_at`, `updated_at`) VALUES (13, 'Perfect Ruby', 'テストだよ', 'Ruby', '初級', '2019-01-07 21:24:12', '2019-01-07 21:24:12') (0.2ms) RELEASE SAVEPOINT active_record_1 => #<Review:0x00007fc5afcfaf18 id: 3, user_id: 13, bookname: "Perfect Ruby", content: "テストだよ", language: "Ruby", level: "初級", created_at: Mon, 07 Jan 2019 21:24:12 JST +09:00, updated_at: Mon, 07 Jan 2019 21:24:12 JST +09:00, image: nil> [5] pry(main)> FactoryBot.create :review (0.3ms) SAVEPOINT active_record_1 User Exists (0.6ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'bev@toy.name' LIMIT 1 User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '前田 杏' LIMIT 1 User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'bev@toy.name' LIMIT 1 SQL (0.4ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('bev@toy.name', '$2a$04$jWBf1xZ3Mq3frPFPZwPrfeTAtnxbL2r1byrkAN2VfurltZppKtVrK', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '前田 杏') (0.3ms) RELEASE SAVEPOINT active_record_1 (0.2ms) SAVEPOINT active_record_1 SQL (0.5ms) INSERT INTO `reviews` (`user_id`, `bookname`, `content`, `language`, `level`, `created_at`, `updated_at`) VALUES (14, 'Perfect Ruby', 'テストだよ', 'Ruby', '初級', '2019-01-07 21:24:19', '2019-01-07 21:24:19') (0.2ms) RELEASE SAVEPOINT active_record_1 => #<Review:0x00007fc5b51029a8 id: 4, user_id: 14, bookname: "Perfect Ruby", content: "テストだよ", language: "Ruby", level: "初級", created_at: Mon, 07 Jan 2019 21:24:19 JST +09:00, updated_at: Mon, 07 Jan 2019 21:24:19 JST +09:00, image: nil> [6] pry(main)> FactoryBot.create :review (0.2ms) SAVEPOINT active_record_1 User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'ferdinandyundt@satterfieldhalvorson.com' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY '野口 悠' LIMIT 1 User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'ferdinandyundt@satterfieldhalvorson.com' LIMIT 1 SQL (0.4ms) INSERT INTO `users` (`email`, `encrypted_password`, `created_at`, `updated_at`, `name`) VALUES ('ferdinandyundt@satterfieldhalvorson.com', '$2a$04$WovEde/7WJawJLYJBkqbRuElisVtJR.yW96Rqvk95AUqsRp1HBfq6', '2019-01-07 21:17:31', '2019-01-07 21:17:31', '野口 悠') (0.2ms) RELEASE SAVEPOINT active_record_1 (0.2ms) SAVEPOINT active_record_1 SQL (0.4ms) INSERT INTO `reviews` (`user_id`, `bookname`, `content`, `language`, `level`, `created_at`, `updated_at`) VALUES (15, 'Perfect Ruby', 'テストだよ', 'Ruby', '初級', '2019-01-07 21:24:21', '2019-01-07 21:24:21') (0.2ms) RELEASE SAVEPOINT active_record_1 => #<Review:0x00007fc5b3e954f0 id: 5, user_id: 15, bookname: "Perfect Ruby", content: "テストだよ", language: "Ruby", level: "初級", created_at: Mon, 07 Jan 2019 21:24:21 JST +09:00, updated_at: Mon, 07 Jan 2019 21:24:21 JST +09:00, image: nil>
めっちゃ長いですが、見るべき所はこちらもcreated_atだけです。
'2019-01-07 21:24:12' '2019-01-07 21:24:19' '2019-01-07 21:24:21'
3つのレコード全ての値が異なっています。
これが遅延評価だ!!
ブロックで囲まなかった方は、コンソールの立ち上げ時にfactoryの値が評価されて(Time.nowの値が決まる)いますが、
ブロックで囲ったTime.nowの方は、factoryを呼び出す(レコードを生成する)たびにTime.nowが行われているのがわかります。
コンソールで試すと理解が進みますね。
参考にさせていただいた記事
RSpecにおけるFactoryGirlの使い方まとめ - Qiita
FactoryGirlチートシート - Qiita