Railsでメールを送信しようとすると以下のようなエラーが出る場合があります。
NameError (uninitialized constant Mail::TestMailer
delegate :deliveries, :deliveries=, to: Mail::TestMailer
^^^^^^^^^^^^):
app/mailers/application_mailer.rb:1:in `<main>'
app/mailers/user_mailer.rb:1:in `<main>'
app/controllers/home_controller.rb:3:in `index'
僕が調べる限り、以下の条件が揃ったときに発生するようです。
- Rails 6.1.5を使用(これ以外のバージョンだと発生しない)
- Ruby 3.1系を使用(Ruby 3.0だと発生しない)
- メール送信を実行しようとする
原因と解決方法
どうやらRuby 3.1でnet-smtpが外部gemになったことに起因しているようです。
The following default gems are now bundled gems.
- net-ftp 0.1.3
- net-imap 0.2.2
- net-pop 0.1.1
- net-smtp 0.3.1
- matrix 0.4.2
- prime 0.1.2
- debug 1.4.0
Gemfileに"net-smtp"を追加してbundle install
&サーバーを再起動するとこのエラーは解消します。
gem 'net-smtp'
# ついでに以下の2つも追加しておく方が良さそう
gem 'net-imap'
gem 'net-pop'
もう少し詳しく
Rails 6.1.5よりも前(Rails 6.1.4.7など)では、"cannot load such file -- net/smtp"というエラーメッセージが表示されるため、net-smtp gemを追加すれば直ることが予想しやすいです。
LoadError (cannot load such file -- net/smtp):
app/mailers/application_mailer.rb:1:in `<main>'
app/mailers/user_mailer.rb:1:in `<main>'
app/controllers/home_controller.rb:3:in `index'
また、以下のPRでnet-smtpが自動的にインストールされるようになったため、Rails 7.0.1以降ではこの問題は発生しません。
Rails 6.1.5でだけ、このエラーが発生する理由
では、なぜRails 6.1.5で"uninitialized constant Mail::TestMailer"というエラーが出るのかというと、Rails 6.1.5の例外処理に問題があるからです。
# frozen_string_literal: true
begin
require "mail"
rescue LoadError => error
if error.message.match?(/net-smtp/)
$stderr.puts "You don't have net-smtp installed in your application. Please add it to your Gemfile and run bundle install"
raise
end
end
error.message
の中身は"cannot load such file -- net/smtp"になっているため、上記コードの正規表現は、
-if error.message.match?(/net-smtp/)
+if error.message.match?(/net\/smtp/)
としなければなりません。
このif文が真にならないため、net-smtpのLoadErrorが発生しても完全に無視され、mail gemがloadされないままサーバー起動するため、Mail::TestMailer
を参照するタイミングでエラーが発生します。
この問題は以下のPRで修正されているため、Rails 6.1系の次回リリースで修正されるものと思われます。
Rails 6.1.6がリリースされ、 "You don't have net-smtp installed in your application. Please add it to your Gemfile and run bundle install" というエラーメッセージが意図通り出力されるようになりました。