はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。
このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る35歳定年説)と、
新しいことに挑戦したいという思いから、
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。
これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
11.2.1 送信メールのテンプレート
本章での学び
アカウント有効化メールを送信するために、Action Mailerライブラリを使用して、メイラーを作成する。
【mailer】Userメイラーの作成
rails generate
で自動生成する。
viewのテンプレートが2つ生成される。
- テキストメール用
- HTMLメール用
yokoyan:~/workspace/sample_app (account-activation) $ rails generate mailer UserMailer account_activation password_reset
Running via Spring preloader in process 1731
Expected string default value for '--jbuilder'; got true (boolean)
create app/mailers/user_mailer.rb
invoke erb
create app/views/user_mailer
create app/views/user_mailer/account_activation.text.erb
create app/views/user_mailer/account_activation.html.erb
create app/views/user_mailer/password_reset.text.erb
create app/views/user_mailer/password_reset.html.erb
invoke test_unit
create test/mailers/user_mailer_test.rb
create test/mailers/previews/user_mailer_preview.rb
【mailer】メールテンプレートのカスタマイズ
デフォルトのfromアドレスを編集する。
なお、この値はアプリケーション全体で共通となる。
default from: 'noreply@example.com'
【mailer】メール送信処理の追加
自動生成された、account_activationメソッドを編集する。
ユーザー情報のインスタンス変数を作成し、user.email
宛にメールを送信する。
def account_activation(user)
@user = user
mail to: user.email, subject: "Account activation"
end
【view】テキストメールとHTMLメールビューのカスタマイズ
ユーザーの有効化URLには、有効化トークンを含める。
q5lt38hQDc_959PVoo6b7A
の文字列は、実装済みのnew_token
メソッドで生成した値。
Base64でエンコードされている。
なお、AccountActivationsコントローラのeditアクションでは、paramsハッシュでparams[:id]
として参照することができる。
http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit
また、URLからユーザーを特定するために、メールアドレスをクエリパラメータで追加する。
URLの末尾に?を付与して、キーと値のペアを記載する。
なお、URLに記載するメールアドレスの@は、URLで使えない文字列であるため
%40
でエスケープする必要がある。
Railsでは自動的にエスケープしてくれる。
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
上記を踏まえ、実装する。
テキストメールのテンプレート。
Hi <%= @user.name %>
Welcome to the Sample App! Click on the link below to active your account:
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
HTMLメールのテンプレート。
<h1>Sample App</h1>
<p>Hi <%= @user.name %></p>,
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
テストコードの作成
演習1
コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don’t panic!"をエスケープすると、どんな結果になりますか?
実行結果は以下の通り。
@や、'、!がエスケープされて表示される。
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 1755
Loading development environment (Rails 5.0.0.1)
>> CGI.escape('foo@example.com')
=> "foo%40example.com"
>>
>> CGI.escape("Don't panic!")
=> "Don%27t+panic%21"
>>
11.2.2 送信メールのプレビュー
本章での学び
特殊なURLにアクセスして、メールの内容をその場でプレビューする。
【environment】develop環境のメール設定変更
ホスト名を各自の環境に合わせて設定する。
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :test
host = 'xxxxxxxxxxxx.c9users.io'
config.action_mailer.default_url_options = { host: host, protocol: 'https' }
設定が完了したら、develop環境のサーバを再起動する。
【mailer】Userメイラーのプレビューファイルの修正
自動生成されたプレビューファイルは、そのままでは動かないため、ユーザー情報を追加する。
- DBの最初のユーザー
- 有効化トークン(メールテンプレート内で使用しているため省略不可)
def account_activation
user = User.first
user.activation_token = User.new_token
UserMailer.account_activation(user)
end
動作確認
ブラウザから、下記URLにアクセスする。
メイラーのプレビュー画面が表示される。
演習1
Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?
上記の動作確認の通り。
「Date」には、UTC時間が表示されているため、日本と9時間ずれてる。
11.2.3 送信メールのテスト
本章での学び
前項で実装した、メールプレビューのテストコードを作成する。
【test】Userメイラーのテストコードの作成
- テストユーザーとしてfixtureからmichaelを取得する
- テストユーザーの有効化トークンを生成する
- Userメイラーのアカウント有効化処理にテストユーザー情報を渡して、メールオブジェクトを作成する
- メールオブジェクトの件名をチェックする
- メールオブジェクトの送信先メールアドレスをチェックする
- メールオブジェクトの送信元メールアドレスをチェックする
- テストユーザーの名前が、メールの本文に含まれているかチェックする
- テストユーザーの有効化トークンが、メールの本文に含まれているかチェックする
- テストユーザーのアドレスがエスケープされて、メールの本文に含まれているかチェックする
上記を踏まえ実装する。
自動的に生成されるtest "password_reset"
については、12章で実装するためコメントアウトする。
(残しておくとテストがredになってしまう)
class UserMailerTest < ActionMailer::TestCase
test "account_activation" do
user = users(:michael)
user.activation_token = User.new_token
mail = UserMailer.account_activation(user)
assert_equal "Account activation", mail.subject
assert_equal [user.email], mail.to
assert_equal ["noreply@example.com"], mail.from
assert_match user.name, mail.body.encoded
assert_match user.activation_token, mail.body.encoded
assert_match CGI.escape(user.email), mail.body.encoded
end
【config】テストファイル内のドメイン名を設定する
テスト内でのドメインを設定する。
config.action_mailer.default_url_options = { host: 'example.com' }
動作確認
mailerのテストがgreenになることを確認。
yokoyan:~/workspace/sample_app (account-activation) $ rails test:mailers
Started with run options --seed 17823
1/1: [========================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00
Finished in 0.63635s
1 tests, 9 assertions, 0 failures, 0 errors, 0 skips
演習1
この時点で、テストスイートが greenになっていることを確認してみましょう。
テストスイートがgreenであることを確認。
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 2015
Started with run options --seed 24199
43/43: [======================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.04996s
43 tests, 188 assertions, 0 failures, 0 errors, 0 skips
演習2
リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。
CGI.escape
部分をコメントアウトする。
# assert_match CGI.escape(user.email), mail.body.encoded
assert_match user.email, mail.body.encoded
テストスイートがredになることを確認。
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 2134
Started with run options --seed 58681
FAIL["test_account_activation", UserMailerTest, 1.9940800210024463]
test_account_activation#UserMailerTest (1.99s)
Expected /michael@example\.com/ to match # encoding: US-ASCII
"\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0\r\nContent-Type: text/plain;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHi Michael Example\r\n\r\nWelcome to the Sample App! Click on the link below to active your account:\r\n\r\nhttp://example.com/account_activations/pFlo06HSkw3Yug79-uZaQg/edit?email=michael%40example.com\r\n\r\n\r\n\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0\r\nContent-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n <head>\r\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n <style>\r\n /* Email styles need to be inline */\r\n </style>\r\n </head>\r\n\r\n <body>\r\n <h1>Sample App</h1>\r\n\r\n<p>Hi Michael Example</p>,\r\n\r\n<p>\r\n Welcome to the Sample App! Click on the link below to activate your account:\r\n</p>\r\n\r\n<a href=\"http://example.com/account_activations/pFlo06HSkw3Yug79-uZaQg/edit?email=michael%40example.com\">Activate</a>\r\n\r\n </body>\r\n</html>\r\n\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0--\r\n".
test/mailers/user_mailer_test.rb:14:in `block in <class:UserMailerTest>'
43/43: [======================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.15642s
43 tests, 188 assertions, 1 failures, 0 errors, 0 skips
動作確認後は、コメントアウト部分を元に戻しておく。
11.2.4 ユーザーのcreateアクションを更新
本章での学び
作成したメイラーを使うために、createアクションを修正する。
【controller】ユーザー登録時に、アカウント有効化メールの送信処理を追加する
以下の処理を、
if @user.save
log_in(@user)
#保存の成功をここで扱う
flash[:success] = "Welcome to the Sample App!"
redirect_to user_url @user
else
以下のように修正する。
deliver_now
は、メールを送信するメソッド。
アカウント有効化メールを送信後、ログインするのではなく、TOPページにリダイレクトさせる
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:success] ="Please check your email to activate your account"
redirect_to root_url
else
【test】統合テストの修正
現時点では、テストがredになってしまう。
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1859
Started with run options --seed 60543
FAIL["test_valid_signup_information", UsersSignupTest, 1.6497548810002627]
test_valid_signup_information#UsersSignupTest (1.65s)
expecting <"users/show"> but rendering with <["user_mailer/account_activation", "layouts/mailer", "static_pages/home", "layouts/_rails_default", "layouts/_shim", "layouts/_header", "layouts/_footer", "layouts/application"]>
test/integration/users_signup_test.rb:37:in `block in <class:UsersSignupTest>'
43/43: [========================================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.49370s
43 tests, 186 assertions, 1 failures, 0 errors, 0 skips
失敗するテストを一時的にコメントアウトする。
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!
# assert_template 'users/show'
# assert_not flash.empty?
# assert is_logged_in?
end
テストがgreenになることを確認。
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1971
Started with run options --seed 16030
43/43: [========================================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.11845s
43 tests, 185 assertions, 0 failures, 0 errors, 0 skips
動作確認
ブラウザから、ユーザー登録を行う。
(test2ユーザーで登録を実施)
コンソールにメール送信ログが表示されていることを確認。
----==_mimepart_594c956b2e8ac_82a276bab859751
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi test2
Welcome to the Sample App! Click on the link below to active your account:
https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com
----==_mimepart_594c956b2e8ac_82a276bab859751
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<h1>Sample App</h1>
<p>Hi test2</p>,
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<a href="https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com">Activate</a>
</body>
</html>
演習1
新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?
コンソールログをより確認。
リダイレクト先のURLが下記の通りルートURLに302で送信されていることを確認。
Redirected to https://rails-tutorial-yokoyan.c9users.io/
Completed 302 Found in 747ms (ActiveRecord: 21.9ms)
有効化トークンの値は、authenticity_token"=>"BChjYdix9aSf0+wv9gPXrz3WoPJzZPKLVPE/bSqMMskkQkQ0cwXPbKmyr583JHM+0APDwxH37qT6+YbHNKWxwg==
Started GET "/" for 125.199.218.217 at 2017-06-23 04:13:13 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by StaticPagesController#home as HTML
Rendering static_pages/home.html.erb within layouts/application
Rendered static_pages/home.html.erb within layouts/application (320.5ms)
Rendered layouts/_rails_default.html.erb (100.5ms)
Rendered layouts/_shim.html.erb (0.4ms)
Rendered layouts/_header.html.erb (3.9ms)
Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 447ms (Views: 436.8ms | ActiveRecord: 0.0ms)
Started GET "/signup" for 125.199.218.217 at 2017-06-23 04:13:17 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#new as HTML
Rendering users/new.html.erb within layouts/application
Rendered shared/_error_messages.html.erb (0.4ms)
Rendered users/_form.html.erb (421.1ms)
Rendered users/new.html.erb within layouts/application (422.4ms)
Rendered layouts/_rails_default.html.erb (30.7ms)
Rendered layouts/_shim.html.erb (0.4ms)
Rendered layouts/_header.html.erb (0.7ms)
Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 473ms (Views: 460.4ms | ActiveRecord: 0.7ms)
Started POST "/signup" for 125.199.218.217 at 2017-06-23 04:13:30 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"BChjYdix9aSf0+wv9gPXrz3WoPJzZPKLVPE/bSqMMskkQkQ0cwXPbKmyr583JHM+0APDwxH37qT6+YbHNKWxwg==", "user"=>{"name"=>"test2", "email"=>"test2@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
(0.1ms) begin transaction
User Exists (5.9ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "test2@example.com"], ["LIMIT", 1]]
SQL (0.3ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?) [["name", "test2"], ["email", "test2@example.com"], ["created_at", 2017-06-23 04:13:30 UTC], ["updated_at", 2017-06-23 04:13:30 UTC], ["password_digest", "$2a$10$ox4DtTzrIjUOsCTFUe3dg.aqbZXJ1taP0HF.v6cTr6C4DUs3zImTK"], ["activation_digest", "$2a$10$OhYHKhyL8LqTJ/r2livKRuRTVhWSfXROncbmcmHmmWVmpINb9xE.S"]]
(14.9ms) commit transaction
Rendering user_mailer/account_activation.html.erb within layouts/mailer
Rendered user_mailer/account_activation.html.erb within layouts/mailer (6.1ms)
Rendering user_mailer/account_activation.text.erb within layouts/mailer
Rendered user_mailer/account_activation.text.erb within layouts/mailer (0.4ms)
UserMailer#account_activation: processed outbound mail in 150.2ms
Sent mail to test2@example.com (8.9ms)
Date: Fri, 23 Jun 2017 04:13:31 +0000
From: noreply@example.com
To: test2@example.com
Message-ID: <594c956b30451_82a276bab859864@yokoyan-rails-tutorial-4550834.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_594c956b2e8ac_82a276bab859751";
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_594c956b2e8ac_82a276bab859751
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi test2
Welcome to the Sample App! Click on the link below to active your account:
https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com
----==_mimepart_594c956b2e8ac_82a276bab859751
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<h1>Sample App</h1>
<p>Hi test2</p>,
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<a href="https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com">Activate</a>
</body>
</html>
----==_mimepart_594c956b2e8ac_82a276bab859751--
Redirected to https://rails-tutorial-yokoyan.c9users.io/
Completed 302 Found in 747ms (ActiveRecord: 21.9ms)
Started GET "/" for 125.199.218.217 at 2017-06-23 04:13:31 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
Rendering static_pages/home.html.erb within layouts/application
Rendered static_pages/home.html.erb within layouts/application (165.7ms)
Rendered layouts/_rails_default.html.erb (50.5ms)
Rendered layouts/_shim.html.erb (0.4ms)
Rendered layouts/_header.html.erb (3.6ms)
Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 229ms (Views: 227.6ms | ActiveRecord: 0.0ms)
演習2
コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。
コンソールから確認。
追加されたユーザーの有効化ステータスが、falseであることを確認。
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 2627
Loading development environment (Rails 5.0.0.1)
>> test2 = User.find_by(email: "test2@example.com")
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "test2@example.com"], ["LIMIT", 1]]
=> #<User id: 101, name: "test2", email: "test2@example.com", created_at: "2017-06-23 04:13:30", updated_at: "2017-06-23 04:13:30", password_digest: "$2a$10$ox4DtTzrIjUOsCTFUe3dg.aqbZXJ1taP0HF.v6cTr6C...", remember_digest: nil, admin: false, activation_digest: "$2a$10$OhYHKhyL8LqTJ/r2livKRuRTVhWSfXROncbmcmHmmWV...", activated: nil, activated_at: nil>
>>
?> test2.activated?
=> false
>>
おわりに
メールの送信処理の組み込みが完了しました。
メールテンプレートの作成や、メール送信処理そのものも、
Railsだと簡単に実装できます。
有効化トークンを埋め込んだURLによる認証は、ECサイト等でも当たり前にある機能であるため、
仕組みが非常に勉強になりました。