Validate we don't create overlapping bundles

This commit is contained in:
Jorge Manrubia
2025-08-26 16:49:36 +02:00
parent b47064b47e
commit 8ba6f5fe8e
2 changed files with 63 additions and 2 deletions
+22 -2
View File
@@ -3,10 +3,20 @@ class Notification::Bundle < ApplicationRecord
enum :status, %i[ pending processing delivered ]
before_create :set_default_window
scope :due, -> { pending.where("ends_at <= ?", Time.current) }
scope :containing, -> (notification) { where("starts_at <= ? AND ends_at >= ?", notification.created_at, notification.created_at) }
scope :overlapping_with, -> (other_bundle) {
where(
"(starts_at <= ? AND ends_at >= ?) OR (starts_at <= ? AND ends_at >= ?) OR (starts_at >= ? AND ends_at <= ?)",
other_bundle.starts_at, other_bundle.starts_at,
other_bundle.ends_at, other_bundle.ends_at,
other_bundle.starts_at, other_bundle.ends_at
)
}
before_create :set_default_window
validate :validate_no_overlapping
class << self
def deliver_all
@@ -51,4 +61,14 @@ class Notification::Bundle < ApplicationRecord
def has_unread_notifications?
notifications.unread.any?
end
def validate_no_overlapping
if overlapping_bundles.exists?
errors.add(:base, "Bundle window overlaps with an existing pending bundle")
end
end
def overlapping_bundles
user.notification_bundles.where.not(id: id).overlapping_with(self)
end
end
+41
View File
@@ -30,6 +30,47 @@ class Notification::BundleTest < ActiveSupport::TestCase
end
bundle_1, bundle_2 = @user.notification_bundles.last(2)
assert_includes bundle_1.notifications, notification_1
assert_includes bundle_1.notifications, notification_2
assert_includes bundle_2.notifications, notification_3
end
test "overlapping bundles are invalid" do
bundle_1 = @user.notification_bundles.create!(
starts_at: Time.current,
ends_at: 4.hours.from_now,
status: :pending
)
bundle_2 = @user.notification_bundles.build(
starts_at: 2.hours.from_now,
ends_at: 6.hours.from_now,
status: :pending
)
assert_not bundle_2.valid?
# Bundle with overlapping end time should be invalid
bundle_3 = @user.notification_bundles.build(
starts_at: 2.hours.ago,
ends_at: 2.hours.from_now,
status: :pending
)
assert_not bundle_3.valid?
# Bundle completely within another bundle should be invalid
bundle_4 = @user.notification_bundles.build(
starts_at: 1.hour.from_now,
ends_at: 3.hours.from_now,
status: :pending
)
assert_not bundle_4.valid?
# Non-overlapping bundle should be valid
bundle_5 = @user.notification_bundles.build(
starts_at: 5.hours.from_now,
ends_at: 9.hours.from_now,
status: :pending
)
assert bundle_5.valid?
end
end