こんにちは、サーバサイドエンジニアの@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つのアクセサを比較する(翻訳)」も参考にさせてもらいました。
SmartBankでは部分的にでもRailsのコードリーディングをしながら開発したいエンジニアを募集しています!
▼サーバーサイドエンジニア smartbank.co.jp
▼カジュアル面談 smartbank.co.jp