From a68a1f4c70c1bf024805d41dc78f6182f9d9bcd7 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 23 Jan 2024 08:58:21 -0600 Subject: [PATCH 1/7] Add ephemeral notifiers --- app/models/concerns/noticed/deliverable.rb | 11 +++-- app/models/noticed/deliverable/deliver_by.rb | 17 ++++++-- app/models/noticed/ephemeral.rb | 42 +++++++++++++++++++ lib/noticed.rb | 1 + lib/noticed/bulk_delivery_method.rb | 12 ++++-- lib/noticed/bulk_delivery_methods/test.rb | 12 ++++++ lib/noticed/delivery_method.rb | 17 +++++--- .../dummy/app/notifiers/ephemeral_notifier.rb | 17 ++++++++ test/ephemeral_notifier_test.rb | 16 +++++++ 9 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 app/models/noticed/ephemeral.rb create mode 100644 lib/noticed/bulk_delivery_methods/test.rb create mode 100644 test/dummy/app/notifiers/ephemeral_notifier.rb create mode 100644 test/ephemeral_notifier_test.rb diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index 05621c07..b5e660d4 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -9,10 +9,13 @@ module Deliverable attribute :params, default: {} - if Rails.gem_version >= Gem::Version.new("7.1.0.alpha") - serialize :params, coder: Coder - else - serialize :params, Coder + # Ephemeral notifiers cannot serialize params since they aren't ActiveRecord backed + if respond_to? :serialize + if Rails.gem_version >= Gem::Version.new("7.1.0.alpha") + serialize :params, coder: Coder + else + serialize :params, Coder + end end end diff --git a/app/models/noticed/deliverable/deliver_by.rb b/app/models/noticed/deliverable/deliver_by.rb index 2b7ff89d..a2bf4b19 100644 --- a/app/models/noticed/deliverable/deliver_by.rb +++ b/app/models/noticed/deliverable/deliver_by.rb @@ -19,14 +19,23 @@ def validate! end def perform_later(event_or_notification, options = {}) - options[:wait] = evaluate_option(:wait, event_or_notification) if config.has_key?(:wait) - options[:wait_until] = evaluate_option(:wait_until, event_or_notification) if config.has_key?(:wait_until) - options[:queue] = evaluate_option(:queue, event_or_notification) if config.has_key?(:queue) - options[:priority] = evaluate_option(:priority, event_or_notification) if config.has_key?(:priority) + options[:wait] ||= evaluate_option(:wait, event_or_notification) if config.has_key?(:wait) + options[:wait_until] ||= evaluate_option(:wait_until, event_or_notification) if config.has_key?(:wait_until) + options[:queue] ||= evaluate_option(:queue, event_or_notification) if config.has_key?(:queue) + options[:priority] ||= evaluate_option(:priority, event_or_notification) if config.has_key?(:priority) constant.set(options).perform_later(name, event_or_notification) end + def ephemeral_perform_later(notifier, recipient, params, options = {}) + options[:wait] ||= evaluate_option(:wait, recipient) if config.has_key?(:wait) + options[:wait_until] ||= evaluate_option(:wait_until, recipient) if config.has_key?(:wait_until) + options[:queue] ||= evaluate_option(:queue, recipient) if config.has_key?(:queue) + options[:priority] ||= evaluate_option(:priority, recipient) if config.has_key?(:priority) + + constant.set(options).perform_later(name, "#{notifier}::Notification", recipient: recipient, params: params, overrides: config) + end + def evaluate_option(name, context) option = config[name] diff --git a/app/models/noticed/ephemeral.rb b/app/models/noticed/ephemeral.rb new file mode 100644 index 00000000..b076c5f8 --- /dev/null +++ b/app/models/noticed/ephemeral.rb @@ -0,0 +1,42 @@ +module Noticed + class Ephemeral + include ActiveModel::Model + include ActiveModel::Attributes + include Noticed::Deliverable + + class Notification + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :recipient + attribute :event + + delegate :params, :record, to: :event + + def self.new_with_params(recipient, params) + instance = new(recipient: recipient) + instance.event = module_parent.new(params: params) + instance + end + end + + attribute :params, default: {} + + def deliver(recipients) + recipients = Array.wrap(recipients) + bulk_delivery_methods.each do |_, deliver_by| + deliver_by.ephemeral_perform_later(self.class.name, recipients, params) + end + + recipients.each do |recipient| + delivery_methods.each do |_, deliver_by| + deliver_by.ephemeral_perform_later(self.class.name, recipient, params) + end + end + end + + def record + params[:record] + end + end +end diff --git a/lib/noticed.rb b/lib/noticed.rb index c39d548d..c52bb161 100644 --- a/lib/noticed.rb +++ b/lib/noticed.rb @@ -20,6 +20,7 @@ def self.deprecator # :nodoc: module BulkDeliveryMethods autoload :Discord, "noticed/bulk_delivery_methods/discord" autoload :Slack, "noticed/bulk_delivery_methods/slack" + autoload :Test, "noticed/bulk_delivery_methods/test" autoload :Webhook, "noticed/bulk_delivery_methods/webhook" end diff --git a/lib/noticed/bulk_delivery_method.rb b/lib/noticed/bulk_delivery_method.rb index 1e3de5ad..58e27a17 100644 --- a/lib/noticed/bulk_delivery_method.rb +++ b/lib/noticed/bulk_delivery_method.rb @@ -7,9 +7,15 @@ class BulkDeliveryMethod < ApplicationJob attr_reader :config, :event - def perform(delivery_method_name, event) - @event = event - @config = event.bulk_delivery_methods.fetch(delivery_method_name).config + def perform(delivery_method_name, event, recipients: nil, params: {}, overrides: {}) + # Ephemeral notifications + if event.is_a? String + @event = @notification.event + @config = overrides + else + @event = event + @config = event.bulk_delivery_methods.fetch(delivery_method_name).config.merge(overrides) + end return false if config.has_key?(:if) && !evaluate_option(:if) return false if config.has_key?(:unless) && evaluate_option(:unless) diff --git a/lib/noticed/bulk_delivery_methods/test.rb b/lib/noticed/bulk_delivery_methods/test.rb new file mode 100644 index 00000000..5f013372 --- /dev/null +++ b/lib/noticed/bulk_delivery_methods/test.rb @@ -0,0 +1,12 @@ +module Noticed + module BulkDeliveryMethods + class Test < DeliveryMethod + class_attribute :delivered, default: [] + + def deliver + delivered << notification + end + end + end +end + diff --git a/lib/noticed/delivery_method.rb b/lib/noticed/delivery_method.rb index f84dc63e..4de329f1 100644 --- a/lib/noticed/delivery_method.rb +++ b/lib/noticed/delivery_method.rb @@ -9,12 +9,19 @@ class DeliveryMethod < ApplicationJob delegate :recipient, to: :notification delegate :record, :params, to: :event - def perform(delivery_method_name, notification, overrides: {}) - @notification = notification - @event = notification.event + def perform(delivery_method_name, notification, recipient: nil, params: {}, overrides: {}) + # Ephemeral notifications + if notification.is_a? String + @notification = notification.constantize.new_with_params(recipient, params) + @event = @notification.event + @config = overrides + else + @notification = notification + @event = notification.event - # Look up config from Notifier and merge overrides - @config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides) + # Look up config from Notifier and merge overrides + @config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides) + end return false if config.has_key?(:if) && !evaluate_option(:if) return false if config.has_key?(:unless) && evaluate_option(:unless) diff --git a/test/dummy/app/notifiers/ephemeral_notifier.rb b/test/dummy/app/notifiers/ephemeral_notifier.rb new file mode 100644 index 00000000..70d14cdc --- /dev/null +++ b/test/dummy/app/notifiers/ephemeral_notifier.rb @@ -0,0 +1,17 @@ +class EphemeralNotifier < Noticed::Ephemeral + bulk_deliver_by :test + + deliver_by :test do |config| + config.wait = 5.minutes + end + + deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = "new_comment" + config.args = :email_args + end + + def email_args(recipient) + [recipient] + end +end diff --git a/test/ephemeral_notifier_test.rb b/test/ephemeral_notifier_test.rb new file mode 100644 index 00000000..379cc5b2 --- /dev/null +++ b/test/ephemeral_notifier_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class EphemeralNotifierTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + include ActiveJob::TestHelper + + test "can enqueue delivery methods" do + assert_enqueued_jobs 3 do + EphemeralNotifier.new(params: {foo: :bar}).deliver(User.last) + end + + assert_emails 1 do + perform_enqueued_jobs + end + end +end From c2bf4047834af4422fe83ce9f6386ddd77cfe178 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 23 Jan 2024 16:17:58 -0600 Subject: [PATCH 2/7] Standardize --- lib/noticed/bulk_delivery_methods/test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/noticed/bulk_delivery_methods/test.rb b/lib/noticed/bulk_delivery_methods/test.rb index 5f013372..2c629453 100644 --- a/lib/noticed/bulk_delivery_methods/test.rb +++ b/lib/noticed/bulk_delivery_methods/test.rb @@ -9,4 +9,3 @@ def deliver end end end - From f610c8222f739d635f383f64c5fe782e268b6565 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 23 Jan 2024 18:09:14 -0600 Subject: [PATCH 3/7] Move ApplicationDeliveryMethod to app/notifiers --- lib/generators/noticed/delivery_method_generator.rb | 2 +- .../{delivery_methods => }/application_delivery_method.rb | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/dummy/app/notifiers/{delivery_methods => }/application_delivery_method.rb (100%) diff --git a/lib/generators/noticed/delivery_method_generator.rb b/lib/generators/noticed/delivery_method_generator.rb index 57ade434..bd53638d 100644 --- a/lib/generators/noticed/delivery_method_generator.rb +++ b/lib/generators/noticed/delivery_method_generator.rb @@ -12,7 +12,7 @@ class DeliveryMethodGenerator < Rails::Generators::NamedBase desc "Generates a class for a custom delivery method with the given NAME." def generate_notification - template "application_delivery_method.rb", "app/notifiers/delivery_methods/application_delivery_method.rb" + template "application_delivery_method.rb", "app/notifiers/application_delivery_method.rb" template "delivery_method.rb", "app/notifiers/delivery_methods/#{singular_name}.rb" end end diff --git a/test/dummy/app/notifiers/delivery_methods/application_delivery_method.rb b/test/dummy/app/notifiers/application_delivery_method.rb similarity index 100% rename from test/dummy/app/notifiers/delivery_methods/application_delivery_method.rb rename to test/dummy/app/notifiers/application_delivery_method.rb From 1d94383b899b1cbb2b6725b848fd385f25f3c7ea Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 26 Jan 2024 09:14:47 -0600 Subject: [PATCH 4/7] Dynamically create Notification class on Ephemeral Notifiers --- app/models/noticed/ephemeral.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/noticed/ephemeral.rb b/app/models/noticed/ephemeral.rb index b076c5f8..016f13ed 100644 --- a/app/models/noticed/ephemeral.rb +++ b/app/models/noticed/ephemeral.rb @@ -4,6 +4,9 @@ class Ephemeral include ActiveModel::Attributes include Noticed::Deliverable + attribute :record + attribute :params, default: {} + class Notification include ActiveModel::Model include ActiveModel::Attributes @@ -20,7 +23,11 @@ def self.new_with_params(recipient, params) end end - attribute :params, default: {} + # Dynamically define Notification on each Ephemeral Notifier + def self.inherited(notifier) + super + notifier.const_set :Notification, Class.new(Noticed::Ephemeral::Notification) + end def deliver(recipients) recipients = Array.wrap(recipients) From d7d3c2e9bc25596e25fc7e20ed2cef0617f72ed4 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 26 Jan 2024 19:08:10 -0600 Subject: [PATCH 5/7] Add notification_methods to ephemeral_notifiers --- app/models/noticed/ephemeral.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/noticed/ephemeral.rb b/app/models/noticed/ephemeral.rb index 016f13ed..14976055 100644 --- a/app/models/noticed/ephemeral.rb +++ b/app/models/noticed/ephemeral.rb @@ -45,5 +45,9 @@ def deliver(recipients) def record params[:record] end + + def notification_methods(&block) + const_get(:Notification).class_eval(&block) + end end end From 18b8084d6a1d0f0f17aa0037d3fdab0d8d6a08e1 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 29 Jan 2024 10:43:51 -0600 Subject: [PATCH 6/7] Lookup ephemeral notifier config instead of passing it to the job --- app/models/noticed/deliverable/deliver_by.rb | 2 +- lib/noticed/delivery_method.rb | 7 +++---- test/dummy/app/notifiers/ephemeral_notifier.rb | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/noticed/deliverable/deliver_by.rb b/app/models/noticed/deliverable/deliver_by.rb index a2bf4b19..9778af1f 100644 --- a/app/models/noticed/deliverable/deliver_by.rb +++ b/app/models/noticed/deliverable/deliver_by.rb @@ -33,7 +33,7 @@ def ephemeral_perform_later(notifier, recipient, params, options = {}) options[:queue] ||= evaluate_option(:queue, recipient) if config.has_key?(:queue) options[:priority] ||= evaluate_option(:priority, recipient) if config.has_key?(:priority) - constant.set(options).perform_later(name, "#{notifier}::Notification", recipient: recipient, params: params, overrides: config) + constant.set(options).perform_later(name, "#{notifier}::Notification", recipient: recipient, params: params) end def evaluate_option(name, context) diff --git a/lib/noticed/delivery_method.rb b/lib/noticed/delivery_method.rb index 5aec8f03..ad27246c 100644 --- a/lib/noticed/delivery_method.rb +++ b/lib/noticed/delivery_method.rb @@ -17,15 +17,14 @@ def perform(delivery_method_name, notification, recipient: nil, params: {}, over if notification.is_a? String @notification = notification.constantize.new_with_params(recipient, params) @event = @notification.event - @config = overrides else @notification = notification @event = notification.event - - # Look up config from Notifier and merge overrides - @config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides) end + # Look up config from Notifier and merge overrides + @config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides) + return false if config.has_key?(:if) && !evaluate_option(:if) return false if config.has_key?(:unless) && evaluate_option(:unless) diff --git a/test/dummy/app/notifiers/ephemeral_notifier.rb b/test/dummy/app/notifiers/ephemeral_notifier.rb index 70d14cdc..a803e9d7 100644 --- a/test/dummy/app/notifiers/ephemeral_notifier.rb +++ b/test/dummy/app/notifiers/ephemeral_notifier.rb @@ -9,6 +9,7 @@ class EphemeralNotifier < Noticed::Ephemeral config.mailer = "UserMailer" config.method = "new_comment" config.args = :email_args + config.params = ->{ {recipient: recipient} } end def email_args(recipient) From f7d7ec38c426eda8e7313e07dd91c92f8a8e86f5 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 29 Jan 2024 10:44:27 -0600 Subject: [PATCH 7/7] Standardize --- test/dummy/app/notifiers/ephemeral_notifier.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dummy/app/notifiers/ephemeral_notifier.rb b/test/dummy/app/notifiers/ephemeral_notifier.rb index a803e9d7..9ab82da7 100644 --- a/test/dummy/app/notifiers/ephemeral_notifier.rb +++ b/test/dummy/app/notifiers/ephemeral_notifier.rb @@ -9,7 +9,7 @@ class EphemeralNotifier < Noticed::Ephemeral config.mailer = "UserMailer" config.method = "new_comment" config.args = :email_args - config.params = ->{ {recipient: recipient} } + config.params = -> { {recipient: recipient} } end def email_args(recipient)