feat(mailer): add Prism and Mailer
This commit is contained in:
1 parent
d37e21fd3a
commit
e4165f8cd6
3 files changed
+172
No files matched your search
@@ -0,0 +1,128 @@
|
|||||||
|
module Prism
|
||||||
|
module Mailer
|
||||||
|
# ActionMailer-lite: subclass, declare `default from: '...'`, define an
|
||||||
|
# instance method per mailer action that ends with a call to #mail.
|
||||||
|
# Calling the action as a class method (`WelcomeMailer.welcome(user)`)
|
||||||
|
# renders the templates immediately and returns a Delivery.
|
||||||
|
class Base
|
||||||
|
class << self
|
||||||
|
def default(options = nil)
|
||||||
|
@default_options = (@default_options || {}).merge(options) if options
|
||||||
|
@default_options || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_roots
|
||||||
|
@template_roots || [File.join(Dir.pwd, 'app', 'views', 'mailers')]
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_writer :template_roots
|
||||||
|
|
||||||
|
# Per-mailer-class override of Mailer.default_url_options, merged
|
||||||
|
# over it (this class's values win) by #url_for.
|
||||||
|
def default_url_options(options = nil)
|
||||||
|
@default_url_options = (@default_url_options || {}).merge(options) if options
|
||||||
|
@default_url_options || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def mailer_name
|
||||||
|
Inflector.underscore(name.to_s.split('::').last.sub(/Mailer\z/, ''))
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(name, *args, &block)
|
||||||
|
return super unless instance_methods(false).include?(name)
|
||||||
|
|
||||||
|
instance = new
|
||||||
|
instance.send(:run_action, name, args)
|
||||||
|
Delivery.new(instance.message, mailer_class: self, action_name: name.to_s, args: args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(name, include_private = false)
|
||||||
|
instance_methods(false).include?(name) || super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :message, :action_name
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@message = Message.new
|
||||||
|
apply_defaults
|
||||||
|
end
|
||||||
|
|
||||||
|
def mail(to:, subject:, from: nil, cc: nil, bcc: nil, reply_to: nil, template: nil, locals: {})
|
||||||
|
message.to = to
|
||||||
|
message.subject = subject
|
||||||
|
message.from = from if from
|
||||||
|
message.cc = cc if cc
|
||||||
|
message.bcc = bcc if bcc
|
||||||
|
message.reply_to = reply_to if reply_to
|
||||||
|
render_bodies(template || action_name, locals)
|
||||||
|
message
|
||||||
|
end
|
||||||
|
|
||||||
|
# Absolute URL for a named route, for use inside mailer actions and
|
||||||
|
# templates (there is no incoming request to infer a host from, unlike
|
||||||
|
# Controller#url_for). Requires Mailer.router to be set and a :host
|
||||||
|
# somewhere in Mailer.default_url_options / self.class.default_url_options
|
||||||
|
# / the params passed here.
|
||||||
|
def url_for(name, params = {})
|
||||||
|
router = Mailer.router
|
||||||
|
raise 'no router configured; set Prism::Mailer.router before generating mailer URLs' unless router
|
||||||
|
|
||||||
|
options = Mailer.default_url_options.merge(self.class.default_url_options).merge(params)
|
||||||
|
host = options.delete(:host)
|
||||||
|
raise ArgumentError, 'default_url_options must include :host to build a mailer URL' unless host
|
||||||
|
|
||||||
|
protocol = options.delete(:protocol) || 'http'
|
||||||
|
port = options.delete(:port)
|
||||||
|
"#{protocol}://#{host}#{port ? ":#{port}" : ''}#{router.path_to(name, options)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def run_action(name, args)
|
||||||
|
@action_name = name.to_s
|
||||||
|
public_send(name, *args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_defaults
|
||||||
|
self.class.default.each do |key, value|
|
||||||
|
message.public_send("#{key}=", value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_bodies(name, locals)
|
||||||
|
view = Prism::View.new(self.class.template_roots, helpers: self)
|
||||||
|
merged_locals = auto_locals.merge(locals)
|
||||||
|
found_any = false
|
||||||
|
|
||||||
|
begin
|
||||||
|
message.text_body = view.render("#{self.class.mailer_name}/#{name}.text", locals: merged_locals)
|
||||||
|
found_any = true
|
||||||
|
rescue Prism::View::TemplateNotFound
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
message.html_body = view.render("#{self.class.mailer_name}/#{name}.html", locals: merged_locals)
|
||||||
|
found_any = true
|
||||||
|
rescue Prism::View::TemplateNotFound
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return if found_any
|
||||||
|
|
||||||
|
raise Prism::View::TemplateNotFound,
|
||||||
|
"no mailer template #{name}.text.erb or #{name}.html.erb for " \
|
||||||
|
"#{self.class.mailer_name} in #{self.class.template_roots.join(', ')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def auto_locals
|
||||||
|
instance_variables.each_with_object({}) do |ivar, locals|
|
||||||
|
next if %i[@message @action_name].include?(ivar)
|
||||||
|
|
||||||
|
locals[ivar.to_s.delete_prefix('@').to_sym] = instance_variable_get(ivar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
module Prism
|
||||||
|
module Mailer
|
||||||
|
# Returned by a mailer action call (`UserMailer.welcome(user)`), in the
|
||||||
|
# spirit of ActionMailer's MessageDelivery: the message is already
|
||||||
|
# rendered at this point, #deliver_now hands it to Prism::Mailer.deliver!
|
||||||
|
# immediately, #deliver_later re-enqueues the action itself (mailer
|
||||||
|
# class, action name, args) as a Prism::Jobs::Job so a Worker performs
|
||||||
|
# the rendering and delivery out of process.
|
||||||
|
class Delivery
|
||||||
|
def initialize(message, mailer_class:, action_name:, args:)
|
||||||
|
@message = message
|
||||||
|
@mailer_class = mailer_class
|
||||||
|
@action_name = action_name
|
||||||
|
@args = args
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :message
|
||||||
|
|
||||||
|
def deliver_now
|
||||||
|
Mailer.deliver!(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver_later(queue: nil, run_at: nil)
|
||||||
|
DeliveryJob.perform_later(@mailer_class.name, @action_name, @args, queue: queue, run_at: run_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
module Prism
|
||||||
|
module Mailer
|
||||||
|
# Performs a mailer action out of process. Enqueued by
|
||||||
|
# Delivery#deliver_later with the mailer class name (resolved back
|
||||||
|
# through Object.const_get, same pattern Jobs::Worker already uses for
|
||||||
|
# job classes) so the queue only ever persists plain strings/arrays.
|
||||||
|
class DeliveryJob < Prism::Jobs::Job
|
||||||
|
queue_as 'mailers'
|
||||||
|
|
||||||
|
def perform(mailer_class_name, action_name, args)
|
||||||
|
mailer_class = Object.const_get(mailer_class_name)
|
||||||
|
mailer_class.public_send(action_name, *args).deliver_now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user