Skip to content

Commit

Permalink
Callbacks and deliver short circuiting (#72)
Browse files Browse the repository at this point in the history
* allows the emails to have before/after callbacks. Fixes #65

* adds in functionality to stop an email from being delivered. Fixes #61
  • Loading branch information
jwoertink authored Aug 15, 2022
1 parent 6cda3dd commit d7d9b30
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 1 deletion.
105 changes: 105 additions & 0 deletions spec/callbacks_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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|
setting.adapter = Carbon::DevAdapter.new
end

private class EmailWithBeforeCallbacks < BaseTestEmail
property ran_before_callback : Bool = false

before_send do
self.ran_before_callback = true
end
end

private class EmailWithAfterCallbacks < BaseTestEmail
property ran_after_callback : Bool = false

after_send do |_response|
self.ran_after_callback = true
end
end

private class EmailWithBothBeforeAndAfterCallbacks < BaseTestEmail
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

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
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

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
14 changes: 14 additions & 0 deletions spec/email_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
81 changes: 81 additions & 0 deletions src/carbon/callbacks.cr
Original file line number Diff line number Diff line change
@@ -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
16 changes: 15 additions & 1 deletion src/carbon/email.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "ecr"

abstract class Carbon::Email
include Carbon::Callbacks
alias Recipients = Carbon::Emailable | Array(Carbon::Emailable)

abstract def subject : String
Expand All @@ -9,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
Expand All @@ -25,6 +30,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
Expand Down Expand Up @@ -122,7 +131,12 @@ abstract class Carbon::Email
end

def deliver
settings.adapter.deliver_now(self)
before_send

if deliverable?
response = settings.adapter.deliver_now(self)
after_send(response)
end
end

def deliver_later
Expand Down

0 comments on commit d7d9b30

Please sign in to comment.