ActionMaier
Railsに標準で組み込まれている、メールを送信してくれる機能。text形式とhtml形式のテンプレートメールを生成する事ができ、宛先や件名含めコントローラから簡単に生成する事ができる。メイラーの生成は下のコマンド。後ろの二つは追加するメソッド名を指定している。
$ rails generate mailer UserMailer account_activation password_reset
実行すると、app/views/user_mailer/account_activation.text.erb
とapp/views/user_mailer/account_activation.html.erb
の様に2つのテンプレートが生成される(password_reset)も同様。
アカウント有効化リンクの生成
#前回 edit_user_url(user) #生成されるurl http://www.example.com/users/1/edit #今回 edit_account_activation_url(@user.activation_token, ...) http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit
上の生成されたリンク内のトークンへeditアクション内ではparams[:id]
でアクセスできる。
url内@
のエスケープ
上で生成したurlにメールアドレスも含める場合、@
をエスケープする必要があるが、これはメソッドが自動で行ってくれる。
edit_account_activation_url(@user.activation_token, email: @user.email) #生成されるurl account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
editアクション内ではparams[:email]
でアクセスできる。
RSpecでのmailerのテスト
RSpecを普通に入れいていれば、チュートリアルの説明と同じ形のテストファイルがRSpec内にも生成される。ので、同じ手順に沿って進めて問題ない。
メソッドの抽象化
チュートリアルではauthenticated?
メソッドを例に挙げている。今まで実装していた、リメンバートークンを比較するメソッドは下。
# トークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end
これをsend
メソッドを用いる事で、柔軟に変数へアクセス可能となる。文字列、シンボルを渡す事が可能。
def authenticated?(attribute, token) digest = self.send("#{attribute}_digest") #モデル内に定義されているのでselfは省略可能 return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end
form_with使用時のエラー表示
下のサイトに書いてありますが、form_with
を使用するとデフォルトではvalidation時のエラーが表示されない様です。local:true
にする事で、解決します。
<%=form_with scope: :session, url:login_path,local: true do |f|%>
Rails 5.1のform_withでViewにvalidationエラー表示 - Qiita
アカウント有効化のフィーチャーテスト
チュートリアルでは、ユーザがサインアップしてから有効化のリンクを踏んでログインできるまでをまとめてテストしている。rspecで再現するなら、capybara
を利用して行うが、capybara
がコントローラ内のインスタンス変数が取れないので、生成されたactivation_token
を取得できず、有効化のリンクを生成できない。代わりの手法として、生成されたメールから正規表現でリンクを取得してみた。チュートリアル上のテストではメール自体のテストを網羅できていないので、フィーチャーテストとしてはむしろ良くなったようにも見えます。
activation_url = expect(ActionMailer:: Base.deliveries.last.body.encoded).to match("^http.*?#{CGI.escape(user.email)}")
他の案としては、コントローラテストで個別に書くとか(コントローラ内からならインスタンス変数にアクセスできる)