7.1.1 デバッグとRails環境
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}.
.
.
/* miscellaneous */
.debug_dump {clear: both;float: left;width: 100%;margin-top: 45px; -moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
演習
1:ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。
このページを表示するとき、どのコントローラとアクションが使われ
ていたでしょうか? paramsの内容から確認してみましょう。
controller: static_pages action: about
2:Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。
その後、puts user.attributes.to_yamlを実行すると何が表示されますか?
ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。
user = User.first
表示は同じ
HTTP リクエストURL アクション 名前付きルート 用途 GET /users index users_path すべてのユーザーを一覧するページ GET /users/1 show user_path(user) 特定のユーザーを表示するページ GET /users/new new new_user_path ユーザーを新規作成するページ (ユーザー登録) POST /users create users_path ユーザーを作成するアクション GET /users/1/ edit edit edit_user_path(user) id=1のユーザーを編集するページ PATCH /users/1 update user_path(user) ユーザーを更新するアクション DELETE /users/1 destroy user_path(user) ユーザーを削除するアクション Usersリソースが提供するRESTfulなルート
演習
埋め込みRubyを使って、マジックカラム (created_atとupdated_at)
の値をshowページに表示してみましょう埋め込みRubyを使って、
Time.nowの結果をshowページに表示してみましょう。
ページを更新すると、その結果はどう変わっていますか? 確認してみてください。
<%= @user.name %>, <%= @user.email %>, <%= @user.updated_at %>, <%= @user.created_at %>, <%= Time.now %>
演習
1:showアクションの中にdebuggerを差し込みブラウザから/users/1
にアクセスしてみましょう。
その後コンソールに移り、putsメソッドを
使ってparamsハッシュの中身をYAML形式で表示してみましょう。
a = params <ActionController::Parameters {"controller"=>"users", "action"=>"show", "id"=>"1"} permitted: false> (byebug) puts a.to_yaml --- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: show id: '1' permitted: false nil
2:newアクションの中にdebuggerを差し込み、/users/new にアクセスし
てみましょう。@userの内容はどのようになっているでしょうか?
@user nil
Digestライブラリのhexdigestメソッドを使うと、
MD5のハッシュ化が実現できます。
>> email = "MHARTL@example.COM" >> Digest::MD5::hexdigest(email.downcase) => "1fda4469bcbec3badf5418269ffc5968"
gravatar_for
ヘルパーメソッドを定義するapp/helpers/users_helper.rb
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
ユーザーのサイドバーの最初のバージョンを作りましょう。ここではasideタグを使って実装します。このタグはサイドバーなどの補完コンテンツの表示に使われますが、単独で表示することもできます。rowクラスとcol-md-4クラスも追加しておきます。これらのクラスはBootstrapの一部です。ユーザー表示ページを変更した結果をリスト 7.10に示します。
show
ビューにサイドバーを追加するapp/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
app/assets/stylesheets/custom.scss
.
.
.
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
margin-top: 20px;
&:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
演習
定義したgravatar_forヘルパーを変更して、
sizeをオプション引数として受け取れるようにしてみましょう。
うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。
重要:この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう
gravatar_for
ヘルパーにオプション引数を追加するapp/helpers/users_helper.rb
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, options = { size: 80 })
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
3:オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリストを置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。
gravatar_for
ヘルパーにキーワード引数を追加するapp/helpers/users_helper.rb
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, size: 80)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
7.2.1 form_for
を使用する
new
アクションに@user
変数を追加するapp/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
end
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
app/assets/stylesheets/custom.scss
.
.
.
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
演習
試しに、リスト 7.15にある:name
を:nome
に置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?
undefined method `nome'
試しに、ブロックの変数f
をすべてfoobar
に置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobar
とするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
変数fは “form” のfを使用している。”f.label”等がHTMLフォーム要素に対応することがわかるようにする。”foobar”はメタ構文変数(意味を持たない名前)として認識されるため適さない。ほかはhogeとかhugaとか
7.2.2 フォームHTML
これまで見てきた次のようなコード (マスアサインメントと呼びます) は、
@user = User.new(params[:user])
実際にはこのようなコードとほぼ同じである、ということです。
@user = User.new(name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar")
演習
/signup?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。
Sassの@extend関数を使ってBootstrapのhas-errorというCSSクラスを適用してみます。
#error_explanation { color: red; ul { color: red; margin: 0 0 30px 0; } } .field_with_errors { @extend .has-error; .form-control { color: $state-danger-text; } }
演習
1:リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。
users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path (中略) assert_template 'users/new' assert_select 'div#error_explanation' assert_select 'div.alert' end end
2:ユーザー登録フォームのURLは /signup ですが、無効なユーザー登録データを送付するとURLが /users に変わってしまいます。これはリスト 5.43で追加した名前付きルート (/signup) と、RESTfulなルーティング (リスト 7.3) のデフォルト設定との差異によって生じた結果です。リスト 7.26とリスト 7.27の内容を参考に、この問題を解決してみてください。うまくいけばどちらのURLも /signup になるはずです。あれ、でもテストは greenのままになっていますね…、なぜでしょうか? (考えてみてください)
ユーザー登録のルーティングにPOST
リクエストを追加するconfig/routes.rb
post '/signup', to: 'users#create'
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user, url: signup_path) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
いずれのURLも /signup となる。
3:リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。
users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post signup_path, params: { user: { name: "", (後略)
4:リスト 7.27のフォームを以前の状態に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action=”/signup”]’という部分が存在するかどうかに着目してテストしてみましょう。
リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してもテストはGREEN。
7.4.2 flash
flashという特殊な変数を使います。この変数はハッシュのように扱います。Railsの一般的な慣習に倣って、:successというキーには成功時のメッセージを代入するようにします。
演習
1:コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう
例えば”#{:success}”といったコードを実行すると、どんな値が返ってきますか?
"#{:success}" => "success"
2:先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。
flash = { success: "It worked!", danger: "It failed." } "#{flash[:success]}" => "It worked!" >> "#{flash[:danger]}" => "It failed."
演習
1:Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。
動作確認するだけ
2:自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
動作確認するだけ
演習
1:実装したflashに対するテストを書いてみてください。どのくらい細かくテストする
かはお任せ。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。
テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。
users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest (中略) test "valid signup information" do (中略) assert_not flash.empty? end end
2:本文中でも指摘しましたが、flash用のHTMLは読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
.
.
.
</html>
3:リダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
create
アクションapp/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
#redirect_to @user
else
render 'new'
end
end
4:リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
create
アクションapp/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.false
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end