Created
March 4, 2025 00:28
-
-
Save kengreeff/3f9687bf6c1189c625eaa6ed4940f8f0 to your computer and use it in GitHub Desktop.
Batch/Debounce Notifications for Noticed Gem
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# db/migrate/20250303023359_create_notification_batches.rb | |
class CreateNotificationBatches < ActiveRecord::Migration[8.0] | |
def change | |
create_table :notification_batches do |t| | |
t.belongs_to :recipient, polymorphic: true, null: false | |
t.string :key | |
t.string :delivery_status, default: "pending" | |
t.integer :inactive_wait_seconds | |
t.integer :max_wait_seconds | |
t.jsonb :options, default: {} | |
t.datetime :delivered_at | |
t.datetime :read_at | |
t.timestamps | |
t.index [ :key ] | |
t.index [ :recipient_type, :recipient_id, :key, :delivery_status ], name: "index_notification_batches_for_lookup" | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# db/migrate/20250303023405_create_notification_batch_items.rb | |
class CreateNotificationBatchItems < ActiveRecord::Migration[8.0] | |
def change | |
create_table :notification_batch_items do |t| | |
t.belongs_to :notification_batch | |
t.belongs_to :notification | |
t.timestamps | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/notifiers/application_delivery_method.rb | |
class ApplicationDeliveryMethod < Noticed::DeliveryMethod | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/notifiers/delivery_methods/batch.rb | |
class DeliveryMethods::Batch < ApplicationDeliveryMethod | |
required_options :batch_key, :deliver_by | |
def deliver | |
batch_key = evaluate_option(:batch_key) | |
NotificationBatch.transaction do | |
notification_batch = NotificationBatch.find_or_create_by!(recipient: recipient, key: batch_key, delivery_status: "pending") do |batch| | |
batch.inactive_wait_seconds = evaluate_option(:inactive_wait) || 10.minutes | |
batch.max_wait_seconds = evaluate_option(:max_wait) || 1.hour | |
batch.deliver_by = evaluate_option(:deliver_by) | |
end | |
NotificationBatchItem.find_or_create_by!(batch: notification_batch, notification: notification) | |
end | |
# Don't deliver anything, we will do with a CRON job | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/notifiers/notification_batch/batch_notifier.rb | |
class NotificationBatch::BatchNotifier < Noticed::Ephemeral | |
required_params :notification_batch | |
deliver_by :email do |config| | |
config.mailer = -> { params[:notification_batch].deliver_by&.dig("email", "mailer") } | |
config.method = -> { params[:notification_batch].deliver_by&.dig("email", "method") } | |
config.params = -> { | |
{ | |
notification_batch: params[:notification_batch], | |
user: recipient, | |
} | |
} | |
config.if = -> { params[:notification_batch].deliver_by&.dig("email").present? } | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/notifiers/comment/comment_created_notifier.rb | |
class Comment::CommentCreatedNotifier < ApplicationNotifier | |
set_event_type "comment.created" | |
deliver_by :batch, class: "DeliveryMethods::Batch" do |config| | |
config.batch_key = "comments" | |
config.deliver_by = { | |
email: { | |
mailer: "CommentMailer", | |
method: :batch_notification, | |
}, | |
} | |
config.inactive_wait = 5.minutes | |
config.max_wait = 15.minutes | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config/initializers/noticed.rb | |
module EventExtensions | |
extend ActiveSupport::Concern | |
included do | |
belongs_to :actor, polymorphic: true, optional: true | |
end | |
end | |
module NotificationExtensions | |
extend ActiveSupport::Concern | |
included do | |
has_many :batch_items, class_name: "Notification::BatchItem", dependent: :destroy | |
end | |
end | |
Rails.application.config.to_prepare do | |
Noticed::Event.include EventExtensions | |
Noticed::Notification.include NotificationExtensions | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/notification_batch.rb | |
class NotificationBatch < ApplicationRecord | |
store_accessor :options, :deliver_by | |
belongs_to :recipient, polymorphic: true | |
has_many :items, class_name: "NotificationBatchItem", dependent: :destroy | |
has_many :notifications, through: :items | |
enum :delivery_status, %w[ pending delivered cancelled ].index_by(&:itself) | |
validates :key, presence: true | |
validate :validate_deliver_by_options, on: :create | |
def deliver | |
if unread? | |
NotificationBatch::BatchNotifier.with(notification_batch: self).deliver(self.recipient) | |
mark_as_status!(:delivered) | |
else | |
mark_as_status!(:cancelled) | |
end | |
end | |
def mark_as_status!(status) | |
self.update(delivery_status: status, delivered_at: Time.current) | |
end | |
def should_deliver? | |
inactive_wait_exceeded? || max_wait_exceeded? | |
end | |
def unread? | |
notifications.read.size == 0 | |
end | |
private | |
def inactive_wait_exceeded? | |
(Time.current - updated_at) > inactive_wait_seconds | |
end | |
def max_wait_exceeded? | |
(Time.current - created_at) > max_wait_seconds | |
end | |
def validate_deliver_by_options | |
errors.add(:options, "must include `deliver_by` options") unless options.has_key?("deliver_by") | |
end | |
end | |
# == Schema Information | |
# | |
# Table name: notification_batches | |
# | |
# id :bigint not null, primary key | |
# delivered_at :datetime | |
# delivery_status :string default("pending") | |
# inactive_wait_seconds :integer | |
# key :string | |
# max_wait_seconds :integer | |
# options :jsonb | |
# read_at :datetime | |
# recipient_type :string not null | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# recipient_id :bigint not null | |
# | |
# Indexes | |
# | |
# index_notification_batches_for_lookup (recipient_type,recipient_id,key,delivery_status) | |
# index_notification_batches_on_recipient (recipient_type,recipient_id) | |
# |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/notification_batch_item.rb | |
class NotificationBatchItem < ApplicationRecord | |
belongs_to :batch, class_name: "NotificationBatch", foreign_key: :notification_batch_id, touch: true | |
belongs_to :notification, class_name: "Noticed::Notification" | |
end | |
# == Schema Information | |
# | |
# Table name: notification_batch_items | |
# | |
# id :bigint not null, primary key | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# notification_batch_id :bigint | |
# notification_id :bigint | |
# | |
# Indexes | |
# | |
# index_notification_batch_items_on_notification_batch_id (notification_batch_id) | |
# index_notification_batch_items_on_notification_id (notification_id) | |
# |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/jobs/notification_batch/process_batch_job.rb | |
class NotificationBatch::ProcessBatchJob < ApplicationJob | |
queue_as :default | |
def perform(params = {}) | |
NotificationBatch.pending.each do |notification_batch| | |
NotificationBatch::ProcessDeliveryJob.perform_later({ notification_batch: notification_batch }) | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/jobs/notification_batch/process_delivery_job.rb | |
class NotificationBatch::ProcessDeliveryJob < ApplicationJob | |
queue_as :default | |
def perform(params = {}) | |
notification_batch = params[:notification_batch] | |
return unless notification_batch&.should_deliver? | |
notification_batch.deliver | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment