本記事は、自分のメモです
通知機能実装メモ - Sidekiq + ActionMailerでの即時・スケジュール通知
はじめに
アプリで「記事(Post)公開時の即時通知」と「翌日のリマインド通知」を実装した際の備忘録です。
Sidekiq + ActionMailerを使用した非同期処理による通知機能の実装を忘れないために残します。
構成
- 即時通知: Post公開時にすぐに送信
- リマインド通知: 公開日の翌日に送信(スケジューリング)
- 技術スタック: Rails 8, Sidekiq, ActionMailer, letter_opener(開発環境)
モデル構成
Notificationモデル
class Notification < ApplicationRecord
belongs_to :user
belongs_to :post
validates :title, :description, :due_date, presence: true
end
Postモデルの通知作成処理
class Post < ApplicationRecord
belongs_to :user
# 通知を作成するメソッド
def create_notifications
# 1. 即時通知を作成
immediate_notification = user.notifications.create!(
title: "【新しい記事を公開しました】#{title}",
description: "タイトル: #{title}\n公開日: #{published_at.strftime('%-m月%-d日 %H:%M')}\n概要:#{summary}",
due_date: Date.current,
post: self
)
# 2. 即時通知をジョブキューに送信
ImmediateNotificationJob.perform_later(immediate_notification.id)
# 3. 翌日リマインド通知を作成(未送信状態で保存)
user.notifications.create!(
title: "【記事リマインド】#{title}",
description: "昨日公開した記事をご覧になりましたか?\nタイトル: #{title}\n公開日: #{published_at.strftime('%-m月%-d日 %H:%M')}",
due_date: published_at.to_date + 1.day,
post: self
)
end
end
ジョブクラス
即時通知ジョブ
class ImmediateNotificationJob < ApplicationJob
queue_as :default
def perform(notification_id)
notification = Notification.find(notification_id)
Notification.transaction do
NotificationMailer.immediate_notification(notification).deliver_now!
notification.update!(is_sent: true)
end
rescue => e
Rails.logger.error "ImmediateNotificationJob failed: #{e.message}"
raise
end
end
翌日リマインドジョブ
class PostReminderJob < ApplicationJob
queue_as :default
def perform
target_date = Time.zone.tomorrow
Notification.includes(:post)
.where(is_sent: false, due_date: target_date)
.find_each do |notification|
Notification.transaction do
NotificationMailer.post_reminder(notification).deliver_now!
notification.update!(is_sent: true)
end
end
end
end
コントローラ処理
PostsController
class PostsController < ApplicationController
def create
@post = current_user.posts.build(post_params)
if @post.save
@post.create_notifications # モデルのメソッドを呼ぶ
redirect_to posts_path, notice: "記事を公開しました"
else
render :new, status: :unprocessable_entity
end
end
private
def post_params
params.require(:post).permit(:title, :content, :summary, :published_at)
end
end
動作確認方法
1. 開発環境での確認
Railsコンソールでの手動テスト
# 1. Postを作成
user = User.first
post = user.posts.create!(
title: "Railsで通知システムを作る",
content: "本文...",
summary: "Rails + Sidekiqで通知システムを構築した話",
published_at: Time.zone.now
)
# 2. 通知を作成(コントローラ経由でなく直接呼び出し)
post.create_notifications
# 3. 即時通知の確認
Notification.where(post: post, is_sent: true).first.title
# => "【新しい記事を公開しました】Railsで通知システムを作る"
# 4. 翌日リマインド通知の確認
Notification.where(post: post, is_sent: false).first.title
# => "【記事リマインド】Railsで通知システムを作る"
2. 翌日リマインド通知のテスト
# テスト用に通知のdue_dateを変更
n = Notification.find(10) # リマインド通知のID
n.update!(due_date: Time.zone.tomorrow)
# ジョブを実行
PostReminderJob.perform_now
# 送信済みに更新されているか確認
n.reload.is_sent # => true
重要なポイント
-
コールバックは使わず明示的に呼び出す
- コントローラで
create_notifications
を呼ぶことで責務を分離。
- コントローラで
-
includes(:post) でN+1回避
- リマインド処理時に関連データを効率的に取得。
-
トランザクションで整合性確保
- メール送信と通知状態更新を一括管理。
実際のログ例
Post Create INSERT INTO "posts" ...
Notification Create '【新しい記事を公開しました】Railsで通知システムを作る'
Enqueued ImmediateNotificationJob (Job ID: ...) to Sidekiq(default)
Notification Create '【記事リマインド】Railsで通知システムを作る'
Notification Update SET "is_sent" = TRUE WHERE "notifications"."id" = 10
まとめ
- 即時通知: 記事公開と同時にユーザーへ通知
- スケジュール通知: 翌日にリマインドを送信
- 非同期処理: Sidekiqでレスポンス性能を維持
- データ整合性: トランザクションで通知状態を保証
本番環境では sidekiq-cron
や whenever
を使って PostReminderJob
を毎日実行することで、自動的なリマインド配信が可能になる。
感想
勉強になりましたが、RailsのJob関連は、本当に難しいと実感しました。