はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。
このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る35歳定年説)と、
新しいことに挑戦したいという思いから、
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。
これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。
個人の解答例なので、誤りがあればご指摘ください。
7.4.1 登録フォームの完成
本章での学び
現状では正しい値を入力しても、登録できない。
原因は、UsersController#create
に対するテンプレートが存在しないため。
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"R19xBQHx6R//YVtxxqec3W47tAAwFX7uF8SnWHMtwziEbsmDAMerAp7XlFJkUBou+339KEet8ddTuC7hHGq8jw==", "user"=>{"name"=>"yokoyan", "email"=>"yokoyan@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.2ms) begin transaction
User Exists (0.4ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "yokoyan@example.com"], ["LIMIT", 1]]
SQL (3.1ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "yokoyan"], ["email", "yokoyan@example.com"], ["created_at", 2017-04-30 21:48:03 UTC], ["updated_at", 2017-04-30 21:48:03 UTC], ["password_digest", "$2a$10$DIVwsH852f5C11ge4wnKqeOa4iWcXxzUuwejIeUeYxG1yQTZEpEBa"]]
(13.0ms) commit transaction
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 128ms (ActiveRecord: 16.7ms)
ユーザー登録に成功した場合はページを描画せずに、ユーザーのプロフィールページにリダイレクトさせる。
def create
#@user = User.new(params[:user]) #これはRails3までの実装
@user = User.new(user_params)
if @user.save
#保存の成功をここで扱う
redirect_to @user
else
render 'new'
end
end
redirect_to @user
は、redirect_to user_url @user
と同じ意味になる。
user_url
は、routes.rb
に、以下を記載すると自動的に生成される模様。(ここがわからない。。。)
resources :users
正しく登録が完了すると、以下のURLに遷移するようになる。
(この場合はユーザIDが2のユーザ)
https://XXXXXX/users/2
つまり、user_url
は、ユーザ登録が完了したユーザの、ユーザ情報ページに遷移している。(名前付きルートuser_path(user)
にGETでアクセスしたときと同じ画面に遷移している)
演習1
有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
正しい情報が登録されていることを確認。
>> User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 2
>> User.second
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ? [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "yokoyan", email: "yokoyan@example.com", created_at: "2017-04-30 21:48:03", updated_at: "2017-04-30 21:48:03", password_digest: "$2a$10$DIVwsH852f5C11ge4wnKqeOa4iWcXxzUuwejIeUeYxG...">
演習2
リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。
コントローラを以下の通り修正。
・・・略・・・
if @user.save
#保存の成功をここで扱う
redirect_to user_url @user
jiroユーザを登録。
登録に成功。
コントローラを元に戻す。
・・・略・・・
if @user.save
#保存の成功をここで扱う
redirect_to @user
7.4.2 flash
本章での学び
flash変数
ユーザ登録処理が成功した際に、画面にユーザ登録完了のメッセージを表示するために、flash変数を使用する。
flash[:success] = "Welcome to the Sample App!"
全画面に適用するために、applicationのひな型であるhtml.erbにメッセージを組み込んでいる。alert-<% message_type %>
の部分では、flash変数とcssを組み合わせることで、メッセージのレベルごとに適用するBootStrapのCSSを変えている。
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message|%>
<div class="alert alert-<% message_type %>"><%= message %></div>
<% end %>
演習1
コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。
?> "#{ :success }"
=> "success"
演習2
先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。
ハッシュのキーであるシンボル:success
と、:danger
を使ってアクセスする。
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> flash.each do |key, value|
?> puts "#{key}"
>> puts "#{value}"
>> end
success
It worked!
danger
It failed.
=> {:success=>"It worked!", :danger=>"It failed."}
>>
?> "#{ flash[:success] }"
=> "It worked!"
>> "#{ flash[:danger] }"
=> "It failed."
7.4.3 実際のユーザー登録
本章での学び
データベースのリセット
$ rails db:migrate:reset
で、データベースの内容をリセットできる。
yokoyan:~/workspace/sample_app (sign-up) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0026s
== 20170417215343 CreateUsers: migrated (0.0028s) =============================
== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
-> 0.0016s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0017s) ====================
== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
-> 0.0011s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0012s) ================
確かにユーザ数が0になっていることを確認。
>> User.count
(0.3ms) SELECT COUNT(*) FROM "users"
=> 0
Rails Tutorialユーザで登録すると、緑色でメッセージが表示されることを確認。
画面のHTMLソースを確認すると、alert-success
クラスが表示されている。
<div class="alert alert-success">Welcome to the Sample App!</div>
演習1
Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。
find_byメソッドを使ってメールアドレスで検索する。
>> User.find_by(email: "example@railstutorial.org")
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-05-03 00:16:34", updated_at: "2017-05-03 00:16:34", password_digest: "$2a$10$qH4zt2NtBpRAAP3JUiqha.uMvZOAN5oZYv5TNn04V/1...">
演習2
自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
自分のメールアドレスで登録。
Gravatarで登録したユーザ画像が表示されることを確認。
7.4.4 成功時のテスト
本章での学び
assert_differenceアサーション
Railsテスティングガイドによると、
yieldされたブロックで評価された結果である式の戻り値における数値の違いをテストする。
とのこと。
assert_difference 'User.count', 1 do
とすることで、assert_differenceの処理前後で、User.count(第一引数)の値が、1(第二引数の値)異なっているかをテストすることができる。
follow_redirect!メソッド
Railsテスティングガイドによると、
単一のリダイレクトレスポンスに従う。
とのこと。
チュートリアル内にも以下のように説明されている。
POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド
つまり、follow_redirect!
は、assert_difference
内で、users_path
へのPOSTリクエスト(URL:/users、アクション:create)を送信した結果(レスポンス)を見て、controllerで指定しているリダイレクト先のuser_url @user
(ユーザ登録完了後のユーザ画面)へ移動している。
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password" } }
end
follow_redirect!
def create
・・・略・・・
if @user.save
#保存の成功をここで扱う
flash[:success] = "Welcome to the Sample App!"
redirect_to user_url @user
そのため、follow_redirect!
の後、特定のユーザを表示するページ(URL:
/users/ユーザID)が表示されることを期待して、users/show
テンプレートのテストを行っている。
test "valid signup information" do
・・・略・・・
follow_redirect!
assert_template 'users/show'
end
Userのルーティング、Userのshowアクション、show.html.erbビューがそれぞれ正常に動いているかをテストすることができる。
yokoyan:~/workspace/sample_app (sign-up) $ rails test
Running via Spring preloader in process 2559
Started with run options --seed 43581
20/20: [===================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.33079s
20 tests, 50 assertions, 0 failures, 0 errors, 0 skips
演習1
7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。
assert_notアサーション
Railsテスティングガイドによると、
testはfalseであると主張する。
つまり、問題文にあるように、flashの中身が空ではないことを主張できれば良い。
作成したテスト
test "valid signup information" do
・・・略・・・
assert_not flash.empty?
end
実行結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (sign-up) $ rails test
Running via Spring preloader in process 2662
Started with run options --seed 206
20/20: [===================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.15631s
20 tests, 51 assertions, 0 failures, 0 errors, 0 skips
演習2
本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
content_tagヘルパー
Railsドキュメントによると、
タグを動的に生成
HTMLとERBが混ざってしまう場合などに使用するとすっきり表現できる
とのこと。
作成したコード
このコードを、
<div class="alert alert-<%= message_type %>"><%= message %></div>
こう変える。確かに読みやすい。
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
テストがgreenになることを確認。
リファクタリングってこうしてやっていくんだなあ。
yokoyan:~/workspace/sample_app (sign-up) $ rails test
Running via Spring preloader in process 3788
Started with run options --seed 51425
20/20: [==========================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.11761s
20 tests, 51 assertions, 0 failures, 0 errors, 0 skips
演習3
リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
対象箇所をコメントアウトする。
#redirect_to user_url @user
テストがredになることを確認。
yokoyan:~/workspace/sample_app (sign-up) $ rails test
Running via Spring preloader in process 3857
Started with run options --seed 14535
ERROR["test_valid_signup_information", UsersSignupTest, 0.9199776879977435]
test_valid_signup_information#UsersSignupTest (0.92s)
RuntimeError: RuntimeError: not a redirect! 204 No Content
test/integration/users_signup_test.rb:36:in `block in <class:UsersSignupTest>'
20/20: [==========================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.10693s
20 tests, 49 assertions, 0 failures, 1 errors, 0 skips
動作確認後はコメントアウトを戻す。
演習4
リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
該当箇所を修正する。
def create
・・・略・・・
#if @user.save
if false
#保存の成功をここで扱う
flash[:success] = "Welcome to the Sample App!"
redirect_to user_url @user
else
render 'new'
end
end
テストがredになることを確認。
yokoyan:~/workspace/sample_app (sign-up) $ rails test
Running via Spring preloader in process 4076
Started with run options --seed 63186
FAIL["test_valid_signup_information", UsersSignupTest, 1.072253059828654]
test_valid_signup_information#UsersSignupTest (1.07s)
"User.count" didn't change by 1.
Expected: 1
Actual: 0
test/integration/users_signup_test.rb:30:in `block in <class:UsersSignupTest>'
FAIL["test_invalid_signup_information", UsersSignupTest, 1.1198141309432685]
test_invalid_signup_information#UsersSignupTest (1.12s)
Expected at least 1 element matching "div#error_explanation", found 0..
Expected 0 to be >= 1.
test/integration/users_signup_test.rb:17:in `block in <class:UsersSignupTest>'
20/20: [==========================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.12681s
20 tests, 42 assertions, 2 failures, 0 errors, 0 skips
コンソールのエラーから読み取れる原因は、2点。
- データベースへの保存処理がないため、エラーのクラスが表示できていないため
- User.countが1増えていないため
あと、今回のテストのエラーログには出ていないが、以下もあると思う。
- 期待値である/user/showにリダイレクトしていない(show.html.erbではなく、new.html.erbを表示してしまっている)
おわりに
ついにユーザ登録機能が動きました!
ずいぶんWEBアプリケーションっぽくなってきました。
これからもPOSTやGETのHTTPリクエストと、
対応する名前付きルートとアクションを意識しながら、
何の処理が動くのかを考えながら作っていきます。
いやー、Rails楽しいなあ。