概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Don’t Email From Active Record Callbacks - Andy Croll
- 原文公開日: 2019/06/02
- 著者: Andy Croll
Rails: メールをActive Recordのコールバックで送信しないこと(翻訳)
Railsアプリケーションで何かとやってみたくなることのひとつといえば、メール送信でしょう。
モデルのインスタンスが変更または作成されたときにメールを送信するというのが、よくあるパターンです。
次のようにしないこと
モデルのコールバックにメール送信を仕込む。
class BookReview < ApplicationRecord
after_create :send_email_to_author
private
def send_email_to_author
AuthorMailer.
with(author: author).
review_notification.
deliver_now
end
end
次のようにすること
メールをコントローラで送信する。
class BookReviewsController < ApplicationController
def create
BookReview.create(comment_params)
AuthorMailer.
with(author: author).
review_notification.
deliver_now
end
end
コールバックで送信すべきでない理由
後でコードに触るときに理解しやすくするためであり、後で自分がびっくりしないためです。
上のコード例で考えてみましょう。本にブックレビューを付けるときに、その本の著者に必ずしもメールを送る必要があるとは限りません。1つ目のサンプルコードでは、ブックレビューを1つ作成する副作用としてメールが発射されてしまいます。
場合によってはrails console
などで何か操作するときに、ユーザーが著者にメールを送信せずに本のレビューを作成する必要が生じるかもしれません。コールバックをスキップするメソッドを駆使する方法も一応可能ですが、そこから先は泥沼です。
「やることリスト」をコントローラのアクションの中に置いてそこで全部見えるようにしておく方が、明確かつ手続き的です。この場合、ブックレビューの作成の後に行う別の操作としてメール送信を書いておけば、後でコードを見返したときに意図がずっと明確になります。
それだけではありません。無関係な機能をデバッグするために、モデルのコールバックたちをもれなくチェックして回るのは、認知に大きな負荷がかかってつらい作業になります。自分の頭で把握しておかなければならないコンテキストがぐっと増えてしまうからです。
コールバックで送信する理由があるとすれば
ドキュメントではいつも、メール送信をモデルのコールバックで行うことは「Rails Way」であるとみなされて上のようなコード例が付いていたりします。そして、シンプルなケースであれば実際にはこれといった問題は生じません。しかし、やがてアプリケーションが複雑になってくれば、このアプローチのつらみだけが前面に出てくるようになるのです。
「モデルはファットに、コントローラは薄くすることを目指すべき」とよく言われることもあって、私たちは多くの機能をモデル層に盛り込むようになります。これはもちろん一般的にはよいアドバイスではあります。しかしこれはどちらかというと、コールバックを量産する副作用を使いまくってもよいということではなく、アプリケーションの操作を明確にすることでコントローラ層から複雑さを排除しようという話です。
自分の感覚では、モデル変更に伴ってメールを送信する程度であれば、コールバックベースの抽象化が混乱するほど複雑にはならないと思います。大事なのは、コントローラのアクションが呼び出されると、アプリケーションのユーザーに重要なことが2つ発生することを明確に示すことです。
私は、コントローラのメソッドが複雑になってきたときには、それらをコールバックに押し込めるよりも素のRubyの「Service Object」に移す方が好みです。
おたより発掘
基本的にコールバックは利用しないほうが良い。
コールバックは副作用であり、副作用は控えるか、疎結合にしておくべきかなあ。Rails: メールをActive Recordのコールバックで送信しないこと(翻訳) https://t.co/hSD2gUBiaM
— Jaga Apple (@jagaapple_tech) September 12, 2019
これ、昔は僕もよくやってた。メールはコントローラで送信が基本だけど、どうしてもモデルで送信したい場合はビックリ防止のためフラグで制御します。
user.send_mail_enabled = true
user. saveRails: メールをActive Recordのコールバックで送信しないこと(翻訳) https://t.co/ECJ9kktmz6
— Junichi Ito (伊藤淳一) (@jnchito) September 17, 2019