分かっている方も多いかもしれませんが、以下のコードは一見例外が起こりそうですが起きません。
以下のコードでは、HogeMailerのインスタンスメソッドdaily_summaryの中身は評価されないからです。(irb等だとinspectメソッドあたりが呼ばれるせいで評価されてしまいますが…)
class HogeMailer < ActionMailer::Base
def daily_summary(user)
mail(to: user.email)
end
end
HogeMailer.daily_summary(nil)
# => #<ActionMailer::MessageDelivery>
メーラーのアクションを呼んだ場合の評価の流れ
http://railsguides.jp/action_mailer_basics.html を見ると書いてありますが、
このHogeMailerクラスのdaily_summaryメソッド(アクションと呼ばれます)を呼んだ場合はActionMailer::MessageDeliveryというMail::Messageをラップしたクラスのインスタンスが返されます。
(内部的にはActionMailer::Baseのmethod_missingメソッドが呼ばれ、ActionMailer::MessageDeliveryクラスのインスタンスが作られます。 )
ActionMailer::MessageDeliveryのコードは以下のようにDelegatorになっていて、Mail::Messageのように振る舞うのですが、インスタンスが作られた段階ではまだ@mail_method
が呼ばれていません。必要になった際に初めて呼ばれて@mail_method
のメソッドの中身が評価されます。
class MessageDelivery < Delegator
def initialize(mailer, mail_method, *args) #:nodoc:
@mailer = mailer
@mail_method = mail_method
@args = args
end
def __getobj__ #:nodoc:
@obj ||= @mailer.send(:new, @mail_method, *@args).message
end
# ...
(https://github.com/rails/rails/blob/v4.2.6/actionmailer/lib/action_mailer/message_delivery.rb から引用)
なんでこういう仕様なのか
なぜ、こういう仕様になっているかというと、非同期送信機能(deliver_laterメソッド)との兼ね合いのためでしょう。
言われると確かにという感じですが、意識しないとテストなどで、最初のHogeMailer.daily_summary(nil)
みたいな、一見テストしているけど実はしてないようなコードを書いてしまうことがあるので気を付けましょう…
テストで使う際はdeliver_now
またはmessage
を必ず呼ぶようにしましょう。どちらもMail::Messageを返します。
ちなみに
HogeMailer.daily_sumary(User.first) # => NoMethodError
HogeMailer.daily_summary() # => #<ActionMailer::MessageDelivery>
1番目のコードはActionMailer::Baseのmethod_missing内で呼ばれたメソッドがアクションとして存在するかチェックされるので例外を起こすのですが、2番目は必要になるまでアクションメソッドが評価されないので、例外が起きません。