From 9d6ea7b354bd58444746eed545887d45ef5e1153 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Sat, 13 Aug 2022 10:45:26 -0700 Subject: [PATCH 1/2] allows the emails to have before/after callbacks. Fixes #65 --- spec/callbacks_spec.cr | 89 +++++++++++++++++++++++++++++++++++++++++ src/carbon/callbacks.cr | 81 +++++++++++++++++++++++++++++++++++++ src/carbon/email.cr | 9 ++++- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 spec/callbacks_spec.cr create mode 100644 src/carbon/callbacks.cr diff --git a/spec/callbacks_spec.cr b/spec/callbacks_spec.cr new file mode 100644 index 0000000..66689d3 --- /dev/null +++ b/spec/callbacks_spec.cr @@ -0,0 +1,89 @@ +require "./spec_helper" + +abstract class BaseTestEmail < Carbon::Email +end + +BaseTestEmail.configure do |setting| + setting.adapter = Carbon::DevAdapter.new +end + +private class EmailWithBeforeCallbacks < BaseTestEmail + subject "My great subject" + from Carbon::Address.new("from@example.com") + to Carbon::Address.new("to@example.com") + + property ran_before_callback : Bool = false + + before_send do + self.ran_before_callback = true + end +end + +private class EmailWithAfterCallbacks < BaseTestEmail + subject "My great subject" + from Carbon::Address.new("from@example.com") + to Carbon::Address.new("to@example.com") + + property ran_after_callback : Bool = false + + after_send do |_response| + self.ran_after_callback = true + end +end + +private class EmailWithBothBeforeAndAfterCallbacks < BaseTestEmail + subject "My great subject" + from Carbon::Address.new("from@example.com") + to Carbon::Address.new("to@example.com") + + property ran_before_callback : Bool = false + property ran_after_callback : Bool = false + + before_send :mark_before_send + after_send :mark_after_send + + private def mark_before_send + self.ran_before_callback = true + end + + private def mark_after_send(_response) + self.ran_after_callback = true + end +end + +describe "before/after callbacks" do + context "before an email is sent" do + it "runs the before_send callback" do + email = EmailWithBeforeCallbacks.new + email.ran_before_callback.should eq(false) + email.deliver + Carbon.should have_delivered_emails + + email.ran_before_callback.should eq(true) + end + end + + context "after an email is sent" do + it "runs the after_send callback" do + email = EmailWithAfterCallbacks.new + email.ran_after_callback.should eq(false) + email.deliver + Carbon.should have_delivered_emails + + email.ran_after_callback.should eq(true) + end + end + + context "running both callbacks" do + it "runs both callbacks" do + email = EmailWithBothBeforeAndAfterCallbacks.new + email.ran_before_callback.should eq(false) + email.ran_after_callback.should eq(false) + email.deliver + Carbon.should have_delivered_emails + + email.ran_before_callback.should eq(true) + email.ran_after_callback.should eq(true) + end + end +end diff --git a/src/carbon/callbacks.cr b/src/carbon/callbacks.cr new file mode 100644 index 0000000..e038028 --- /dev/null +++ b/src/carbon/callbacks.cr @@ -0,0 +1,81 @@ +module Carbon::Callbacks + # Runs the given method before the adapter calls `deliver_now` + # + # ``` + # before_send :attach_metadata + # + # private def attach_metadata + # #... + # end + # ``` + macro before_send(method_name) + before_send do + {{ method_name.id }} + end + end + + # Runs the block before the adapter calls `deliver_now` + # + # ``` + # before_send do + # #... + # end + # ``` + macro before_send + def before_send + {% if @type.methods.map(&.name).includes?(:before_send.id) %} + previous_def + {% else %} + super + {% end %} + + {{ yield }} + end + end + + # Runs the given method after the adapter calls `deliver_now`. + # Passes in the return value of the adapter's `deliver_now` method. + # + # ``` + # after_send :mark_email_as_sent + # + # private def mark_email_as_sent(response) + # # ... + # end + # ``` + macro after_send(method_name) + after_send do |object| + {{ method_name.id }}(object) + end + end + + # Runs the block after the adapter calls `deliver_now`, and passes the + # return value of the adapter's `deliver_now` method to the block. + # + # ``` + # after_send do |response| + # # ... + # end + # ``` + macro after_send(&block) + {% + if block.args.size != 1 + raise <<-ERR + The 'after_send' callback requires exactly 1 block arg to be passed. + Example: + after_send { |value| some_method(value) } + ERR + end + %} + def after_send(%object) + {% if @type.methods.map(&.name).includes?(:after_send.id) %} + previous_def + {% else %} + super + {% end %} + + {{ block.args.first }} = %object + {{ block.body }} + end + end +end diff --git a/src/carbon/email.cr b/src/carbon/email.cr index ec84ced..83f74c8 100644 --- a/src/carbon/email.cr +++ b/src/carbon/email.cr @@ -1,6 +1,7 @@ require "ecr" abstract class Carbon::Email + include Carbon::Callbacks alias Recipients = Carbon::Emailable | Array(Carbon::Emailable) abstract def subject : String @@ -25,6 +26,10 @@ abstract class Carbon::Email def html_layout(content_io : IO); end + def before_send; end + + def after_send(result); end + getter headers macro inherited @@ -122,7 +127,9 @@ abstract class Carbon::Email end def deliver - settings.adapter.deliver_now(self) + before_send + response = settings.adapter.deliver_now(self) + after_send(response) end def deliver_later From 66c29281ad9de25cbef6f09dd7af6f71538c1ff4 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Sat, 13 Aug 2022 11:09:08 -0700 Subject: [PATCH 2/2] adds in functionality to stop an email from being delivered. Fixes #61 --- spec/callbacks_spec.cr | 40 ++++++++++++++++++++++++++++------------ spec/email_spec.cr | 14 ++++++++++++++ src/carbon/callbacks.cr | 4 ++-- src/carbon/email.cr | 11 +++++++++-- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/spec/callbacks_spec.cr b/spec/callbacks_spec.cr index 66689d3..b02bbf1 100644 --- a/spec/callbacks_spec.cr +++ b/spec/callbacks_spec.cr @@ -1,6 +1,9 @@ require "./spec_helper" abstract class BaseTestEmail < Carbon::Email + subject "My great subject" + from Carbon::Address.new("from@example.com") + to Carbon::Address.new("to@example.com") end BaseTestEmail.configure do |setting| @@ -8,10 +11,6 @@ BaseTestEmail.configure do |setting| end private class EmailWithBeforeCallbacks < BaseTestEmail - subject "My great subject" - from Carbon::Address.new("from@example.com") - to Carbon::Address.new("to@example.com") - property ran_before_callback : Bool = false before_send do @@ -20,10 +19,6 @@ private class EmailWithBeforeCallbacks < BaseTestEmail end private class EmailWithAfterCallbacks < BaseTestEmail - subject "My great subject" - from Carbon::Address.new("from@example.com") - to Carbon::Address.new("to@example.com") - property ran_after_callback : Bool = false after_send do |_response| @@ -32,10 +27,6 @@ private class EmailWithAfterCallbacks < BaseTestEmail end private class EmailWithBothBeforeAndAfterCallbacks < BaseTestEmail - subject "My great subject" - from Carbon::Address.new("from@example.com") - to Carbon::Address.new("to@example.com") - property ran_before_callback : Bool = false property ran_after_callback : Bool = false @@ -51,6 +42,21 @@ private class EmailWithBothBeforeAndAfterCallbacks < BaseTestEmail end end +private class EmailUsingBeforeToStopSending < BaseTestEmail + before_send :dont_actually_send + after_send :never_actually_ran + + property ran_after_callback : Bool = false + + private def dont_actually_send + @deliverable = false + end + + private def never_actually_ran(_response) + self.ran_after_callback = true + end +end + describe "before/after callbacks" do context "before an email is sent" do it "runs the before_send callback" do @@ -86,4 +92,14 @@ describe "before/after callbacks" do email.ran_after_callback.should eq(true) end end + + context "Halting the deliver before it's sent" do + it "never sends" do + email = EmailUsingBeforeToStopSending.new + email.deliver + Carbon.should_not have_delivered_emails + email.deliverable?.should eq(false) + email.ran_after_callback.should eq(false) + end + end end diff --git a/spec/email_spec.cr b/spec/email_spec.cr index b995b78..1968aaf 100644 --- a/spec/email_spec.cr +++ b/spec/email_spec.cr @@ -78,6 +78,12 @@ private class EmailWithLayout < BareMinimumEmail layout custom_layout end +private class UndeliverableEmail < Carbon::Email + subject "My great subject" + from Carbon::Address.new("from@example.com") + to Carbon::Address.new("to@example.com") +end + describe Carbon::Email do it "can build a bare minimum email" do email = BareMinimumEmail.new @@ -146,4 +152,12 @@ describe Carbon::Email do email.html_body.should contain "Email Layout" email.html_body.should contain "Email body" end + + context "deliverable?" do + it "is not delivery it is digiorno" do + email = UndeliverableEmail.new + email.deliverable = false + email.deliverable?.should eq(false) + end + end end diff --git a/src/carbon/callbacks.cr b/src/carbon/callbacks.cr index e038028..cfd4649 100644 --- a/src/carbon/callbacks.cr +++ b/src/carbon/callbacks.cr @@ -5,7 +5,7 @@ module Carbon::Callbacks # before_send :attach_metadata # # private def attach_metadata - # #... + # # ... # end # ``` macro before_send(method_name) @@ -18,7 +18,7 @@ module Carbon::Callbacks # # ``` # before_send do - # #... + # # ... # end # ``` macro before_send diff --git a/src/carbon/email.cr b/src/carbon/email.cr index 83f74c8..45c3c63 100644 --- a/src/carbon/email.cr +++ b/src/carbon/email.cr @@ -10,6 +10,10 @@ abstract class Carbon::Email def_equals subject, from, to, cc, bcc, headers, text_body, html_body + # Set this value to `false` to prevent the email from + # being delivered + property? deliverable : Bool = true + def cc [] of Carbon::Address end @@ -128,8 +132,11 @@ abstract class Carbon::Email def deliver before_send - response = settings.adapter.deliver_now(self) - after_send(response) + + if deliverable? + response = settings.adapter.deliver_now(self) + after_send(response) + end end def deliver_later