Skip to content

Commit df040a2

Browse files
committed
Add deliver_all_later to enqueue multiple emails at once
1 parent 9a64857 commit df040a2

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed

actionmailer/CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,17 @@
1+
* Add `deliver_all_later` to enqueue multiple emails at once.
2+
3+
```ruby
4+
user_emails = User.all.map { |user| Notifier.welcome(user) }
5+
ActionMailer.deliver_all_later(user_emails)
6+
7+
# use a custom queue
8+
ActionMailer.deliver_all_later(user_emails, queue: :my_queue)
9+
```
10+
11+
This can greatly reduce the number of round-trips to the queue datastore.
12+
For queue adapters that do not implement the `enqueue_all` method, we
13+
fall back to enqueuing email jobs indvidually.
14+
15+
*fatkodima*
116

217
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/actionmailer/CHANGELOG.md) for previous changes.

actionmailer/lib/action_mailer/message_delivery.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,38 @@
33
require "delegate"
44

55
module ActionMailer
6+
class << self
7+
# Enqueue many emails at once to be delivered through Active Job.
8+
# When the individual job runs, it will send the email using +deliver_now+.
9+
def deliver_all_later(*deliveries, **options)
10+
_deliver_all_later("deliver_now", *deliveries, **options)
11+
end
12+
13+
# Enqueue many emails at once to be delivered through Active Job.
14+
# When the individual job runs, it will send the email using +deliver_now!+.
15+
# That means that the message will be sent bypassing checking +perform_deliveries+
16+
# and +raise_delivery_errors+, so use with caution.
17+
def deliver_all_later!(*deliveries, **options)
18+
_deliver_all_later("deliver_now!", *deliveries, **options)
19+
end
20+
21+
private
22+
def _deliver_all_later(delivery_method, *deliveries, **options)
23+
deliveries.flatten!
24+
25+
jobs = deliveries.map do |delivery|
26+
mailer_class = delivery.mailer_class
27+
delivery_job = mailer_class.delivery_job
28+
29+
delivery_job
30+
.new(mailer_class.name, delivery.action.to_s, delivery_method, params: delivery.params, args: delivery.args)
31+
.set(options)
32+
end
33+
34+
ActiveJob.perform_all_later(jobs)
35+
end
36+
end
37+
638
# = Action Mailer \MessageDelivery
739
#
840
# The +ActionMailer::MessageDelivery+ class is used by
@@ -17,6 +49,8 @@ module ActionMailer
1749
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
1850
# Notifier.welcome(User.first).message # a Mail::Message object
1951
class MessageDelivery < Delegator
52+
attr_reader :mailer_class, :action, :params, :args # :nodoc:
53+
2054
def initialize(mailer_class, action, *args) # :nodoc:
2155
@mailer_class, @action, @args = mailer_class, action, args
2256

actionmailer/test/message_delivery_test.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
require "abstract_unit"
44
require "active_job"
5+
require "mailers/base_mailer"
56
require "mailers/delayed_mailer"
7+
require "mailers/params_mailer"
68

79
class MessageDeliveryTest < ActiveSupport::TestCase
810
include ActiveJob::TestHelper
@@ -136,6 +138,67 @@ class DummyJob < ActionMailer::MailDeliveryJob; end
136138
end
137139
end
138140

141+
test "deliver_all_later enqueues multiple deliveries" do
142+
mail1 = DelayedMailer.test_message(1)
143+
mail2 = DelayedMailer.test_kwargs(argument: 1)
144+
145+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", params: nil, args: [1]]) do
146+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_kwargs", "deliver_now", params: nil, args: [argument: 1]]) do
147+
ActionMailer.deliver_all_later(mail1, mail2)
148+
end
149+
end
150+
end
151+
152+
test "deliver_all_later! enqueues multiple deliveries" do
153+
mail1 = DelayedMailer.test_message(1)
154+
mail2 = DelayedMailer.test_kwargs(argument: 1)
155+
156+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now!", params: nil, args: [1]]) do
157+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_kwargs", "deliver_now!", params: nil, args: [argument: 1]]) do
158+
ActionMailer.deliver_all_later!(mail1, mail2)
159+
end
160+
end
161+
end
162+
163+
test "deliver_all_later enqueues multiple deliveries with correct jobs" do
164+
old_delivery_job = BaseMailer.delivery_job
165+
BaseMailer.delivery_job = DummyJob
166+
167+
mail1 = DelayedMailer.test_message
168+
mail2 = BaseMailer.welcome
169+
170+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", params: nil, args: []]) do
171+
assert_performed_with(job: DummyJob, args: ["BaseMailer", "welcome", "deliver_now", params: nil, args: []]) do
172+
ActionMailer.deliver_all_later(mail1, mail2)
173+
end
174+
end
175+
ensure
176+
BaseMailer.delivery_job = old_delivery_job
177+
end
178+
179+
test "deliver_all_later enqueues multiple deliveries with custom options" do
180+
mail1 = DelayedMailer.test_message(1)
181+
mail2 = DelayedMailer.test_message(2)
182+
183+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", params: nil, args: [1]], queue: "another_queue") do
184+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", params: nil, args: [2]], queue: "another_queue") do
185+
ActionMailer.deliver_all_later(mail1, mail2, queue: :another_queue)
186+
end
187+
end
188+
end
189+
190+
test "deliver_all_later enqueues parameterized emails" do
191+
mail1 = DelayedMailer.test_message(1)
192+
mail2 = ParamsMailer.with(inviter: "[email protected]", invitee: "[email protected]").invitation
193+
194+
assert_performed_with(job: ActionMailer::MailDeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", params: nil, args: [1]]) do
195+
assert_performed_with(job: ActionMailer::MailDeliveryJob,
196+
args: ["ParamsMailer", "invitation", "deliver_now", args: [], params: { inviter: "[email protected]", invitee: "[email protected]" }]) do
197+
ActionMailer.deliver_all_later(mail1, mail2)
198+
end
199+
end
200+
end
201+
139202
test "job delegates error handling to mailer" do
140203
# Superclass not rescued by mailer's rescue_from RuntimeError
141204
message = DelayedMailer.test_raise("StandardError")

0 commit comments

Comments
 (0)