個人的リマインド用
参考
Ruby on Rails チュートリアル プロダクト開発の0→1を学ぼう
ユーザー登録
ユーザーを表示する
最終目標はユーザーのプロフ写真と基本ユーザーデータ、マイクロポストの一覧を表示すること。
デバッグとRails環境
動的なページを追加する準備として、Webサイトのレイアウトにデバッグ情報を追加する。ビルトインのdebugメソッドとparams変数を使って、各プロフィールページにデバッグ用の情報が表示されるようになる。
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>
</html>
本番環境にデプロイしたアプリケーションではデバッグ情報を表示したくないので、if Rails.env.development?を書く。
Railsには環境が3つあり、テスト環境(test)と開発環境(development)、そして本番環境(production)がデフォルトで装備されている。
デバッグ情報
#<ActionController::Parameters {"controller"=>"static_pages", "action"=>"home"}
permitted: false>
これはparamsのリテラル表示であり、基本的にはハッシュである。ここではそのページのコントローラとアクションを表示している。
Usersリソース
ユーザープロフィールページを作成するには、その前にデータベースにユーザーが登録されていなければならない。登録ページがない状態でどう登録するのか。今回はrails consoleで登録することで解決している。
次はユーザー情報をWebアプリケーションに表示することが目標である。ここではRESTアーキテクチャの慣習に従い、データの作成、表示、更新、削除をリソース(Resources)として扱う。
RESTの原則に従う場合、リソースへの参照はリソース名と一意のIDを使うことが普通。今回はユーザーリソースのid=1を参照したいので、/users/1というURLに対して、GETリクエストを発行するということ。RailsのREST機能が有効になっていればGETリクエストは自動的にshowアクションになる。
そして/users/1のURLを有効にするためには以下のコードを追加する。
config/routes.rb
Rails.application.routes.draw do
root "static_pages#home"
get "/help", to: "static_pages#help"
get "/about", to: "static_pages#about"
get "/contact", to: "static_pages#contact"
get "/signup", to: "users#new"
resources :users # ←ここ!
end
上記のコードを追記すれば、/users/1だけではなく、ユーザーのURLを生成するための多数の名前付きルーティングと共に、RESTfulなUsersリソースで必要となる、全てのアクションが利用できるようになる。
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) | ユーザーを削除するアクション |
ただしルーティング先のページはまだない。そのため最小限のページを作成する。
今回がジェネレータを使ってないのでファイルは手動で作成。
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>
ユーザー表示ビューが正しく動作するには、Usersコントローラないのshowアクションに対応する@user変数を定義する必要がある。今回はUserモデルのfindメソッドを使ってデータベースからユーザーを取り出す。
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
Usresコントローラにリクエストが正常に送られると、params[:id]の部分はユーザーidの1に置き換わる。
この時点で画面を表示し、デバッグ情報を見てみると
{"controller"=>"users", "action"=>"show", "id"=>"1"}
debuggerメソッド
debug情報をより直接的に取り出すのがdebuggerメソッド。
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger # ←ここ!
end
def new
end
end
これを記述したら、ブラウザから/users/1にアクセスし、Railsサーバーを立ち上げているターミナルを見てみる。いろいろ見れるので詳しくは本文の3文の1らへんをチェック。
Gravatar画像とサイドバー
今回はGravatarを使ってアバター画像をプロフィールに導入する。これは、プロフィール写真をアップロードして、指定したメールアドレスと関連づけることができるサービスである。
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
ヘルパーファイルで定義されているメソッドは、デフォルトで自動的に全てのビューで利用できる。ここでは、利便性を考え、garavatar_forをUsersコントローラに関連づけられているヘルパーファイルに置くことにする。
GravatarのURLはユーザーのメールアドレスをMD5という仕組みでハッシュ化している。Rubyでは、Digestライブラリのhexdigestメソッドを使うとMD5のハッシュ化を実現できる。
>> email = "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
メールアドレスは大文字と小文字を区別しないが、MD5は区別するのでdowncaseメソッドを使って、hexdigestの引数を小文字に変換している。
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
サイドバーに関してはCSSをごちゃごちゃして、どうにかする。
form_withを使用する
次はユーザー登録フォームを作る。Railsではform_withヘルパーメソッドを使う。
ユーザー登録ページの/signupのルーティングは、Usersコントローラのnewアクションに紐づけられているので、次のステップはfom_withの引数で必要となるUserオブジェクトを作成すること。必要となる@user変数の定義は、次のようになる。
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
end
htmlはこんな感じ
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_with(model: @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>
ユーザー登録フォームのHTML
HTMLの構造を細かく見ていく。
<%= form_with(model: @user) do |f| %>
.
.
.
<% end %>
fはformのこと。このfオブジェクトは、HTMLフォーム要素に対応するメソッドが呼び出されると、@userの属性を設定するために特別に設計されたHTMLを返す。
<%= f.label :name %>
<%= f.text_field :name %>
このようにすると、Userモデルのname属性を設定する、ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成する。生成されたものはこんな感じ
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />
ユーザー作成で重要なのがinputごとにある特殊なname属性。
これらのname属性値のおかげで、Railsはparams変数から初期化ハッシュを構成できる。
次に重要なのはformタグ自身。Railsはformタグを作成する時に@userオブジェクトを使う。すべてのRubyオブジェクトは自分のクラスを知っているので、Railsは@userのクラスがUserであることを認識する。また、@userは新しいユーザーなので、Railsはpostメソッドでフォームを構築すべきだと判断する。
<form action="/users" accept-charset="UTF-8" method="post">
/usersに対してHTTPのPOSTリクエストを送るという意味。
ユーザーの登録失敗
無効なデータ送信を受け付けるユーザー登録フォームを作成し、ユーザー登録フォームを更新してエラー一覧を表示する。
正しいフォーム
/usersへのPOSTリクエストはcreateアクションに送られる。ここではcreateアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存(または失敗)し、再送信できるようにユーザー登録ページを表示するという方法で機能を実装する。
まずはユーザー登録の失敗に対応できるcreateアクションを作成する。
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
end
status: :unprocessable_entityについて。これはHTTPステータスコード433 Unprocessable Entityに対応するもので、Turbo(後々登場)を用いて通常のHTMLをレンダリングする場合に必要。
この時点で無効なデータを送信すると、エラー画面に飛ばされる。
ここでデバッグ情報の内パラメーターハッシュのuserの部分を詳しく見てみる。
"user" => { "name" => "Foo Bar",
"email" => "foo@invalid",
"password" => "[FILTERED]",
"password_confirmation" => "[FILTERED]"
}
このハッシュはUsersコントローラにparamsとして渡される。このparamsハッシュには各リクエストの情報が含まれている。このハッシュのキーがinputタグにあったname属性の値になる。
例:user[name(←ここの部分)]
ちなみに、デバッグ情報ではハッシュのキーが文字列になっているが、Railsではシンボルになっている点に注意。
@user = User.new(params[:user])
上のコードは
@user = User.new(name: "Foo Bar", email: "foo@invalid",
password: "foo", password_confirmation: "bar")
上のコードと同じ意味。
昔はこのコード(@user = User.new(params[:user]))でも動いたが、悪意のあるユーザーにアプリケーションのデータベースが書き換えられないために、Strong Parametersを使うようになった。
Strong Parameters
paramsハッシュ全体を初期化するという行為はセキュリティ上極めて危険。これはユーザーが送信したデータを丸ごとUser.newに渡していることになる。例えば管理者権限みたいなものがあったとき、それを簡単に書き換えられたら恐ろしい。そこでstrong Parametersをコントローラ層で使うことで問題は解決できる。
params.require(:user).permit(:name, :email, :password, :password_confirmation)
この場合、paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外は認めないようにする。
これらのパラメータはuser_paramsのような補助メソッドの形で使うのが定番。このメソッドはUsersコントローラ内部のみで実行され、Web経由で外部ユーザーに公開する必要はないため、privateキーワードを使い外部から使えないようにする。
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params) # ←ここと
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
private # ←ここ!
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
エラーメッセージ
RailsはエラーメッセージをUserモデルの検証時に自動生成してくれる。
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?> password: "dude", password_confirmation: "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]
errors.full_messagesオブジェクトは、エラーメッセージの配列を持っている。
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_with(model: @user) 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>
Railsの慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」 に置かれる。
mkdir app/views/shared
touch app/views/shared/_error_messages.html.erb
app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(@user.errors.count, "error") %>.
</div>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
countメソッドは数を数え、any?はempty?の逆。これらはRubyの配列に対してそのまま使える。
またpluralizeは第一引数に数字、第二引数に文字列で、数字に合わせた形で文を作る。
失敗時のテスト
最初に新規ユーザー登録用の統合テストを作成する。
rails g integration_test users_signup
このテストでは、ユーザー情報が無効の場合はユーザー登録ボタンを押してもユーザーが作成されないことを確認する。ここではUser.countを使う。
まずはgetメソッドを使ってユーザー登録ページにアクセス。
get signup_path
フォーム送信をテストするには、POSTリクエストをusers_pathに送信する必要がある。
assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
createアクションのUser.newで期待されているデータを、params[:user]というハッシュまとめている。
assert_no_differenceは違いがないという意味。なのでフォーム送信後もユーザーカウントが変わらないことをテストしている。
また、正しいレスポンスコードが返され、正しいテンプレートがレンダリングされることを検証するために、以下のアサーションを追加。
assert_response :unprocessable_entity
assert_template 'users/new'
完成形はこちら
test/integration/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 users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_response :unprocessable_entity
assert_template 'users/new'
end
end
ユーザー登録成功
次はフォームが有効な場合に新規ユーザーを実際にデータベースに保存できるようにし、ユーザー登録フォームを完成させる。
保存に成功するとデータベースに登録され、登録されたユーザーのプロフにリダイレクトする。
現状有効な情報で送信してもエラーになる。理由としては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', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
flash
登録時にウェルカムメッセージを表示したい。そんなときにflashを使う。
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!" # ←ここ!
redirect_to @user
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
flash変数に代入したメッセージは、リダイレクトした直後のページで表示できるようになる。今回はflash内に存在するキーがあるかを調べ、キーがあればその値を全て表示するよう修正する。
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
successならclassはalert-successになり、cssが適用される。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
.
.
.
</body>
</html>
成功時のテスト
今回の目的はデータベースの中身が正しいかどうか検証すること。すなわち、有効な情報を送信して、ユーザーが作成されたことを確認する。
assert_difference 'User.count', 1 do
post users_path, ...
end
test/integration/users_signup_test.rb
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
.
.
.
test "valid signup information" do
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!
assert_template 'users/show'
end
end
follow_redirect!に注目。このメソッドは、POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド。
ユーザー登録が成功した後には、UserのルーティングとUserのshowアクション、そしてshow.html.erbがそれぞれ正しく動いている必要がある。