0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails初学者】deliver_nowとdeliver_now!の違いについてまとめる備忘録

Posted at

deliver_nowと、deliver_now!の違い

アプリ作成時に、Railsでメール送信を行う際に使用した以下2つ

  • deliver_now
  • deliver_now!
    一見よく似ていますが、失敗時の挙動が大きく異なるようでした。
    この記事では、ブログアプリの通知機能を例に、それぞれの違いを備忘録としてまとめます。

違いについて

メソッド 挙動 失敗時
deliver_now メールをすぐ送信(同期実行) 原則として例外を投げず、Mail::Message を返す(静かに失敗する場合あり)
deliver_now! メールをすぐ送信(同期実行) 送信エラー発生時に例外を投げる(rescue可能)

deliver_now→ 失敗してもアプリは落ちない。ログでしか気づけない。
deliver_now!→ 失敗時に例外を発生させ、トランザクションと組み合わせて安全に扱える。

ブログアプリで「記事投稿通知メール」を送る

通知ジョブ(非同期処理)

# app/jobs/post_notification_job.rb
class PostNotificationJob < ApplicationJob
  queue_as :default

  def perform(post_id)
    post = Post.find(post_id)

    Notification.transaction do
      # 投稿者へ「記事が公開されました」メールを送信
      NotificationMailer.post_published(post).deliver_now!

      # メール送信が成功したら通知を記録
      Notification.create!(post: post, sent_at: Time.current)
    end
  end
end

上記についてのポイント

deliver_now!は送信失敗時に例外を投げるからNotification.create!は実行されない。
また、Notification.transaction によって、メール送信と通知レコード作成が1つの処理単位として扱われる。
もしも、エラーなどで送信に失敗した場合でも、トランザクションがロールバックされ、
Notificationのsent_atが誤って登録されることを防げる。

deliver_nowの場合

NotificationMailer.post_published(post).deliver_now
Notification.create!(post: post, sent_at: Time.current)

もしメール送信が失敗しても例外が発生せず、そのまま次の行が実行される。

  • ユーザーにはメールが届かないけど、DBには「送信済み」と記録される
    そうすると、実際の状態とデータベースの整合性が崩れる可能性がある

例外を投げるメリット

deliver_now!を使うことで、失敗を確実に検知してロールバックできる。
Notification.transactionの中で使用すれば、送信に失敗した際に例外が発生して、後続の Notification.create!は実行されない。
そうすると、送信失敗と同時にトランザクション全体が巻き戻され、「送れていないのに送信済みと記録される」といった不整合を防げる。

リマインド通知の例

複数の記事に対して「明日リマインドメールを送る」ジョブも同様に扱える。

# app/jobs/post_reminder_job.rb
class PostReminderJob < ApplicationJob
  queue_as :default

  def perform
    target_date = Time.zone.tomorrow

    Post.where(published: true, reminder_sent: false, reminder_date: target_date)
        .find_each do |post|
      Notification.transaction do
        NotificationMailer.reminder(post).deliver_now!
        post.update!(reminder_sent: true)
      end
    end
  end
end

deliver_now!によって、送信失敗時には例外が発生して、post.update!が実行されない。
→「リマインド済みフラグ」だけが誤って立つといった事態を防げる。

まとめ

項目 deliver_now deliver_now!
メール送信 即時(同期) 即時(同期)
非同期処理 deliver_later を使用 deliver_later! を使用
失敗時の挙動 原則として例外を投げない(Mail gem依存) 例外を投げる(Mail gem依存)
戻り値 Mail::Message オブジェクト Mail::Message オブジェクト
トランザクション内での利用 失敗を検知できない 例外でロールバック可能

結論

  • 安全にトランザクションを管理したい場合はdeliver_now!を使う
  • 失敗を無視して処理を続行したい場合のみdeliver_nowを使う
  • 特に、通知やリマインドのように「送信状態をDBで管理する」機能では、deliver_now!を使うことで、データの整合性が保たれる

deliver_now!は「失敗を確実に検知したいとき」
deliver_nowは「失敗しても処理を続けたいとき」
という使い分けをする


初学者のため、間違えていたらすいません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?