#近況報告
エンジニア転職成功しました。YouTubeもはじめました。
著者略歴
YUUKI
ポートフォリオサイト:Pooks
現在:RailsTutorial2周目
#第7章 難易度 ★★★ 6時間
挫折しないRailsチュートリアルの進め方を先にお読みください↓↓
#7ユーザー登録
Userモデルが出来上がったので、この章ではユーザー登録機能を追加する。
具体的には、HTMLフォームを使ってWebアプリケーションに登録情報を送信、ユーザーを新規作成してデータベースに保存、さらにユーザー登録後のプロフィールページの作成、そして統合テストの追加を行う。
##7.1ユーザーを表示する
まずはプロフィールページを作成する。
モックアップは以下
出典:図 7.1: この節で作成するユーザープロフィールのモックアップ
ユーザーの名前と画像を表示する。
バージョン管理を使っているので、まずはトピックブランチを作成する。
$ git checkout -b sign-up
###7.1.1デバッグとRails環境
この章からアプリケーションのデータベースから取り出した情報を使って各プロフィールページの表示をカスタマイズする。
いわゆる「動的なページの作成」だが、その準備として、Webサイトのレイアウトにデバッグ情報を追加する。
<DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
ビュー内ではこのように、ビルドインのdebugメソッド
とparams変数
を使って、デバッグ情報を表示することができる。
また、if文を用いて、開発環境のみで動作するように記述している。
デバッグ情報は、なるべく開発環境でのみ表示させた方が良い
###Railsの3つの環境
Railsには
- テスト環境(test)
- 開発環境(development)
- 本番環境(production)
の3つの環境がデフォルトで装備されている。
例えば、Rails Consoleのデフォルト環境はdevelopment
である。
$ rails c
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false
RailsにはRailsオブジェクトがあり、それには環境の論理値(boolean)を取るenvという属性がある。
それに、それぞれの環境を渡してクエスチョンで確かめる。
test?ではテスト環境ではtrueを返し、それ以外の環境ではfalseを返す。
テストの環境のデバッグなど、他の環境でconsoleを実行する場合、環境をパラメータとしてconsoleスクリプトに渡すことができる。
$ rails console test
Running via Spring preloader in process 18352
Loading test environment (Rails 5.1.6)
>> Rails.env
=> "test"
>> Rails.env.test?
=> true
Railsサーバーでもデフォルトではdevelopmentが使われるが、他の環境を明示的に実行することもできる。
$ rails s --environment production
なお、アプリケーションを本番環境で実行するには本番環境用のDBが利用できないと実行できないので
$ rails db:migrate RAILS_ENV=production
このように本番データベースを作成する。
ここで、Heroku上での環境を確認してみる。
$ heroku run rails console
irb(main):001:0> Rails.env
=> "production"
irb(main):002:0> Rails.env.production?
=> true
このように、Herokuで実行されるアプリケーションは本番環境であることが分かる。
ここで、デバッグ表示用のCSSを整える。
@mixin box_sizing {
-moz-box-sizeing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* debug用表示*/
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
ここでSassのミックスイン機能を使っている。
@mixin
の後にセレクタを指定して書いたCSSは、@include セレクタ
で要素に適用させることができる。
つまり
/* debug用表示*/
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
-moz-box-sizeing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
このように変換される。
実際にデバッグが表示されたか確認してみる。
ちなみに
---
controller: static_pages
action: home
これはparamsに含まれている内容で、YAML形式で書かれている。
上記の書き方では、コントローラとページ、アクションを一意に指定している。
####演習
1:aboutページのデバック情報を確認。
controller: static_pages
action: about
2:最初のUser情報を変数userに格納し、puts user.attributes.to_yaml
とy user.attributes
の実行結果を比較
puts user.attributes.to_yaml
の場合
>> puts user.attributes.to_yaml
---
id: 1
name: YUUKI
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &1 2018-12-23 20:59:49.589763000 Z
zone: &2 !ruby/object:ActiveSupport::TimeZone
name: Etc/UTC
time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &3 2018-12-23 21:35:02.139347000 Z
zone: *2
time: *3
password_digest: "$2a$10$AxSaG6sZMlG/lLCye8qiDeqLEBuYPsltBx5glmWhpU.d2sAwjOrsW"
=> nil
y user.attributes
の場合
>> y user.attributes
---
id: 1
name: YUUKI
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &1 2018-12-23 20:59:49.589763000 Z
zone: &2 !ruby/object:ActiveSupport::TimeZone
name: Etc/UTC
time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &3 2018-12-23 21:35:02.139347000 Z
zone: *2
time: *3
password_digest: "$2a$10$AxSaG6sZMlG/lLCye8qiDeqLEBuYPsltBx5glmWhpU.d2sAwjOrsW"
=> nil
書き方が違うだけで同じコードが返ってきたことが分かる。
###7.1.2Usersリソース
ユーザープロフィールページを作成するには、データベースにユーザーが登録されてる必要がある。
前章でRails Cosnoleを使いユーザーレコードを登録したので、確認してみる。
$ rails c
Running via Spring preloader in process 6929
Loading development environment (Rails 5.1.6)
>> User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 1
>> User.first
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "YUUKI", email: "mhartl@example.com", created_at: "2018-12-23 20:59:49", updated_at: "2018-12-23 21:35:02", password_digest: "$2a$10$AxSaG6sZMlG/lLCye8qiDeqLEBuYPsltBx5glmWhpU....">
Console上で表示されたユーザー情報を、Webアプリケーション上で表示するのが目的。
RESTアーキテクチャの習慣に従い、GET,POST,PATCH,DELETEの4つのリクエストをアサインする。
RESTの原則に従う場合、リソース(作成/表示/更新/削除)への参照はリソース名とユニークなIDを使うのが一般的。
つまり、リソース名と一意のIDを組み合わせて参照する。
ここで、コントローラにてshowアクションを使う。
(showアクションは/users/1
のようなurlの中身の処理を行う)
ユーザーをリソースとみなす場合、id=1のユーザーを参照する場合は/users/1というURLに対してGETリクエストを発行するということを意味する。
ただし、現時点では/users/1
にアクセスしてもエラーになる。
このURLを有効にするため、config/routes.rb
に以下の行を追加。
Rails.application.routes.draw do
get 'users/new'
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
このresources :users
によって、多数の
- 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) | ユーザーを削除するアクション |
出典:表 7.1: リスト 7.3のUsersリソースが提供するRESTfulなルート
resources :users
を使うことで、ルーティングが有効になる。
ルーティングが有効になったので、コントローラに対応したビューを作成し記述していくが、前回ジェネレータを使った時とは異なり、ビューは手動で作成する必要がある。
今回は、app/views/users/show.html.erb
というRailsの標準的な場所にshowファイルを作成。
showファイル=/users/id
と割り当てられているので、showファイルはユーザー情報を表示するためのビューとなる。
<%= @user.name %> <%= @user.email %>
@userをUsersコントローラ内のshowアクションで定義し、ユーザーオブジェクトを代入する。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
この部分に注目
@user = User.find(params[:id]) #paramsで:idパラメータを受け取る(/users/1にアクセスしたら1を受け取る)。DBからid:1のレコードをfindメソッドで取り出す
この(params[:id])
で、ルーティングでURLリクエスト(今回はGET)を受けたid(今回は1)を引数として受け取り、同じidの値を持つユーザーレコードを探し出し(User.find
)、@userに代入している。
これをshowメソッドで一纏めにし、コントローラがshow.html.erb
ビューに返して、帰ってきたビューをブラウザに返す(/users/1で表示)
MVCモデルの挙動が曖昧であればこちらを参考に
また、**findメソッドは自動的に文字列型の1
を整数型の1
に変換する点にも注意。**こうして数値の1のidデータを取り出すことができる。
ここで、上記の通りになっているかページにアクセスして、デバッグ情報を確認してみる
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
controller: users
action: show
id: '1'
permitted: false
/users/:id
から1を取得、
usersコントローラのshowアクションでidを1で取得出来ていることがわかる。
####演習
埋め込みRubyを使って、showページにマジックカラムの値を表示、さらにTime.now
の結果を表示してみる。
<%= @user.name %> <%= @user.email %> <br>
<%= @user.created_at %> <%= @user.updated_at %> <br> <!-- @userの作成日時と更新日時を表示 -->
<%= Time.now <%> <!-- 現在の時刻を表示 -->
###7.1.3 debuggerメソッド
debugメソッドは便利だが、もっと直接的にデバッグする方法もある。
それがdebuggerメソッド
これはbyebug gemによるメソッドで、
コンソールに差し込んで使うことができる
def show
@user = User.find(params[:id]) # paramsで:idパラメータを受け取る(/users/1にアクセスしたら1を受け取る)
debugger
end
差し込んだ後、ブラウザから/users/1にアクセスし、Railsサーバーを立ち上げターミナルで見てみる。
(ちなみにbyebug gemは開発環境とテスト環境でのみ適用されるようgemで指定されている)
(byebug) @user.name
"YUUKI"
(byebug) @user,email
*** SyntaxError Exception: (byebug):1: syntax error, unexpected end-of-input, expecting '='
@user,email
^
nil
(byebug) @user.email
"mhartl@example.com"
(byebug) params[:id]
"1"
こんな感じに、デバッグをターミナルで扱うことができる。
byebugプロンプトから抜け出すには、control + D
コマンドを打つ。
デバッグが終わったらdebuggerを削除しておこう。
今後Railsアプリケーションの中でよく分からない挙動があったら、debuggerを差し込んで調べてみる。
トラブルが起こっていそうなコードの近くに差し込むのコツらしい。
####演習
1:debuggerでshowアクションのparamsハッシュの中身をYAML形式で表示してみる。
puts params[:id].to_yaml
--- '1'
nil
2:newアクションの中にdebuggerを差し込み、@userに格納されたデータを確認する。
(byebug) @user
nil
###7.1.4 Gravatar画像とサイドバー
Gravatarを使うことで、プロフィール写真をアップロードする時の面倒な作業や写真が欠けるトラブル、画像の置き場所の悩みを解決する。
Gravatarの使い方は、ユーザーのメールアドレスを組み込んだGravatar専用の画像パスを構成するだけで、
対応するGravatarの画像が自動的に表示される。
<% provide(:title, @user.name ) %> <!-- @user.nameを:titleに格納-->
<h1>
<%= gravatar_for @user %> <!-- gravatar_forというヘルパーメソッドでユーザー(user)の画像を表示、@userで表示しているビューのユーザー情報を表示 -->
<%= @user.name %> <!-- 表示しているビューのユーザーの名前(nmae)を表示-->
</h1>
このユーザー表示用のビューでは、gravatar_for
というヘルパーメソッドを定義している。
@userにはコントローラで定義したように、DBから取り出したユーザー情報が格納されている。
デフォルトでは、ヘルパーファイルで定義されているメソッドは自動的に全てのビューで利用できる。
ここでは、利便性を考えgravatar_for
をUsersコントローラ
に関連付けられているヘルパーファイルに置くことにする。
GravatarのURLはユーザーのメールアドレスをMD5でハッシュ化している。
Rubyでは、Digestライブラリのhexdigestメソッドを使うとMD5のハッシュ化が実現できる。
>> email = "MHARTL@example.COM"
=> "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
メールアドレスは大文字小文字を区別しないが、MD5ハッシュでは大文字小文字が区別されるので、downcaseメソッドで小文字化している。
(前章のコールバック処理で小文字化されたメールアドレスを使用している為、変換しなくても結果は変わらないが、将来的にgrvatar_forメソッドが別の場所から呼び出される可能性を考えると、ここで小文字化する必要がある。)
users_helperは結果的にこのようなコードになる。
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase) #小文字化したuserメールをMD5でハッシュ化し、変数に代入
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" #gravatarのURLに変数展開,画像用の変数に代入
image_tag(gravatar_url, alt: user.name, class: "gravatar") #画像を表示し、user.nameを画像が表示されない時用に、classにgravatarを指定)
end
end
プロフィールページを表示してみる。
userにはデータが入っていないが、その場合でもgravatar側でデフォルトの画像が表示される。
アプリケーションでGravatarを利用できるようにする為、まずはupdate_attributes
でDB上のユーザー情報を更新してみる。
$ rails c
>> user = User.first
>> user.update_attributes(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar")
=> true
emailにexample@railstutorial.org
を渡したことで
RailsTutorialのデフォルトロゴが表示された
(これはTutorial側で用意されているもの)
これからモックアップに近づける為に、ユーザーのサイドバーを作る。
asideタグを使って実装。
<% provide(:title, @user.name ) %> <!-- @user.nameを:titleに格納-->
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %> <!-- gravatar_forというヘルパーメソッドでユーザー(user)の画像を表示、@userで表示しているビューのユーザー情報を表示 -->
<%= @user.name %> <!-- 表示しているビューのユーザーの名前(nmae)を表示-->
</h1>
</section>
</aside>
</div>
row
やcol-md-4
はBootstrapで指定されているクラス。
さらに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;
}
####演習
1:Gravatar上にアカウントを作成し、自分のメールアドレスと適当な画像を紐付けてみる。
メールアドレスをMD5ハッシュ化して、紐付けた画像が表示されているか確認。
>> user = User.find(1)
>> user.update_attributes(name: "Example user",email: "yuukitetsuyanet@gmail.com" , password: "foobar", password_confirmation: "foobar" )
2:gravatar_forヘルパーを変更して、size
をオプション引数として受け取れるようにしてみる。
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, options = { size: 80 }) #メソッドの定義と指定した2つの引数を渡す
gravatar_id = Digest::MD5::hexdigest(user.email.downcase) #小文字化したuserメールをMD5でハッシュ化し、変数に代入
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"#gravatarのURLに変数展開,画像用の変数に代入
image_tag(gravatar_url, alt: user.name, class: "gravatar") #画像を表示し、user.nameを画像が表示されない時用に、classにgravatarを指定)
end
end
3:Ruby 2.0から導入された新機能「キーワード引数」を使ってオプション引数を実現する。
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, size: 80 ) #メソッドの定義と指定した2つの引数を渡す
gravatar_id = Digest::MD5::hexdigest(user.email.downcase) #小文字化したuserメールをMD5でハッシュ化し、変数に代入
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"#gravatarのURLに変数展開,画像用の変数に代入
image_tag(gravatar_url, alt: user.name, class: "gravatar") #画像を表示し、user.nameを画像が表示されない時用に、classにgravatarを指定)
end
end
メソッドの引数にsize:という名前をつけ、80を渡す。
これを呼び出す時、size
として呼び出しが可能。
##7.2 ユーザー登録フォーム
ユーザー登録フォームのモックアップは以下
ユーザー登録フォーム作成にはform_forというヘルパーメソッドを使う。
/signupのルーティングは、Usersコントローラのnewアクションに紐付けられているので(routes.rb)
ここではform_forの引数で必要となるUserオブジェクトを作成する。
def new
@user = User.new # Userオブジェクトを作成
end
早速newビューに記述・・・の前に、SCSSでレイアウトを整える。
/* フォーム */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
@include
でbox_sizingミックスインを再利用している点に注目。
これでフォーム用の見た目を楽に整えられる。
HTMLを記述。
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row"></div>
<div class="col-md-6 col-md-offset-3">
<!-- formの送信先を指定 -->
<%= form_for (@user) do |f| %>
<!-- form作成-->
<%= 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 "送信", class: "btn btn-primary" %>
<% end %>
</div>
</div>
表示を確認
####演習
1::nameを:nomeに置き換えてエラーメッセージを確認。
:nomeを受け取るf.text_fieldが未定義のメソッドですよと怒られている。
2:ブロックの変数fを全てfoobarに置き換えてみて、結果が変わらないことを確認。
しかし、変数名をfoobarとするのはよくない。
その理由は
- foobarにはメタ構文変数で意味を持たない名前として認識される
- fの方が無駄がなく
form
の略称としてわかりやすい
点が挙げられる。
###7.2.2 フォームHTML
先程書いたフォームの内容を理解するために、1つ1つみていく。
<%= form_for (@user) do |f| %>
<!-- 内容 -->
<% end %>
ここでは、form_forの引数に@userを取り、ブロック変数にfを取っている。
このfオブジェクト(form_for (@user))は、
**HTMLフォーム要素(テキストフィールド、ラジオボタン、パスワードフィールドなど)**に対応するメソッドが呼び出されると、@userの属性を設定するために特別に設計されたHTMLを返す。
つまり
<%= f.label :name %> #Userモデルのname属性を設定
<%= f.text_field :name %> #ラベル付きのテキストフィールド要素を作成
ということ。
HTMLのソースコードを見て、nameの部分にどんなHTMLが作成されているのか確認。
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name">
labelタグは入力フォームのラベルを指定できるので、今回はName
というラベル名が表示される。
label forは"user_name"
の部分がid属性と関連付けられる。
つまり、1行目の文字のNameをクリックすると、2行目と関連付けられて、inputタグが動き、フォームに入力できるようになる。
type属性では、入力フォーム用に最適化された文字を入力できるよう指定する。今回はtext
にして、テキスト文を入力することを指定している。
type属性に渡す属性値は様々あるが、基本的なものは以下の3つ。
- text テキスト文書を入力
- email emailアドレスを入力
- password パスワードを入力(文字がセキリュティ上隠蔽される)
name属性では、初期化したハッシュをparams変数経由で構成する。
このハッシュは、入力された値に基づいてユーザーを作成する時に使われるが、後に詳解する。
ここで重要なのが、formタグ(form_tag)の中身。
<form class="new_user" id="new_user"
action="/users" accept-charset="UTF-8" method="post">
formタグは非常に便利で、@userオブジェクトを作ったら自動的にそれのクラスがUserであることを認識し、
さらに、@userの各属性(nameやemailなど)に値がない場合、postメソッドを使ってフォームを構築すべきだと判断する。
postメソッドはPOSTリクエスト(HTTPリクエスト、CRUDのうちの一つ)を行うもので、GETと違い、
サーバーを通してDBにデータを登録する際に使用する。
action="/users"
となっているのは、/usersに対して、という意味なので、
/usersというURLにPOSTリクエストを送信している。
さらに、formタグで生成されたHTMLを見ていると、下の方にこんな記述が
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="pmH4IJiFhWfSrorn34l+cU6OEO6IFV61zC8GRqf44pHcDts1FlDmWbB9cLJYeWrs8KV8eNQqyJblQT2jHMb/Ug==">
このコードはRails内部で使われるコードだが、後に詳解する。
####演習
1:Learnなんちゃらでは HTMLを全て手動で書き起こしている。なぜformタグを使わなかったのか?
Railsを使っておらず、HTMLのみではフォームを送信することができないから
##7.3 ユーザー登録失敗
フォームを理解するにはユーザー登録の失敗の時が最も参考になる、ということで
無効なデータ送信を受け付けるユーザー登録フォームを作成、ユーザー登録フォームを更新してエラーの一覧を表示する。
モックアップはこれ
出典:図 7.14: ユーザー登録が失敗したときのモックアップ。
###7.3.1 正しいフォーム
ルーターにてresources :users
を定義したことにより、自動的にRailsアプリケーションがRESTful URIに応答するようになった。
/usersへのPOSTリクエストはcreateアクションに送られる。
つまり、createアクションにてフォーム送信を受け取っているので、このアクション内でUser.newでユーザーオブジェクトを作成し、モデル内のテーブルに保存すれば良い。
そして、保存後に再度ユーザー登録ページを表示できるようにしていく。
ユーザー登録の流れを分かりやすくまとめると
①ユーザー登録フォームで新規登録
②フォームに書かれた内容を/usersへPOSTリクエストで送信、createアクションに送る
③createアクションにてフォームの内容をモデルのテーブルに保存(DBに格納)、保存失敗時にエラーメッセージを表示
④保存後にユーザー登録フォームへ自動で移動。
さて、先程のformタグのHTMLを見て上記の流れができるかどうか確認。
<form action="/users" class="new_user" id="new_user" method="post">
OK。
さて、usersコントローラ内のcreateアクションにユーザー保存用のコードを書いていこう。
def create
@user = User.new(params[:user]) #newビューにて送ったformをparamsで受け取り、ユーザーオブジェクトを生成、@userに代入
if @user.save #保存に成功した場合
else
render 'new' #保存が失敗したらnewビューへ戻す
end
end
この時点でフォームを送信しても失敗する。
ここで、デバッグ情報のパラメーターハッシュのuserの部分を見てみる。
"user"=>{"name"=>"",
"email"=>"",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
userハッシュの中にname
email
password
password_confirmation
という四つのハッシュが入っている。
この構造をhash-of-hashesと言う。
つまり、フォームの内容で送られたそれぞれの値は、createアクションで定義したparamsの[:user]にシンボルで渡されている。
[:user]はuserハッシュで、さらにその中にハッシュが入っていてそれぞれの属性値が渡されるという訳。
そして、上記のキー(nameやemailなど)は、newビューのinputタグにあった属性の値になる。
例えば、emailの場合
<input type="email" name="user[email]" id="user_email">
name属性を見てみると、user[email]となっている。
これは、userハッシュの中のemailキーの値を送りますよ、という意味。
そして、
createアクションのparams[:user]にて、emailのキーに値として受け取っている。
(ここ重要)
params[:user]の:userはシンボルなので、データの値を受け取れる点もポイント。
@user = User.new(params[:user])
このようなコードは、アスアサインメントと呼ぶ。
これが実質的には
@user = User.new(name: "Foo Bar", email: "foo@invalid",
password: "foo", password_confirmation: "bar")
このようなコードになっている。(フォームで送られた値を受け取ってるから)
ちなみに、昔のRailsでは
@user = User.new(params[:user])
だけでも動いたが、悪意のあるユーザーによってアプリケーションのデータベースが書き換えられてしまう危険性(これをマスアサインメント脆弱性と言う)があるため、このコードのみだとエラーとし、このエラーを解消するためのコードを次項で追記する。
###7.3.2 Strong Parameters
paramsハッシュ全体を初期化する行為はセキュリティ上危険。
何故なら、ユーザーが送信したデータを丸ごとUser.new
に渡しているの同じだから。
例えば、Userモデルに管理者権限のadmin属性を定義し、admin='1'という値をparams[:user]の一部に紛れ込ませて渡せば、この属性をtrueにすることが出来る。
この場合、paramsハッシュがまるごとUser.newに渡されてしまうと、どのユーザーでもadmin='1'をWebリクエストに紛れ込ませるだけで、Webサイトの管理者権限を奪うことが出来る。
これをRails4.0ではコントローラ層で、Strong Parametersというテクニックを使うことが推奨されている。
Strong Parametersを使うことで、必須のパラメータと許可されたパラメータを指定することが出来る。
さらに、上のようにparamsハッシュを丸ごと渡すとエラーが発生するので、Railsはデフォルトでマスアサインメントの脆弱性から守られるようになった。
早速paramsハッシュを弄る。
def create
@user = User.new(user_params) # newビューにて送ったformをuser_paramsで受け取り、ユーザーオブジェクトを生成、@userに代入
if @user.save
# 保存の成功をここで扱う。
else
render 'new' # 保存が失敗したらnewビューへ戻す
end
end
private # 外部から使えない(Usersコントローラ内のみ)部分
def user_params # paramsハッシュの中を指定する。(requireで:user属性を必須、permitで各属性の値が入ってないとparamsで受け取れないよう指定)
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
privateキーワード以降のコードは、privateメソッドの物であることを強調するため、インデントを一段深くしてある。
こうすることで、privateの境目を簡単に見分けれるようにしている。
これで、フォームに何も値を入力しないで送信ボタンを押すと、createアクションにて/users
に飛ぶ
ユーザー登録フォームは動いているが
- 間違った値でも送信出来る
- 新しいユーザーが作成されない
この二つの問題点が発生しているため、これを改善していく。
####演習
1:/signup?admin=1
にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
admin: '1'
controller: users
action: new
permitted: false
###7.3.3 エラーメッセージ
ユーザーに登録に失敗した場合の最後の手順として、エラーメッセージを追加する。
Railsでは、エラーメッセージはUserモデルの検証時に自動的に生成してくれる。
例えば、無効なメールアドレスと短すぎるパスワードを保存しようとしてみる。
$ rails c
>> user = User.new(name: "Foo Bar", email: "foo@invalid", password: "dude", password_confirmation: "dube")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password confirmation doesn't match Password", "Password is too short (minimum is 6 characters)"]
このように、保存しようとして失敗したらfalseを、オブジェクトにerrors.full_messages
を渡すとエラーメッセージを返してくれる。
errors.full_messages
オブジェクトは、エラーメッセージの配列を持っている点に注目。
EmailとPasswordのバリデーションが引っ掛かったことを教えてくれる。
このメッセージをブラウザで表示するには、ユーザーのnewページでエラーメッセージのパーシャルを出力する。
さらに、BootstrapのCSSクラスも一緒に追加することで、フォームが色付される。
<!-- formの送信先を指定 -->
<%= form_for (@user) do |f| %>
<%= render 'shared/error_messages' %> <!-- エラーメッセージ用のパーシャルを表示 -->
<!-- form作成-->
<%= f.label :name %> <!-- Userモデルの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' %>
Railsの慣習として、複数のビューで使われるパーシャルは、
専用のディクレトリ**shared
**に置く。
早速ディレクトリを作成し、直下にパーシャルファイルを作成
<% if @user.errors.any? %> <!-- エラーメッセージが空だった(エラーを起こしていない)場合true -->
<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| %> <!-- エラーメッセージの配列をブロック変数msgに繰り返し代入 -->
<li><%= msg %></li> <!-- ブロック変数msgを表示(エラー文の値を一つ一つ表示) -->
<% end %>
</ul>
</div>
<% end %>
ここで重要なメソッドが2つある。
countメソッド
とany?メソッド
>> user.errors.count
=> 2
countメソッドはエラーの数を返す。
>> user.errors.empty?
=> false
>> user.errors.any?
=> true
any?メソッドはempty?の逆で、userオブジェクトが空の場合trueを返す
さらに、もう一つ、pluralize
という英語専用のテキストヘルパーが新たに登場。
helperオブジェクトを通してRails Consoleから試してみる
>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"
pluralizeの最初の引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返す。
このメソッドの背後に強力な活用形生成があり、不規則活用を含むさまざまな単語を複数形にする。
>> helper.pluralize(2, "woman")
=> "2 women"
>> helper.pluralize(3, "erratum")
=> "3 errata"
woman
は女性という意味で、women
は女性達。
erratum
は正誤という意味で、errata
は正誤表。
pluralizeを使うことでコードは以下のようになる。
<%= pluralize(@user.errors.count, "error") %>.
第一引数のエラーにcountメソッドでエラーの数を数え、
エラーの数に応じて、第二引数のerror
が、
単数形のerror
が複数形のerrors
を返す。
これによって、1 errors
などという英語の文法に合わない文字列を避けることができる
さらに、エラーメッセージにerror_explanation
というidを指定している点に注目。
Railsは、無効な内容の送信によって元のページに戻されると、
CSSクラスfield_with_errors
を持ったdivタグでエラー箇所を自動的に囲んでくれる。
これは、newビューで定義したform_forタグで囲んだ中のフィールドの部分を、
自動でfield_with_errors
というdivタグで囲むというもの。
う〜む・・・便利なようなお節介なような?w
ここでは、Sassの@extend関数を使ってBootstrapのhas-error
というCSSクラスを適用してみる。
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
/* f.〇〇_fieldの部分に適用されるCSS */
.field_with_errors {
@extend .has-error;
.form-control {
color: $state-danger-text;
}
}
これで、無効なユーザー登録情報を送信した時の
エラーメッセージの見栄えが良くなる。
ちなみに、エラーメッセージはモデルの検証時に生成されるので、
メールアドレスのスタイルやパスワードの最小文字列などを変更すると、メッセージも自動的に変更される。
(validationがきちんと掛かった状態で生成されるから、エラーメッセージに反映されるってことだね)
また、この時presence: true
(文字列が空)のバリデーションもhas_secure_password
(パスワードハッシュ化)によるバリデーションも、空のパスワード(nil)を検知してしまうため、
ユーザー登録フォームで空のパスワードを入力すると2つの同じエラーメッセージが表示されてしまう
こういった冗長なメッセージは直接修正もできるが、
それより、後に追加するallow_nil: true
というオプションで簡単に解決できる。
とりあえず実際にエラーが効くか確かめる。
いっぱい出てきたw
確かに同じエラーが二つ出てるね。
####演習
1:最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみる。
2:送信前(/signup)と送信後(/users)でURLが変わる理由は?
送信前はルーティングで/signupはnewアクションと割り当てられているが、送信時にUserオブジェクトを生成した際に、resources :users
により、createアクションが呼び出され、割り当てられている/usersが表示される。
####7.3.4 失敗時のテスト
昔はフォームのテストは毎回手動で行う必要があったが、
Railsではフォーム用テストを書くことができ、こういったプロセスを自動化できる。
今回は無効な送信をした時の正しい振る舞いについてテストを書いていく。
まずは、新規ユーザー登録用(signup)の統合テストを生成する。
$ rails g integration_test users_signup
Running via Spring preloader in process 17907
invoke test_unit
create test/integration/users_signup_test.rb
コントローラの慣習である「リソース名は複数形」に因んで、ファイル名はusers_signup
(このファイルは後の送信が成功する時のテストでも使う)
$ rails c
>> User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 1
>>
このテストでは、
ユーザー登録ボタンを押す→ユーザーが作成されない
ことを確認する。
これを確認するため、まずはユーザー数をカウント。
(このテストの背後で動作するcountメソッドは、Userを含むあらゆるActive Recordクラスで使える)
以前DBをリセットしたので、ユーザー数は1。
ここでは、assert_select
で関連ページのHTML要素をテストしていく。
これにより、今後うっかり要素を変更してしまっても気づけるようにする。
まずは、getメソッド
でユーザー登録ページにアクセス
get signup_path
フォーム送信をテスト
test "invalid signup information" do # 新規登録が失敗(フォーム送信が)した時用のテスト
get signup_path # ユーザー登録ページにアクセス
assert_no_difference 'User.count' do # User.countでユーザー数が変わっていなければ(ユーザー生成失敗)true,変わっていればfalse
post users_path, params: { user: { name: "", # signup_pathからusers_pathに対してpostリクエスト送信(/usersへ)、paramsでuserハッシュとその下のハッシュで値を受け取れるか確認
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
end
get
やpost
は半角スペースを空け、そこにURL(ここでは名前付きルートを使用)を記述することで、取得したり送れたりできる。
このテストでは、ユーザーの新規登録前と後でユーザー数が変わらないかどうかをテストしている。
フォームの値を送信(受け取り)できないバリデーションが効いてるかのテストなので、送信できなければtrue、送信できればfalseである。
同じコードで表すと
before_count = User.count
post users_path
after_count = User.count
assert_equal before_count, after_count
ちなみに、上記ではコgetメソッドを使っていないが、実は上記のコードではユーザー登録ページにアクセスしなくても、直接postメソッドを呼び出してユーザー登録を行うコードを書ける。
これは両方のメソッドを呼び出しての検証だからできる技で、このアイデアを使って再びテストを記述してみる。
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
test "invalid signup information" do # 新規登録が失敗(フォーム送信が)した時用のテスト
get signup_path # ユーザー登録ページにアクセス
assert_no_difference 'User.count' do # User.countでユーザー数が変わっていなければ(ユーザー生成失敗)true,変わっていればfalse
post users_path, params: { user: { name: "", # signup_pathからusers_pathに対してpostリクエスト送信(/usersへ)、paramsでuserハッシュとその下のハッシュで値を受け取れるか確認
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_template 'users/new' # newアクションが描画(つまり@user.save失敗)されていればtrue、なければfalse
end
end
これでテストがパスする。
$ rails t
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
####演習
1:エラーメッセージが描画されているかテスト
assert_template 'users/new' # newアクションが描画(つまり@user.save失敗)されていればtrue、なければfalse
assert_select 'div#error_explanation' # divタグの中のid error_explanationをが描画されていれば成功
assert_select 'div.field_with_errors' # divタグの中のclass field_with_errorsが描画されていれば成功
2:/signup
と/users
のURLを、ルーティングを通していずれのURLも/signup
にしてみる。また、何故テストはGREENなのか?
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'
get '/signup', to: 'users#create' #createアクションの時`signup`を取得
<%= form_for @user,url: signup_path do |f| %> <!-- 送信先をurlが/signup、usersコントローラのcreateアクションに@userを送り、fブロックに代入-->
A.フォーム送信時のテストで送り先をusers_pathにしているが、RESTfulルートで/users→createアクションで失敗した時にnewビューの呼び出しに成功しているから
3:テストで/signup_pathからget、postが出来ることをテスト
get signup_path # ユーザー登録ページにアクセス
assert_no_difference 'User.count' do # User.countでユーザー数が変わっていなければ(ユーザー生成失敗)true,変わっていればfalse
post signup_path, params: { user: { name: "", # signup_pathからusers_pathに対してpostリクエスト送信(/usersへ)、paramsでuserハッシュとその下のハッシュで値を受け取れるか確認
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
1 tests, 4 assertions, 0 failures, 0 errors, 0 skips
4:newビューのform_forから第二引数(オプション)を削除し、前の状態に戻してみる。
しかし、テストはパスする。
これは問題(signup_pathに送ってないのにテストが通るから)なので、assert_selectを使ってテストを追加し、バグを検知できるようにする。
assert_select 'form[action="/signup"]' # formタグの中に`/signup`があれば成功
##7.4 ユーザー登録成功
実際にフォームでユーザー情報を送信し、
データベースに保存させ、
さらにユーザー登録成功のページにリダイレクト、登録されたユーザープロフィールも表示してみる。
モックアップはこれ
出典:図 7.19: ユーザー登録に成功した画面のモックアップ
###7.4.1 登録フォームの完成
保存成功時の動作を書いていく。
現時点では、有効な情報でフォーム送信してもエラーが発生してしまう。
これは、データをcreateアクションに送った際、対応するビューがないことが原因。
(5.8ms) commit transaction
No template found for UsersController#create, rendering head :no_content
🔼rails sで起動しているサーバーのログ情報
ではビューを作成すればよいのか?
確かにビューを作成すればエラーは解消されるが、今回はredirect_to
メソッドを使い、保存成功と同時に/users
へ移動させる。
def create
@user = User.new(user_params) # newビューにて送ったformをuser_paramsで受け取り、ユーザーオブジェクトを生成、@userに代入
if @user.save
redirect_to @user #(user_url(@user) つまり/users/idへ飛ばす
else
render 'new'
end
end
redirect_to @user
と言うコードから、Railsが自動で
redirect_to user_url(@user)
と言うコードを推察してくれている。
ちなみにこのコードをもう少し詳しくみると
redirect_to user_url(id: @user.id)
である。
モデルオブジェクト内で定義された@userをreidirect_to で引数に取るとき、Railsは自動でオブジェクトのid取得して、それを返してくれる。
つまり、このコードで/users/id
という部分が形成されるのである。
この部分については、こちらの表を参考に。
ヘルパー名(_path) | ヘルパー名(_url) | 戻り値(パス) |
---|---|---|
users_path | users_url | /users |
user_path(id) | user_url(id) | /users/:id |
new_user_path | new_user_url | /users/new |
edit_user_path | edit_user_url(id) | /users/:id/edit |
####演習
1:Railsコンソールを使って、実際にユーザーを作成できるか確認。
$ rails c
>> user = User.new(name: "bakaya", email: "tommmm@yahoo.co.jp", password: "tamagoyaki", password_confirmation: "tamagoyaki")
>> user.save
=> true
2:redirect_to user_url(@user)
と、redirect_to @user
が同じ結果になることを確認。
A.省略できるのでその通り。
###7.4.2 flash
新規ユーザー作成完了後に、作成しましたと言うウェルカムメッセージを表示させる。
(登録完了後のみウェルカムメッセージは表示させ、リロードしたら消えるようにする)
Railsではこれをflash
と言う特殊な変数を使って表示させる。
これはハッシュのように扱う。
def create
@user = User.new(user_params) # newビューにて送ったformの中身(nameやemailの値)をuser_paramsで受け取り、ユーザーオブジェクトを生成、@userに代入
if @user.save
flash[:success] = "ようこそYUUKIのサイトへ" # flashの:successシンボルに成功時のメッセージを代入
redirect_to @user #(user_url(@user) つまり/users/idへ飛ばす(https://qiita.com/Kawanji01/items/96fff507ed2f75403ecb)を参考
else
render 'new'
end
end
flash内のキー値をのちにapplication
ビューに記述する。
その前に、Rails Consoleで挙動を確認。
>> flash = { success: "It worked!", danger: "It falied." }
=> {:success=>"It worked!", :danger=>"It falied."}
>> flash.each do |key, value|
?> puts "#{key}"
>> puts "#{value}"
>> end
success
It worked!
danger
It falied.
=> {:success=>"It worked!", :danger=>"It falied."}
>>
このパターンに則って、flash内容をWebサイト全体に渡って表示できるように、applicationビューに以下の内容を記述。
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
alert-の後に続くCSSクラス名を、メッセージの種類によって変更するにしている。
flash[:success]にはようこそYUUKIのサイトへ
を代入しているので、
success
ようこそYUUKIのサイトへ
の二つが埋め込まれる。
さらに、ようこそYUUKIのサイトへ
は、alert-successクラスが定義された状態で表示されるので、
きちんとCSSが適用された状態で成功メッセージを表示してくれる
この性質を利用し、キーの内容によって異なったCSSクラスを適用させることができる。
後の章で詳解する、flash[:danger]
を使って、ログイン失敗のメッセージを表示のCSSをカスタマイズする。
(alert-dangerになる)
BootstarpCSSは、このようなflashのクラス用に
- alert-succcess(成功)
- alert-info(情報)
- alert-warning(注意)
- alert-danger(危険)
の四つが用意されている。
RailsTutorialではこのCSSを全て使っていく。
例えば
flash[:success] = "ようこそYUUKIのサイトへ"
は
<div class="alert alert-success">ようこそYUUKIのサイトへ</div>
という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? %> <!-- 開発環境時のみ、debugを表示 -->
</div>
</body>
flash変数の内容を全体ビューのapplicationに追加。
####演習
1:コンソールで#"#{:successs}"を表示
>> puts "#{:success}"
success
シンボルが文字列に変わって表示された
2:flashの中身の結果を表示
>> flash = {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
ユーザー登録後の表示を確認。
ページを再読み込みしてみる。
flash変数の特性上、flashは一回しか表示されない。
####演習
1:Railsコンソールを使って本当にユーザーは作成されたのか確認
$ rails c
>> User.first
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "manuke", email: "manuke@gmail.com", created_at: "2018-12-29 10:35:50", updated_at: "2018-12-29 10:35:50", password_digest: "$2a$10$E.LHYqv0IMT9h.M7WlWYoeywCyH6SRPVAabAzmQihwg...">
2:自分のメールアドレスを登録し、Gravatarで登録した画像が表示されているか確認
よし。
###7.4.4 成功時のテスト
今回のテストでは、データベースの中身が正しいかどうか検証する。
すなわち有効な情報を送信して、ユーザーが作成されたかどうかを確認。
今回はassert_difference
というメソッドを使う。
assert_no_difference
の逆。
test "valid signup information" do # 新規登録が成功(フォーム送信)したかのテスト
get signup_path # signup_path(/signup)ユーザー登録ページにアクセス
assert_difference 'User.count', 1 do # User.countでユーザー数をカウント、1とし、ユーザー数が変わったらtrue、変わってなければfalse
post users_path, params: { user: { name: "Example User", # signup_path(/signup)からusers_path(/users)へparamsハッシュのuserハッシュの値を送れるか検証
email: "user@example.com",
password: "password",
password_confirmation: "password" } }
end
follow_redirect! # 指定されたリダイレクト先(users/show)へ飛べるか検証
assert_template 'users/show' # users/showが描画されているか確認
end
このメソッドでは、no_differenceと同様、第一引数(User.count)でユーザー数をカウントしている。
さらに、第二引数でユーザー数が変わったか確認している。
no_differenceとは逆で、
- 変わったらtrue(1→2)
- 変わってなければfalse(1→1)を返す。
つまり、ユーザーをusers_path(createアクションの/users)に送れて、ユーザーが作成できたか(1→2)
を確認している。
さらに、follow_redirect!
とメソッドを使い、POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動している。
この場合は、リダイレクト先はusers/show
である。
これらのテストは、ユーザープロフィールに関するほぼすべてをテストできている。
なぜなら、ページにアクセスした際のエラーなどをすべて確認しているから。
このように、端から端までテストしていることをE2E(End to End)テスト
と呼ぶ。
アプリケーションの重要な機能をカバーしてくれるので、こういった理由から統合テストが便利だと言われている。
####演習
1:flashに対するテストを書く。(とりあえず空かどうかのテスト)
assert_not flash.blank? # flashが空ならfalse,空じゃなければtrue
2:applicationビューのflashメッセージをcontent_tagを使って見やすくする。
<%= content_tag(:div, message, class: "alert alert-#{message_type}" ) %> <!-- ブロック変数に代入されてるflashメッセージを表示-->
3:redirect_toをコメントアウトするとテストが失敗することを確認
ERROR["test_valid_signup_information", UsersSignupTest, 0.22806750599920633]
test_valid_signup_information#UsersSignupTest (0.23s)
RuntimeError: RuntimeError: not a redirect! 204 No Content
test/integration/users_signup_test.rb:31:in `block in <class:UsersSignupTest>'
4:@user.saveをfalseに置き換えたとして、asset_differenceのテストでどのようにしてバグを検知するのか?
A.保存失敗だとすると、保存前(get)で読み込んだassert_differenceで、User.countの値が変わらず(1→1)、postでcreate
アクションに送るも、paramsでuserハッシュの値を受け取れず失敗する。
##7.5 プロのデプロイ
これまでのデプロイと違って、実際に本番環境でデータを操作できるようにする。
具体的には、ユーザー登録をセキュアにするために、本番用アプリケーションに重要な機能を追加していく。
その後、デフォルトのWebサーバーをプロが使うWebサーバーに置き換える
まずはこの時点までの変更をmasterブランチにマージ
git add -A
git commit -m "Finish user signup"
git checkout master
git merge sign-up
###7.5.1 本番環境でのSL
これまでと違うのは、ユーザー登録フォームでデータを送信するという点。
ユーザーの名前やパスワードなどがネットワーク越しに流されていく。
このような個人情報はセキュアにしないと、ハッカーなんかに情報を盗まれてしまうため非常に危険。
情報セキュリティの三原則「機密性の確保」を行うためにも、Secure Sockets Layer(SSL)を使って、
通信を暗号化する。
SSLを有効化するのは簡単で、production.rb
と言う本番環境の設定ファイルの1行を修正するだけで済む。
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
config.force_ssl = true
これを行うことで、config
に本番環境ではSSLを強制するようにと命令できる。
Herokuではデフォルトの設定でもSSLを使用できるが、ブラウザに強制する訳ではない。
(https://でもhttp://でもアクセスできる)
また、サーバー用のSSLをセットアップを行う。
通常はドメイン毎にSSL証明書を購入する必要があるが、HerokuではHeroku上でサンプルアプリケーションを動かし、HerokuのSSL証明書を使えば簡単にセットアップできる。
ただし、独自ドメイン取得にはSSL証明書の購入が必要である。
###7.5.2 本番環境用のWebサーバー
Herokuデフォルでは、Rubyだけで実装されたWEBrickと言うWebサーバーを使っているが、
多くのトラフィックを捌けないので、今回はPumaを使う。
最初にpuma gemをGemfileに追加する必要があるが、Rails 5ではデフォルトの設定でも使えるので、今回は必要なし。
pumaファイルに以下を記述するだけ
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
on_worker_boot do
# Worker specific setup for Rails 4.1+
# See: https://devcenter.heroku.com/articles/
# deploying-rails-applications-with-the-puma-web-server#on-worker-boot
ActiveRecord::Base.establish_connection
end
あとはProcfileと呼ばれる、Heroku上でPumaのプロセスを走らせる設定ファイルを作成するだけ。
web: bundle exec puma -C config/puma.rb
###7.5.3 本番環境へのデプロイ
$ rails t
$ git add -A
$ git commit -m "Use SSL and the Puma webserver in production"
$ git push
$ git push heroku
$ heroku run rails db:migrate
ユーザー登録機能が使えればOK.
ちなみにHerokuデプロイ時に
remote: You have not declared a Ruby version in your Gemfile.
remote: To set your Ruby version add this line to your Gemfile:
remote: ruby '2.4.5'
Rubyのバージョンを明示的に指定しろ、と怒られている。
Rubyのバージョンを常に最新状態に保っていようとすると、インストールなどに時間を取られてしまう。
という訳で、多少のバージョン違いは気にしない方が良いとのこと。
ただし、仕事でHerokuを使ったアプリケーションを動かす場合、GemfileでRubyのバージョンを明示していた方が良いらしい。
これにより、開発環境(development)と本番環境(production)の互換性を最大限高めることができるそうな。
####演習
1:本番環境でhttp://でアクセスしても強制的にhttps://に置換されることを確認
2:Gravatarの画像は正しく表示されているか
#単語集
- デバッグ
プログラムのバグを探し当て、原因を取り除く作業のこと。
デバッグを支援するソフトウェアをデバッガと呼ぶ。
- Rails.erv.development?
Rails.env
コマンドは現在の環境を確認し、development?
で開発環境であればtrueを返す。
if文の場合、Rails.env.development?
がtrueになるのは開発環境のみなので、開発環境でのみ処理が実行される。
- ミックスイン
要素をグループ化できるSassの機能。複数の要素に適用することができる。
- YAML
階層構造で記述していく言語であり、Rubyではハッシュのような形式で書くことができる。
- to_yaml
RubyオブジェクトをYAML形式の文字列に変換するメソッド。
- show
RailsのREST機能が有効になっている場合、GETリクエストは自動的にshowアクションとして扱われる。つまり、コントローラで作成したshowメソッド内の処理が、ルーティングで割り当てたURLのページで実行される。
- params
URLから送られてきた値やフォームで入力した値(リクエスト情報)を取得できる。
使い方は
params[:パラメータ名] #URLが/users/1だとして、ルーティングで`get users/:id`としていれば、"1"を受け取る
- プロンプト
ターミナルでのコマンド入力待ち状態のこと。
- Gravatar
プロフィール写真をアップロードして、指定したメールアドレスと関連付けることでプロフィール写真を表示することのできるサービス。
- MD5
ハッシュ関数の1つ。128ビットのハッシュ値を生成する。
- Digest
メッセージダイジェストライブラリのこと。
Digest::Baseと同じようなインターフェースを持つ。
使い方
Digest::MD5::メソッド名(引数で値を渡す)
- aside
htmlにてサイドバーなどの表示を行うためのタグ。単独でも表示が可能。
- キーワード引数
引数に渡す値にキーワードを付けることができる。書き方は
def log(ab, yo: "aa")
puts "#{yo}"
end
- form_for
Active Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築する。
- hash-of-hash
ハッシュの中にハッシュがあるという意味。中は階層構造になっている。
- マスアサインメント
フォームから送信したデータを、オブジェクトを新規作成する際の引数に渡して、そのままデータベースに保存する行為。ただし、一斉保存すると不正なデータを登録することが可能となってしまうため、そこには脆弱性がある。
- admin
権限があるかどうかの属性。
- require
外部ファイルやライブラリを読み込める。
なお、POSTでフォーム送信した場合、paramsに対してparams.require(:user)
とすれば、
userハッシュの値を読み込むことができる。
使い方
params.require(:user) #userハッシュの中身を読み込んでいる
- permit
許可したいパラメータだけをフィルタしてくれるメソッド。
使い方
params.require(:user).permit(:name, :email ) #userハッシュの中のnameハッシュとemailハッシュの値のみ、許可されて受け取れる。
これ以外の値(例えばpasswordハッシュ)が送られても、許可せずエラーとなる(値を受け取れない)
これでマスアサインメントの脆弱性を阻止した状態でデータの値を送ることが出来る。
- assert_no_difference
引数の値が同じなら成功、変わったら失敗
- flash
簡単なメッセージを画面に表示する変数。登録完了メッセージやエラー文を表示させる時に使う。
使い方は
flash[:notice] = "エラーメッセージ"
- SSL
Secure Sockets Layerの略。
ローカルのサーバーからネットワークに流れる前に、大事な情報を暗号化する技術。