From 13002715eb59aa583cc8fb9cbb8cf2f44c04f84b Mon Sep 17 00:00:00 2001 From: Thomas Cannon Date: Tue, 21 Oct 2025 18:06:02 -0400 Subject: [PATCH] Fix `Noticed::DeliveryMethods::Email` to support keyword arguments * Mailers with keyword arguments were not able to be used by the `Noticed::DeliveryMethods::Email` because the arguments were not double-splatted to convert a hash to the arguments the mailer expects * To support keyword arguments in mailers, while maintaining backward compatability with existing configurations, a new `kwargs` option is available * If present, the mailer will be called with `public_send(name, **kwargs)`, to properly convert the hash of argument to keyword arguments as Ruby expects them * When delivering, `kwargs` and `args` cannot both be present, because only one can be used. In this case, an `ArgumentError` will be thrown * Added unit tests for the new behavior, along with documentation for the new option --- docs/delivery_methods/email.md | 21 ++++++++ lib/noticed/delivery_methods/email.rb | 15 +++++- test/delivery_methods/email_test.rb | 74 ++++++++++++++++++++++++++- test/dummy/app/mailers/user_mailer.rb | 4 ++ 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/delivery_methods/email.md b/docs/delivery_methods/email.md index 5237e134..93d94d18 100644 --- a/docs/delivery_methods/email.md +++ b/docs/delivery_methods/email.md @@ -15,6 +15,21 @@ deliver_by :email do |config| end ``` +Mailer methods with keyword arguments are also supported. + +```ruby +deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = :greeting + config.params = ->{ params } + config.kwargs = ->{ {body: "Hey there", subject: "Thanks for joining"} } + + # Enqueues a separate job for sending the email using deliver_later. + # Deliveries already happen in jobs so this is typically unnecessary. + # config.enqueue = false +end +``` + ##### Options - `mailer` - **Required** @@ -30,6 +45,12 @@ end Use a custom method to define the params sent to the mailer. `recipient` will be merged into the params. - `args` - _Optional_ + + The arguments for the `method` if it uses **positional arguments** (eg: `def hello(a, b, c=1)`) + +- `kwargs` - _Optional_ + + The arguments for the `method` if it uses **keyword arguments** (eg: `def hello(a:, b:, c: 1)`) - `enqueue: false` - _Optional_ diff --git a/lib/noticed/delivery_methods/email.rb b/lib/noticed/delivery_methods/email.rb index e53d997a..7dcf8b9b 100644 --- a/lib/noticed/delivery_methods/email.rb +++ b/lib/noticed/delivery_methods/email.rb @@ -7,7 +7,20 @@ def deliver mailer = fetch_constant(:mailer) email = evaluate_option(:method) args = evaluate_option(:args) || [] - mail = mailer.with(params).public_send(email, *args) + kwargs = evaluate_option(:kwargs) || {} + + if args.present? && kwargs.present? + raise ArgumentError, "`args` and `kwargs` cannot both be provided." + end + + mailer_instance = mailer.with(params) + + mail = if kwargs.present? + mailer_instance.public_send(email, **kwargs) + else + mailer_instance.public_send(email, *args) + end + (!!evaluate_option(:enqueue)) ? mail.deliver_later : mail.deliver_now end diff --git a/test/delivery_methods/email_test.rb b/test/delivery_methods/email_test.rb index 20915759..0bb70f9f 100644 --- a/test/delivery_methods/email_test.rb +++ b/test/delivery_methods/email_test.rb @@ -8,7 +8,7 @@ class EmailTest < ActiveSupport::TestCase @notification = noticed_notifications(:one) end - test "sends email" do + test "sends email (with args)" do set_config( mailer: "UserMailer", method: "new_comment", @@ -21,6 +21,78 @@ class EmailTest < ActiveSupport::TestCase end end + test "sends email (with kwargs)" do + set_config( + mailer: "UserMailer", + method: "greeting", + params: -> { {foo: :bar} }, + kwargs: -> { {body: "Custom"} } + ) + + assert_emails(1) do + @delivery_method.deliver + end + end + + test "sends email (with kwargs, replacing default argument)" do + set_config( + mailer: "UserMailer", + method: "greeting", + params: -> { {foo: :bar} }, + kwargs: -> { {body: "Custom", subject: "Testing"} } + ) + + assert_emails(1) do + @delivery_method.deliver + end + end + + test "raises the underlying ArgumentError if kwargs are missing" do + set_config( + mailer: "UserMailer", + method: "greeting", + params: -> { {foo: :bar} }, + kwargs: -> { {baz: 123} } + ) + + error = assert_raises ArgumentError do + @delivery_method.deliver + end + + assert_equal "missing keyword: :body", error.message + end + + test "raises the underlying ArgumentError if unknown kwargs are given" do + set_config( + mailer: "UserMailer", + method: "greeting", + params: -> { {foo: :bar} }, + kwargs: -> { {body: "Test", baz: 123} } + ) + + error = assert_raises ArgumentError do + @delivery_method.deliver + end + + assert_equal "unknown keyword: :baz", error.message + end + + test "raises an ArgumentError if both args and kwargs are present, since only one can be used" do + set_config( + mailer: "UserMailer", + method: "greeting", + params: -> { {foo: :bar} }, + args: -> { ["hey"] }, + kwargs: -> { {body: "Test"} } + ) + + error = assert_raises ArgumentError do + @delivery_method.deliver + end + + assert_equal "`args` and `kwargs` cannot both be provided.", error.message + end + test "enqueues email" do set_config( mailer: "UserMailer", diff --git a/test/dummy/app/mailers/user_mailer.rb b/test/dummy/app/mailers/user_mailer.rb index ba07b19e..10f57165 100644 --- a/test/dummy/app/mailers/user_mailer.rb +++ b/test/dummy/app/mailers/user_mailer.rb @@ -3,6 +3,10 @@ def new_comment(*args) mail(body: "new comment") end + def greeting(body:, subject: "Hello") + mail(body: body, subject: subject) + end + def receipt mail(body: "receipt") end