こんにちは、サーバサイドエンジニアの@ohbaryeです。
本記事ではRailsのActionMailerで最近ハマった小ネタについて記載します。
先に結論を言うと
ApplicationMailerの設定のうち、ActionMailer::Baseのサブクラスで値を変更すると全てのMailerに反映されてしまう設定があるので気をつけましょう。
記事執筆時点*1で筆者が気づいたのはdeliver_later_queue_name, raise_delivery_errors, perform_deliveriesあたりです。

前提: メール送信の非同期設定
Railsのメール配信機構であるActionMailerには、メール送信を非同期で送るためのdeliver_laterメソッドがあります。
このときに使用されるキューはdeliver_later_queue_nameで設定でき、使用されるJobクラスはdelivery_jobで設定できるとRails Guideで紹介されています。
この値はenvironment.rb, development.rb等のconfiguration filesで設定することができます。
Rails.application.configure do config.action_mailer.deliver_later_queue_name = :bulk config.action_mailer.delivery_job = RegistrationDeliveryJob end
やりたかったこと: 個別のMailerクラスでキューの設定を行う
configuration filesの設定は全てのMailerに適用されるものです。使用するキューやジョブを各Mailerクラスごとに設定するにはどうすればよいでしょうか。
これら2つの値を設定するアクセサがあるので、初めは以下のような設定を思いつきました。
class UserMailer < ApplicationMailer self.deliver_later_queue_name = :bulk # 後述しますがこの設定は要注意! self.delivery_job = RegistrationDeliveryJob def welcome_email @user = params[:user] @url = 'http://example.com/login' mail(to: @user.email, subject: '私の素敵なサイトへようこそ') end end
しかしながら、この記述ではうまくいきませんでした。
この2つの設定のうちdelivery_jobはMailerクラス単位になりますが、deliver_later_queue_nameはすべてのMailerに適用されてしまいます。
原因: class_attributeとcattr_accessorの違い
同じようなインタフェースで値を設定できるにも関わらずなぜ影響範囲が異なるのか、Railsのコードを読んでみました。
クラス単位で設定できるclass_attribute
まず、Mailerクラス個別の設定となったdelivery_jobはActionMailer::Baseのclass_attributeとして定義されています。これはセットしたクラスにのみ影響する変数です。
対象コード付近にあるdefault_paramsも同様です。
継承先/元で共有されるcattr_accessor
一方、deliver_later_queue_nameはActionMailer::DeliveryMethodsでcattr_accessorとして定義されています。raise_delivery_errors, perform_deliveriesあたりの設定も同様です。*2
cattr_accessorの説明をAPIドキュメントから一部抜粋すると、子クラスで値を変更すると親クラスにも影響が及ぶもの(逆も然り)だと説明されています。
If a subclass changes the value then that would also change the value for parent class. Similarly if parent class changes the value then that would change the value of subclasses too.
これにて謎が解けました。
Rails Guideで同列に並んでいる設定なので勝手に適用範囲も同じであろうと推測・誤認してしまいました。今後は各設定の適用範囲について注意深く見ていきたいと思います。
回避策: 個別のMailerクラスでキューを指定する方法は?
キューを使い分ける方法には以下の2つがあると同僚の@nilpoonaが教えてくれました。
1) delivery_jobに与えるJobクラスの実装でqueue_asを設定する*3
class RegistrationDeliveryJob < ApplicationJob queue_as :bulk # `self.queue_name = :bulk` でも同等 end
class UserMailer < ApplicationMailer self.delivery_job = RegistrationDeliveryJob end
Mailerに対応するJobクラスを作らないといけないのが冗長ですね。
2) deliver_laterのキーワード引数queueで指定する
UserMailer.with(user:).welcome_email.deliver_later(queue: :bulk)
Mailerクラス側の設定ではなく呼び出し側で決定しなければいけないのが設計として微妙に思います*4。
より良い方法をご存じの方がいたらぜひ教えていただけると幸いです。
記事を書くにあたりアクセサの違いについては「Rails: クラスレベルの3つのアクセサを比較する(翻訳)」も参考にさせてもらいました。
株式会社スマートバンクでは部分的にでもRailsのコードリーディングをしながら開発したいエンジニアを募集しています!
▼サーバーサイドエンジニア https://smartbank.co.jp/205ff77ca53648a1952efe962422e71csmartbank.co.jp
▼カジュアル面談 https://smartbank.co.jp/de1f8d3093a64bcd99e2487352cf5f56smartbank.co.jp