diff --git a/lib/noticed/base.rb b/lib/noticed/base.rb index c5f44a73..1546e88d 100644 --- a/lib/noticed/base.rb +++ b/lib/noticed/base.rb @@ -55,9 +55,7 @@ def deliver(recipients) validate! run_callbacks :deliver do - Array.wrap(recipients).uniq.each do |recipient| - run_delivery(recipient, enqueue: false) - end + run_delivery(Array.wrap(recipients).uniq, enqueue: false) end end @@ -65,9 +63,7 @@ def deliver_later(recipients) validate! run_callbacks :deliver do - Array.wrap(recipients).uniq.each do |recipient| - run_delivery(recipient, enqueue: true) - end + run_delivery(Array.wrap(recipients).uniq, enqueue: true) end end @@ -78,22 +74,42 @@ def params private # Runs all delivery methods for a notification - def run_delivery(recipient, enqueue: true) + def run_delivery(recipients, enqueue: true) delivery_methods = self.class.delivery_methods.dup # Run database delivery inline first if it exists so other methods have access to the record if (index = delivery_methods.find_index { |m| m[:name] == :database }) delivery_method = delivery_methods.delete_at(index) - record = run_delivery_method(delivery_method, recipient: recipient, enqueue: false, record: nil) + records = run_database_delivery_method(delivery_method, recipients: recipients) end - delivery_methods.each do |delivery_method| - run_delivery_method(delivery_method, recipient: recipient, enqueue: enqueue, record: record) + # build hash with recipients as keys + records = Array.wrap(records).to_h { |record| [record.recipient, record] } + + recipients.each do |recipient| + delivery_methods.each do |delivery_method| + run_delivery_method(delivery_method, recipient: recipient, enqueue: enqueue, record: records[recipient]) + end + end + end + + # Run database delivery method for all recipients + def run_database_delivery_method(delivery_method, recipients:) + args = { + notification_class: self.class.name, + options: delivery_method[:options], + params: params, + recipients: recipients + } + + run_callbacks :database do + method = delivery_method_for(:database, delivery_method[:options]) + method.perform_now(args) end end # Actually runs an individual delivery - def run_delivery_method(delivery_method, recipient:, enqueue:, record:) + def run_delivery_method(delivery_method, recipient:, record:, enqueue:) args = { notification_class: self.class.name, options: delivery_method[:options], diff --git a/lib/noticed/delivery_methods/base.rb b/lib/noticed/delivery_methods/base.rb index 4e5e77a0..80433c44 100644 --- a/lib/noticed/delivery_methods/base.rb +++ b/lib/noticed/delivery_methods/base.rb @@ -33,12 +33,12 @@ def perform(args) @notification = args[:notification_class].constantize.new(args[:params]) @options = args[:options] @params = args[:params] - @recipient = args[:recipient] @record = args[:record] + @recipient = args[:recipient] # Make notification aware of database record and recipient during delivery - @notification.record = args[:record] - @notification.recipient = args[:recipient] + @notification.record = record + @notification.recipient = recipient return if (condition = @options[:if]) && !@notification.send(condition) return if (condition = @options[:unless]) && @notification.send(condition) diff --git a/lib/noticed/delivery_methods/database.rb b/lib/noticed/delivery_methods/database.rb index d074e594..28fcb8aa 100644 --- a/lib/noticed/delivery_methods/database.rb +++ b/lib/noticed/delivery_methods/database.rb @@ -1,9 +1,13 @@ module Noticed module DeliveryMethods class Database < Base + attr_reader :recipients # Must return the database record def deliver - recipient.send(association_name).create!(attributes) + # build array of notification attributes + notifications = build_notifications + # save all the notification + save_notifications(notifications) end def self.validate!(options) @@ -13,6 +17,12 @@ def self.validate!(options) raise ArgumentError, "database delivery cannot be delayed" if options.key?(:delay) end + def perform(args) + @recipients = args[:recipients] + + super + end + private def association_name @@ -29,6 +39,49 @@ def attributes } end end + + # retun the class notifications or the association + def klass + association_name.to_s.upcase_first.singularize.constantize + end + + # with the recipients build an array of notifications + def build_notifications + Array.wrap(recipients).uniq.map do |recipient| + build_notification(recipient) + end + end + + # new notification and then return the attributes without id and with timestamps + def build_notification(recipient) + recipient.send(association_name) + .new(attributes) + .attributes + .merge({created_at: DateTime.current, updated_at: DateTime.current}) + .except("id") + end + + # if the notification can bulk, use insert_all if not creates records + def save_notifications(notifications) + if bulk? + ids = klass.insert_all!(notifications).rows + klass.preload(:recipient).find(ids) + else + klass.create!(notifications) + end + end + + def current_adapter + if ActiveRecord::Base.respond_to?(:connection_db_config) + ActiveRecord::Base.connection_db_config.adapter + else + ActiveRecord::Base.connection_config[:adapter] + end + end + + def bulk? + Rails::VERSION::MAJOR > 6 && ["postgresql", "postgis"].include?(current_adapter) + end end end end diff --git a/test/delivery_methods/database_test.rb b/test/delivery_methods/database_test.rb index b0518973..7541f336 100644 --- a/test/delivery_methods/database_test.rb +++ b/test/delivery_methods/database_test.rb @@ -50,10 +50,10 @@ class WithDelayedDatabaseDelivery < Noticed::Base test "deliver returns the created record" do args = { notification_class: "Noticed::Base", - recipient: user, + recipients: user, options: {} } - record = Noticed::DeliveryMethods::Database.new.perform(args) + record = Noticed::DeliveryMethods::Database.new.perform(args).first assert_kind_of ActiveRecord::Base, record end