Remove global state. Immutable mailer. (#69)
This commit is contained in:
parent
dded5d2268
commit
d2b8a32ade
|
@ -2,3 +2,7 @@
|
||||||
# alphabetically
|
# alphabetically
|
||||||
inherit_from:
|
inherit_from:
|
||||||
- https://raw.githubusercontent.com/hanami/devtools/master/.rubocop.yml
|
- https://raw.githubusercontent.com/hanami/devtools/master/.rubocop.yml
|
||||||
|
Style/Documentation:
|
||||||
|
Exclude:
|
||||||
|
- "examples/*"
|
||||||
|
- "spec/**/*"
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -2,8 +2,9 @@ source 'https://rubygems.org'
|
||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
unless ENV['TRAVIS']
|
unless ENV['TRAVIS']
|
||||||
gem 'byebug', require: false, platforms: :mri
|
gem 'byebug', require: false, platforms: :mri
|
||||||
gem 'yard', require: false
|
gem 'allocation_stats', require: false
|
||||||
|
gem 'benchmark-ips', require: false
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'hanami-utils', '2.0.0.alpha1', require: false, git: 'https://github.com/hanami/utils.git', branch: 'unstable'
|
gem 'hanami-utils', '2.0.0.alpha1', require: false, git: 'https://github.com/hanami/utils.git', branch: 'unstable'
|
||||||
|
|
155
README.md
155
README.md
|
@ -44,10 +44,10 @@ Or install it yourself as:
|
||||||
|
|
||||||
### Conventions
|
### Conventions
|
||||||
|
|
||||||
* Templates are searched under `Hanami::Mailer.configuration.root`, set this value according to your app structure (eg. `"app/templates"`).
|
* Templates are searched under `Hanami::Mailer::Configuration#root`, set this value according to your app structure (eg. `"app/templates"`).
|
||||||
* A mailer will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
|
* A mailer will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
|
||||||
* A template must have two concatenated extensions: one for the format and one for the engine (eg. `".html.erb"`).
|
* A template must have two concatenated extensions: one for the format and one for the engine (eg. `".html.erb"`).
|
||||||
* The framework must be loaded before rendering the first time: `Hanami::Mailer.load!`.
|
* The framework must be loaded before rendering the first time: `Hanami::Mailer.finalize(configuration)`.
|
||||||
|
|
||||||
### Mailers
|
### Mailers
|
||||||
|
|
||||||
|
@ -55,10 +55,60 @@ A simple mailer looks like this:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
require 'ostruct'
|
||||||
|
|
||||||
class InvoiceMailer
|
# Create two files: `invoice.html.erb` and `invoice.txt.erb`
|
||||||
include Hanami::Mailer
|
|
||||||
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.delivery_method = :test
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Invoice < Hanami::Mailer
|
||||||
|
from "noreply@example.com"
|
||||||
|
to ->(locals) { locals.fetch(:user).email }
|
||||||
|
end
|
||||||
|
|
||||||
|
configuration = Hanami::Mailer.finalize(configuration)
|
||||||
|
|
||||||
|
invoice = OpenStruct.new(number: 23)
|
||||||
|
mailer = InvoiceMailer.new(configuration: configuration)
|
||||||
|
mail = mailer.deliver(invoice: invoice)
|
||||||
|
|
||||||
|
mail
|
||||||
|
# => #<Mail::Message:70303354246540, Multipart: true, Headers: <Date: Wed, 22 Mar 2017 11:48:57 +0100>, <From: noreply@example.com>, <To: user@example.com>, <Cc: >, <Bcc: >, <Message-ID: <58d25699e47f9_b4e13ff0c503e4f4632e6@escher.mail>>, <Subject: >, <Mime-Version: 1.0>, <Content-Type: multipart/alternative; boundary=--==_mimepart_58d25699e42d2_b4e13ff0c503e4f463186>, <Content-Transfer-Encoding: 7bit>>
|
||||||
|
|
||||||
|
mail.to_s
|
||||||
|
# =>
|
||||||
|
# From: noreply@example.com
|
||||||
|
# To: user@example.com
|
||||||
|
# Message-ID: <58d25699e47f9_b4e13ff0c503e4f4632e6@escher.mail>
|
||||||
|
# Subject:
|
||||||
|
# Mime-Version: 1.0
|
||||||
|
# Content-Type: multipart/alternative;
|
||||||
|
# boundary="--==_mimepart_58d25699e42d2_b4e13ff0c503e4f463186";
|
||||||
|
# charset=UTF-8
|
||||||
|
# Content-Transfer-Encoding: 7bit
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# ----==_mimepart_58d25699e42d2_b4e13ff0c503e4f463186
|
||||||
|
# Content-Type: text/plain;
|
||||||
|
# charset=UTF-8
|
||||||
|
# Content-Transfer-Encoding: 7bit
|
||||||
|
#
|
||||||
|
# Invoice #23
|
||||||
|
#
|
||||||
|
# ----==_mimepart_58d25699e42d2_b4e13ff0c503e4f463186
|
||||||
|
# Content-Type: text/html;
|
||||||
|
# charset=UTF-8
|
||||||
|
# Content-Transfer-Encoding: 7bit
|
||||||
|
#
|
||||||
|
# <html>
|
||||||
|
# <body>
|
||||||
|
# <h1>Invoice template</h1>
|
||||||
|
# </body>
|
||||||
|
# </html>
|
||||||
|
#
|
||||||
|
# ----==_mimepart_58d25699e42d2_b4e13ff0c503e4f463186--
|
||||||
```
|
```
|
||||||
|
|
||||||
A mailer with `.to` and `.from` addresses and mailer delivery:
|
A mailer with `.to` and `.from` addresses and mailer delivery:
|
||||||
|
@ -66,20 +116,18 @@ A mailer with `.to` and `.from` addresses and mailer delivery:
|
||||||
```ruby
|
```ruby
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
|
||||||
Hanami::Mailer.configure do
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
delivery_method :smtp,
|
config.delivery_method = :smtp,
|
||||||
address: "smtp.gmail.com",
|
address: "smtp.gmail.com",
|
||||||
port: 587,
|
port: 587,
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
user_name: ENV['SMTP_USERNAME'],
|
user_name: ENV['SMTP_USERNAME'],
|
||||||
password: ENV['SMTP_PASSWORD'],
|
password: ENV['SMTP_PASSWORD'],
|
||||||
authentication: "plain",
|
authentication: "plain",
|
||||||
enable_starttls_auto: true
|
enable_starttls_auto: true
|
||||||
end.load!
|
end
|
||||||
|
|
||||||
class WelcomeMailer
|
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
|
class WelcomeMailer < Hanami::Mailer
|
||||||
from 'noreply@sender.com'
|
from 'noreply@sender.com'
|
||||||
to 'noreply@recipient.com'
|
to 'noreply@recipient.com'
|
||||||
cc 'cc@sender.com'
|
cc 'cc@sender.com'
|
||||||
|
@ -88,7 +136,7 @@ class WelcomeMailer
|
||||||
subject 'Welcome'
|
subject 'Welcome'
|
||||||
end
|
end
|
||||||
|
|
||||||
WelcomeMailer.deliver
|
WelcomeMailer.new(configuration: configuration).call(locals)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Locals
|
### Locals
|
||||||
|
@ -97,25 +145,17 @@ The set of objects passed in the `deliver` call are called `locals` and are avai
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
require 'ostruct'
|
||||||
|
|
||||||
User = Struct.new(:name, :username, :email)
|
user = OpenStruct.new(name: Luca', email: 'user@hanamirb.org')
|
||||||
luca = User.new('Luca', 'jodosha', 'luca@jodosha.com')
|
|
||||||
|
|
||||||
class WelcomeMailer
|
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
|
class WelcomeMailer < Hanami::Mailer
|
||||||
from 'noreply@sender.com'
|
from 'noreply@sender.com'
|
||||||
subject 'Welcome'
|
subject 'Welcome'
|
||||||
to :recipient
|
to ->(locals) { locals.fetch(:user).email }
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def recipient
|
|
||||||
user.email
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
WelcomeMailer.deliver(user: luca)
|
WelcomeMailer.new(configuration: configuration).deliver(user: luca)
|
||||||
```
|
```
|
||||||
|
|
||||||
The corresponding `erb` file:
|
The corresponding `erb` file:
|
||||||
|
@ -131,9 +171,7 @@ All public methods defined in the mailer are accessible from the template:
|
||||||
```ruby
|
```ruby
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
|
||||||
class WelcomeMailer
|
class WelcomeMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
from 'noreply@sender.com'
|
from 'noreply@sender.com'
|
||||||
to 'noreply@recipient.com'
|
to 'noreply@recipient.com'
|
||||||
subject 'Welcome'
|
subject 'Welcome'
|
||||||
|
@ -154,7 +192,7 @@ The template file must be located under the relevant `root` and must match the i
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Given this root
|
# Given this root
|
||||||
Hanami::Mailer.configuration.root # => #<Pathname:app/templates>
|
configuration.root # => #<Pathname:app/templates>
|
||||||
|
|
||||||
# For InvoiceMailer, it looks for:
|
# For InvoiceMailer, it looks for:
|
||||||
# * app/templates/invoice_mailer.html.erb
|
# * app/templates/invoice_mailer.html.erb
|
||||||
|
@ -164,9 +202,7 @@ Hanami::Mailer.configuration.root # => #<Pathname:app/templates>
|
||||||
If we want to specify a different template, we can do:
|
If we want to specify a different template, we can do:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class InvoiceMailer
|
class InvoiceMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
template 'invoice'
|
template 'invoice'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -311,21 +347,22 @@ It supports a few options:
|
||||||
```ruby
|
```ruby
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
|
||||||
Hanami::Maler.configure do
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
# Set the root path where to search for templates
|
# Set the root path where to search for templates
|
||||||
# Argument: String, Pathname, #to_pathname, defaults to the current directory
|
# Argument: String, Pathname, #to_pathname, defaults to the current directory
|
||||||
#
|
#
|
||||||
root '/path/to/root'
|
config.root = 'path/to/root'
|
||||||
|
|
||||||
# Set the default charset for emails
|
# Set the default charset for emails
|
||||||
# Argument: String, defaults to "UTF-8"
|
# Argument: String, defaults to "UTF-8"
|
||||||
#
|
#
|
||||||
default_charset 'iso-8859'
|
config.default_charset = 'iso-8859'
|
||||||
|
|
||||||
# Set the delivery method
|
# Set the delivery method
|
||||||
# Argument: Symbol
|
# Argument: Symbol
|
||||||
# Argument: Hash, optional configurations
|
# Argument: Hash, optional configurations
|
||||||
delivery_method :stmp
|
config.delivery_method = :stmp
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Attachments
|
### Attachments
|
||||||
|
@ -333,14 +370,10 @@ Hanami::Maler.configure do
|
||||||
Attachments can be added with the following API:
|
Attachments can be added with the following API:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class InvoiceMailer
|
class InvoiceMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
# ...
|
# ...
|
||||||
|
before do |mail, locals|
|
||||||
def prepare
|
mail.attachments["invoice-#{locals.fetch(:invoice).number}.pdf"] = 'path/to/invoice.pdf'
|
||||||
mail.attachments['invoice.pdf'] = '/path/to/invoice.pdf'
|
|
||||||
# or
|
|
||||||
mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
@ -350,14 +383,14 @@ end
|
||||||
The global delivery method is defined through the __Hanami::Mailer__ configuration, as:
|
The global delivery method is defined through the __Hanami::Mailer__ configuration, as:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Hanami::Mailer.configuration do
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
delivery_method :smtp
|
config.delivery_method = :smtp
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Hanami::Mailer.configuration do
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
delivery_method :smtp, address: "localhost", port: 1025
|
config.delivery_method = :smtp, { address: "localhost", port: 1025 }
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -386,14 +419,14 @@ class MandrillDeliveryMethod
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Hanami::Mailer.configure do
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
delivery_method MandrillDeliveryMethod,
|
config.delivery_method = MandrillDeliveryMethod,
|
||||||
username: ENV['MANDRILL_USERNAME'],
|
username: ENV['MANDRILL_USERNAME'],
|
||||||
password: ENV['MANDRILL_API_KEY']
|
password: ENV['MANDRILL_API_KEY']
|
||||||
end.load!
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
The class passed to `.delivery_method` must accept an optional set of options
|
The class passed to `.delivery_method=` must accept an optional set of options
|
||||||
with the constructor (`#initialize`) and respond to `#deliver!`.
|
with the constructor (`#initialize`) and respond to `#deliver!`.
|
||||||
|
|
||||||
### Multipart Delivery
|
### Multipart Delivery
|
||||||
|
@ -402,8 +435,8 @@ All the email are sent as multipart messages by default.
|
||||||
For a given mailer, the framework looks up for associated text (`.txt`) and `HTML` (`.html`) templates and render them.
|
For a given mailer, the framework looks up for associated text (`.txt`) and `HTML` (`.html`) templates and render them.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
InvoiceMailer.deliver # delivers both text and html templates
|
InvoiceMailer.new(configuration: configuration).deliver({}) # delivers both text and html templates
|
||||||
InvoiceMailer.deliver(format: :txt) # delivers only text template
|
InvoiceMailer.new(configuration: configuration).deliver(format: :txt) # delivers only text template
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that **they aren't both mandatory, but at least one of them MUST** be present.
|
Please note that **they aren't both mandatory, but at least one of them MUST** be present.
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'bundler/setup'
|
||||||
|
require 'hanami/mailer'
|
||||||
|
require 'benchmark/ips'
|
||||||
|
require 'allocation_stats'
|
||||||
|
require_relative './examples/base'
|
||||||
|
|
||||||
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.root = "examples/base"
|
||||||
|
config.delivery_method = :test
|
||||||
|
end
|
||||||
|
|
||||||
|
configuration = Hanami::Mailer.finalize(configuration)
|
||||||
|
|
||||||
|
invoice = Invoice.new(1, 23)
|
||||||
|
user = User.new("Luca", "luca@domain.test")
|
||||||
|
|
||||||
|
mailer = InvoiceMailer.new(configuration: configuration)
|
||||||
|
|
||||||
|
Benchmark.ips do |x|
|
||||||
|
# # Configure the number of seconds used during
|
||||||
|
# # the warmup phase (default 2) and calculation phase (default 5)
|
||||||
|
# x.config(time: 5, warmup: 2)
|
||||||
|
x.report "deliver" do
|
||||||
|
mailer.deliver(invoice: invoice, user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stats = AllocationStats.new(burn: 5).trace do
|
||||||
|
1_000.times do
|
||||||
|
mailer.deliver(invoice: invoice, user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
total_allocations = stats.allocations.all.size
|
||||||
|
puts "total allocations: #{total_allocations}"
|
||||||
|
|
||||||
|
total_memsize = stats.allocations.bytes.to_a.inject(&:+)
|
||||||
|
puts "total memsize: #{total_memsize}"
|
||||||
|
|
||||||
|
detailed_allocations = stats.allocations(alias_paths: true)
|
||||||
|
.group_by(:sourcefile, :class_plus)
|
||||||
|
.sort_by_count
|
||||||
|
.to_text
|
||||||
|
|
||||||
|
puts 'allocations by source file and class:'
|
||||||
|
puts detailed_allocations
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
threshold: 10
|
|
||||||
total_score: 77
|
|
|
@ -1,2 +1,2 @@
|
||||||
---
|
---
|
||||||
threshold: 20.2
|
threshold: 18.4
|
||||||
|
|
|
@ -25,12 +25,12 @@ DuplicateMethodCall:
|
||||||
FeatureEnvy:
|
FeatureEnvy:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Hanami::Mailer#build
|
- Hanami::Mailer#__part?
|
||||||
LongParameterList:
|
LongParameterList:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Devtools::Config#self.attribute
|
- Devtools::Config#self.attribute
|
||||||
max_params: 2
|
max_params: 4
|
||||||
overrides: {}
|
overrides: {}
|
||||||
LongYieldList:
|
LongYieldList:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@ -44,28 +44,23 @@ NestedIterators:
|
||||||
NilCheck:
|
NilCheck:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Hanami::Mailer::Configuration#default_charset
|
|
||||||
- Hanami::Mailer::Configuration#delivery_method
|
|
||||||
- Hanami::Mailer::Dsl#bcc
|
- Hanami::Mailer::Dsl#bcc
|
||||||
- Hanami::Mailer::Dsl#cc
|
- Hanami::Mailer::Dsl#cc
|
||||||
- Hanami::Mailer::Dsl#from
|
- Hanami::Mailer::Dsl#from
|
||||||
- Hanami::Mailer::Dsl#subject
|
- Hanami::Mailer::Dsl#subject
|
||||||
- Hanami::Mailer::Dsl#template
|
|
||||||
- Hanami::Mailer::Dsl#templates
|
|
||||||
- Hanami::Mailer::Dsl#to
|
- Hanami::Mailer::Dsl#to
|
||||||
- Hanami::Mailer#__part?
|
- Hanami::Mailer#__part?
|
||||||
RepeatedConditional:
|
RepeatedConditional:
|
||||||
enabled: true
|
enabled: true
|
||||||
max_ifs: 1
|
max_ifs: 1
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer::Configuration
|
|
||||||
TooManyConstants:
|
TooManyConstants:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Devtools
|
- Devtools
|
||||||
TooManyInstanceVariables:
|
TooManyInstanceVariables:
|
||||||
enabled: true
|
enabled: true
|
||||||
max_instance_variables: 2
|
max_instance_variables: 3
|
||||||
exclude:
|
exclude:
|
||||||
- Hanami::Mailer::Configuration
|
- Hanami::Mailer::Configuration
|
||||||
TooManyMethods:
|
TooManyMethods:
|
||||||
|
@ -76,12 +71,10 @@ TooManyStatements:
|
||||||
enabled: true
|
enabled: true
|
||||||
max_statements: 5
|
max_statements: 5
|
||||||
exclude:
|
exclude:
|
||||||
- initialize
|
- Hanami::Mailer#bind
|
||||||
- Hanami::Mailer::Configuration#duplicate
|
- Hanami::Mailer::Configuration#initialize
|
||||||
- Hanami::Mailer::Dsl#self.extended
|
- Hanami::Mailer::Dsl#self.extended
|
||||||
- Hanami::Mailer::Rendering::TemplatesFinder#find
|
- Hanami::Mailer::TemplatesFinder#find
|
||||||
- Hanami::Mailer#build
|
|
||||||
- Hanami::Mailer#self.included
|
|
||||||
UncommunicativeMethodName:
|
UncommunicativeMethodName:
|
||||||
enabled: true
|
enabled: true
|
||||||
reject:
|
reject:
|
||||||
|
@ -104,9 +97,7 @@ UncommunicativeParameterName:
|
||||||
- !ruby/regexp /[0-9]$/
|
- !ruby/regexp /[0-9]$/
|
||||||
- !ruby/regexp /[A-Z]/
|
- !ruby/regexp /[A-Z]/
|
||||||
accept: []
|
accept: []
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer#method_missing
|
|
||||||
- Hanami::Mailer#respond_to_missing?
|
|
||||||
UncommunicativeVariableName:
|
UncommunicativeVariableName:
|
||||||
enabled: true
|
enabled: true
|
||||||
reject:
|
reject:
|
||||||
|
@ -114,10 +105,7 @@ UncommunicativeVariableName:
|
||||||
- !ruby/regexp /[0-9]$/
|
- !ruby/regexp /[0-9]$/
|
||||||
- !ruby/regexp /[A-Z]/
|
- !ruby/regexp /[A-Z]/
|
||||||
accept: []
|
accept: []
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer::Configuration#duplicate
|
|
||||||
- Hanami::Mailer::Configuration#load!
|
|
||||||
- Hanami::Mailer#build
|
|
||||||
UnusedParameters:
|
UnusedParameters:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude: []
|
exclude: []
|
||||||
|
@ -125,15 +113,10 @@ UtilityFunction:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Devtools::Project::Initializer::Rspec#require_files # intentional for deduplication
|
- Devtools::Project::Initializer::Rspec#require_files # intentional for deduplication
|
||||||
- Hanami::Mailer::Rendering::TemplateName#tokens
|
|
||||||
max_helper_calls: 0
|
max_helper_calls: 0
|
||||||
PrimaDonnaMethod:
|
PrimaDonnaMethod:
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer::Configuration
|
|
||||||
- Hanami::Mailer::Rendering::TemplateName
|
|
||||||
ModuleInitialize:
|
ModuleInitialize:
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer
|
|
||||||
InstanceVariableAssumption:
|
InstanceVariableAssumption:
|
||||||
exclude:
|
exclude: []
|
||||||
- Hanami::Mailer::Configuration
|
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
inherit_from:
|
|
||||||
- https://raw.githubusercontent.com/hanami/hanami/master/.rubocop.yml
|
|
||||||
|
|
||||||
Metrics/BlockLength:
|
|
||||||
Exclude:
|
|
||||||
# Ignore RSpec DSL
|
|
||||||
- spec/**/*
|
|
||||||
|
|
||||||
# Avoid parameter lists longer than four parameters.
|
|
||||||
ParameterLists:
|
|
||||||
Max: 4
|
|
||||||
CountKeywordArgs: true
|
|
||||||
|
|
||||||
# Avoid more than `Max` levels of nesting.
|
|
||||||
BlockNesting:
|
|
||||||
Max: 3
|
|
||||||
|
|
||||||
# Align with the style guide.
|
|
||||||
CollectionMethods:
|
|
||||||
PreferredMethods:
|
|
||||||
collect: 'map'
|
|
||||||
inject: 'reduce'
|
|
||||||
find: 'detect'
|
|
||||||
find_all: 'select'
|
|
||||||
|
|
||||||
# Disable documentation checking until a class needs to be documented once
|
|
||||||
Documentation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Do not favor modifier if/unless usage when you have a single-line body
|
|
||||||
IfUnlessModifier:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow case equality operator (in limited use within the specs)
|
|
||||||
CaseEquality:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Constants do not always have to use SCREAMING_SNAKE_CASE
|
|
||||||
ConstantName:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Not all trivial readers/writers can be defined with attr_* methods
|
|
||||||
TrivialAccessors:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow empty lines around class body
|
|
||||||
EmptyLinesAroundClassBody:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow empty lines around module body
|
|
||||||
EmptyLinesAroundModuleBody:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow empty lines around block body
|
|
||||||
EmptyLinesAroundBlockBody:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow multiple line operations to not require indentation
|
|
||||||
MultilineOperationIndentation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Prefer String#% over Kernel#sprintf
|
|
||||||
FormatString:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Align if/else blocks with the variable assignment
|
|
||||||
EndAlignment:
|
|
||||||
EnforcedStyleAlignWith: variable
|
|
||||||
|
|
||||||
# Do not always align parameters when it is easier to read
|
|
||||||
AlignParameters:
|
|
||||||
Exclude:
|
|
||||||
- spec/**/*_spec.rb
|
|
||||||
|
|
||||||
# Prefer #kind_of? over #is_a?
|
|
||||||
ClassCheck:
|
|
||||||
EnforcedStyle: kind_of?
|
|
||||||
|
|
||||||
# Do not prefer double quotes to be used when %q or %Q is more appropriate
|
|
||||||
UnneededPercentQ:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Do not prefer lambda.call(...) over lambda.(...)
|
|
||||||
LambdaCall:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Allow additional spaces
|
|
||||||
ExtraSpacing:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# All objects can still be mutated if their eigenclass is patched
|
|
||||||
RedundantFreeze:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Prefer using `fail` when raising and `raise` when reraising
|
|
||||||
SignalException:
|
|
||||||
Enabled: false
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'bundler/setup'
|
||||||
|
require 'hanami/mailer'
|
||||||
|
|
||||||
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.root = File.expand_path(__dir__, "base")
|
||||||
|
config.delivery_method = :test
|
||||||
|
end
|
||||||
|
|
||||||
|
class Invoice
|
||||||
|
attr_reader :id, :number
|
||||||
|
|
||||||
|
def initialize(id, number)
|
||||||
|
@id = id
|
||||||
|
@number = number
|
||||||
|
freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class User
|
||||||
|
attr_reader :name, :email
|
||||||
|
|
||||||
|
def initialize(name, email)
|
||||||
|
@name = name
|
||||||
|
@email = email
|
||||||
|
freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class InvoiceMailer < Hanami::Mailer
|
||||||
|
template "invoice"
|
||||||
|
|
||||||
|
from "invoices@domain.test"
|
||||||
|
to ->(locals) { locals.fetch(:user).email }
|
||||||
|
|
||||||
|
subject ->(locals) { "Invoice ##{locals.fetch(:invoice).number}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
configuration = Hanami::Mailer.finalize(configuration)
|
||||||
|
|
||||||
|
invoice = Invoice.new(1, 23)
|
||||||
|
user = User.new("Luca", "luca@domain.test")
|
||||||
|
|
||||||
|
mailer = InvoiceMailer.new(configuration: configuration)
|
||||||
|
puts mailer.deliver(invoice: invoice, user: user)
|
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Invoice</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Invoice #<%= invoice.number %></h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
Invoice #<%= invoice.number %>
|
|
@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
|
||||||
|
|
||||||
spec.add_development_dependency 'bundler', '~> 1.15'
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
||||||
spec.add_development_dependency 'rake', '~> 12'
|
spec.add_development_dependency 'rake', '~> 12'
|
||||||
spec.add_development_dependency 'rspec', '~> 3.5'
|
spec.add_development_dependency 'rspec', '~> 3.7'
|
||||||
end
|
end
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
require 'hanami/mailer' # rubocop:disable Naming/FileName
|
|
|
@ -1,8 +1,7 @@
|
||||||
require 'hanami/utils/class_attribute'
|
# frozen_string_literal: true
|
||||||
require 'hanami/mailer/version'
|
|
||||||
require 'hanami/mailer/configuration'
|
|
||||||
require 'hanami/mailer/dsl'
|
|
||||||
require 'mail'
|
require 'mail'
|
||||||
|
require 'concurrent'
|
||||||
|
|
||||||
# Hanami
|
# Hanami
|
||||||
#
|
#
|
||||||
|
@ -11,23 +10,12 @@ module Hanami
|
||||||
# Hanami::Mailer
|
# Hanami::Mailer
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
module Mailer
|
class Mailer
|
||||||
# Base error for Hanami::Mailer
|
require 'hanami/mailer/version'
|
||||||
#
|
require 'hanami/mailer/template'
|
||||||
# @since 0.1.0
|
require 'hanami/mailer/finalizer'
|
||||||
class Error < ::StandardError
|
require 'hanami/mailer/configuration'
|
||||||
end
|
require 'hanami/mailer/dsl'
|
||||||
|
|
||||||
# Missing delivery data error
|
|
||||||
#
|
|
||||||
# It's raised when a mailer doesn't specify <tt>from</tt> or <tt>to</tt>.
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
class MissingDeliveryDataError < Error
|
|
||||||
def initialize
|
|
||||||
super("Missing delivery data, please check 'from', or 'to'")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Content types mapping
|
# Content types mapping
|
||||||
#
|
#
|
||||||
|
@ -38,173 +26,156 @@ module Hanami
|
||||||
txt: 'text/plain'
|
txt: 'text/plain'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
include Utils::ClassAttribute
|
private_constant(:CONTENT_TYPES)
|
||||||
|
|
||||||
# @since 0.1.0
|
# Base error for Hanami::Mailer
|
||||||
# @api private
|
|
||||||
class_attribute :configuration
|
|
||||||
self.configuration = Configuration.new
|
|
||||||
|
|
||||||
# Configure the framework.
|
|
||||||
# It yields the given block in the context of the configuration
|
|
||||||
#
|
|
||||||
# @param blk [Proc] the configuration block
|
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
#
|
class Error < ::StandardError
|
||||||
# @see Hanami::Mailer::Configuration
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# Hanami::Mailer.configure do
|
|
||||||
# root '/path/to/root'
|
|
||||||
# end
|
|
||||||
def self.configure(&blk)
|
|
||||||
configuration.instance_eval(&blk)
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Unknown mailer
|
||||||
|
#
|
||||||
|
# This error is raised at the runtime when trying to deliver a mail message,
|
||||||
|
# by using a configuration that it wasn't finalized yet.
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
#
|
||||||
|
# @see Hanami::Mailer.finalize
|
||||||
|
class UnknownMailerError < Error
|
||||||
|
# @param mailer [Hanami::Mailer] a mailer
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def initialize(mailer)
|
||||||
|
super("Unknown mailer: #{mailer.inspect}. Please finalize the configuration before to use it.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Missing delivery data error
|
||||||
|
#
|
||||||
|
# It's raised when a mailer doesn't specify `from` or `to`.
|
||||||
|
#
|
||||||
|
# @since 0.1.0
|
||||||
|
class MissingDeliveryDataError < Error
|
||||||
|
def initialize
|
||||||
|
super("Missing delivery data, please check 'from', or 'to'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
@_subclasses = Concurrent::Array.new
|
||||||
|
|
||||||
# Override Ruby's hook for modules.
|
# Override Ruby's hook for modules.
|
||||||
# It includes basic Hanami::Mailer modules to the given Class.
|
# It includes basic `Hanami::Mailer` modules to the given Class.
|
||||||
# It sets a copy of the framework configuration
|
# It sets a copy of the framework configuration
|
||||||
#
|
#
|
||||||
# @param base [Class] the target mailer
|
# @param base [Class] the target mailer
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since next
|
||||||
# @api private
|
# @api unstable
|
||||||
#
|
def self.inherited(base)
|
||||||
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
@_subclasses.push(base)
|
||||||
def self.included(base)
|
base.extend Dsl
|
||||||
conf = configuration
|
|
||||||
conf.add_mailer(base)
|
|
||||||
|
|
||||||
base.class_eval do
|
|
||||||
extend Dsl
|
|
||||||
extend ClassMethods
|
|
||||||
|
|
||||||
include Utils::ClassAttribute
|
|
||||||
class_attribute :configuration
|
|
||||||
|
|
||||||
self.configuration = conf.duplicate
|
|
||||||
end
|
|
||||||
|
|
||||||
conf.copy!(base)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Test deliveries
|
private_class_method :inherited
|
||||||
|
|
||||||
|
# Finalize the configuration
|
||||||
#
|
#
|
||||||
# This is a collection of delivered messages, used when <tt>delivery_method</tt>
|
# This should be used before to start to use the mailers
|
||||||
# is set on <tt>:test</tt>
|
|
||||||
#
|
#
|
||||||
# @return [Array] a collection of delivered messages
|
# @param configuration [Hanami::Mailer::Configuration] the configuration to
|
||||||
|
# finalize
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @return [Hanami::Mailer::Configuration] the finalized configuration
|
||||||
#
|
#
|
||||||
# @see Hanami::Mailer::Configuration#delivery_mode
|
# @since next
|
||||||
|
# @api unstable
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configure do
|
# configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
# delivery_method :test
|
# # ...
|
||||||
# end.load!
|
# end
|
||||||
#
|
#
|
||||||
# # In testing code
|
# configuration = Hanami::Mailer.finalize(configuration)
|
||||||
# Signup::Welcome.deliver
|
# MyMailer.new(configuration: configuration)
|
||||||
# Hanami::Mailer.deliveries.count # => 1
|
def self.finalize(configuration)
|
||||||
def self.deliveries
|
Finalizer.finalize(@_subclasses, configuration)
|
||||||
Mail::TestMailer.deliveries
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load the framework
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def self.load!
|
|
||||||
Mail.eager_autoload!
|
|
||||||
configuration.load!
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
module ClassMethods
|
|
||||||
# Delivers a multipart email message.
|
|
||||||
#
|
|
||||||
# When a mailer defines a <tt>html</tt> and <tt>txt</tt> template, they are
|
|
||||||
# both delivered.
|
|
||||||
#
|
|
||||||
# In order to selectively deliver only one of the two templates, use
|
|
||||||
# <tt>Signup::Welcome.deliver(format: :txt)</tt>
|
|
||||||
#
|
|
||||||
# All the given locals, excepted the reserved ones (<tt>:format</tt> and
|
|
||||||
# <tt>charset</tt>), are available as rendering context for the templates.
|
|
||||||
#
|
|
||||||
# @param locals [Hash] a set of objects that acts as context for the rendering
|
|
||||||
# @option :format [Symbol] specify format to deliver
|
|
||||||
# @option :charset [String] charset
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @see Hanami::Mailer::Configuration#default_charset
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# Hanami::Mailer.configure do
|
|
||||||
# delivery_method :smtp
|
|
||||||
# end.load!
|
|
||||||
#
|
|
||||||
# module Billing
|
|
||||||
# class Invoice
|
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# from 'noreply@example.com'
|
|
||||||
# to :recipient
|
|
||||||
# subject :subject_line
|
|
||||||
#
|
|
||||||
# def prepare
|
|
||||||
# mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def recipient
|
|
||||||
# user.email
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def subject_line
|
|
||||||
# "Invoice - #{ invoice.number }"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# invoice = Invoice.new
|
|
||||||
# user = User.new(name: 'L', email: 'user@example.com')
|
|
||||||
#
|
|
||||||
# Billing::Invoice.deliver(invoice: invoice, user: user) # Deliver both text, HTML parts and the attachment
|
|
||||||
# Billing::Invoice.deliver(invoice: invoice, user: user, format: :txt) # Deliver only the text part and the attachment
|
|
||||||
# Billing::Invoice.deliver(invoice: invoice, user: user, format: :html) # Deliver only the text part and the attachment
|
|
||||||
# Billing::Invoice.deliver(invoice: invoice, user: user, charset: 'iso-8859') # Deliver both the parts with "iso-8859"
|
|
||||||
def deliver(locals = {})
|
|
||||||
new(locals).deliver
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Initialize a mailer
|
# Initialize a mailer
|
||||||
#
|
#
|
||||||
# @param locals [Hash] a set of objects that acts as context for the rendering
|
# @param configuration [Hanami::Mailer::Configuration] the configuration
|
||||||
# @option :format [Symbol] specify format to deliver
|
# @return [Hanami::Mailer]
|
||||||
# @option :charset [String] charset
|
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
def initialize(locals = {})
|
def initialize(configuration:)
|
||||||
@locals = locals
|
@configuration = configuration
|
||||||
@format = locals.fetch(:format, nil)
|
freeze
|
||||||
@charset = locals.fetch(:charset, self.class.configuration.default_charset)
|
|
||||||
@mail = build
|
|
||||||
prepare
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Prepare the email message when a new mailer is initialized.
|
||||||
|
#
|
||||||
|
# @return [Mail::Message] the delivered email
|
||||||
|
#
|
||||||
|
# @since 0.1.0
|
||||||
|
# @api unstable
|
||||||
|
#
|
||||||
|
# @see Hanami::Mailer::Configuration#default_charset
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
# config.delivery_method = :smtp
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# configuration = Hanami::Mailer.finalize(configuration)
|
||||||
|
#
|
||||||
|
# module Billing
|
||||||
|
# class InvoiceMailer < Hanami::Mailer
|
||||||
|
# from 'noreply@example.com'
|
||||||
|
# to ->(locals) { locals.fetch(:user).email }
|
||||||
|
# subject ->(locals) { "Invoice number #{locals.fetch(:invoice).number}" }
|
||||||
|
#
|
||||||
|
# before do |mail, locals|
|
||||||
|
# mail.attachments["invoice-#{locals.fetch(:invoice).number}.pdf"] =
|
||||||
|
# File.read('/path/to/invoice.pdf')
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# invoice = Invoice.new(number: 23)
|
||||||
|
# user = User.new(name: 'L', email: 'user@example.com')
|
||||||
|
#
|
||||||
|
# mailer = Billing::InvoiceMailer.new(configuration: configuration)
|
||||||
|
#
|
||||||
|
# # Deliver both text, HTML parts and the attachment
|
||||||
|
# mailer.deliver(invoice: invoice, user: user)
|
||||||
|
#
|
||||||
|
# # Deliver only the text part and the attachment
|
||||||
|
# mailer.deliver(invoice: invoice, user: user, format: :txt)
|
||||||
|
#
|
||||||
|
# # Deliver only the text part and the attachment
|
||||||
|
# mailer.deliver(invoice: invoice, user: user, format: :html)
|
||||||
|
#
|
||||||
|
# # Deliver both the parts with "iso-8859"
|
||||||
|
# mailer.deliver(invoice: invoice, user: user, charset: 'iso-8859')
|
||||||
|
def deliver(locals)
|
||||||
|
mail(locals).deliver
|
||||||
|
rescue ArgumentError
|
||||||
|
raise MissingDeliveryDataError
|
||||||
|
end
|
||||||
|
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
alias call deliver
|
||||||
|
|
||||||
# Render a single template with the specified format.
|
# Render a single template with the specified format.
|
||||||
#
|
#
|
||||||
# @param format [Symbol] format
|
# @param format [Symbol] format
|
||||||
|
@ -212,127 +183,78 @@ module Hanami
|
||||||
# @return [String] the output of the rendering process.
|
# @return [String] the output of the rendering process.
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
# @api private
|
# @api unstable
|
||||||
def render(format)
|
def render(format, locals)
|
||||||
self.class.templates(format).render(self, @locals)
|
template(format).render(self, locals)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Delivers a multipart email, by looking at all the associated templates and render them.
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def deliver
|
|
||||||
mail.deliver
|
|
||||||
rescue ArgumentError => exception
|
|
||||||
raise MissingDeliveryDataError if exception.message =~ /SMTP (From|To) address/
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Prepare the email message when a new mailer is initialized.
|
|
||||||
#
|
|
||||||
# This is a hook that can be overwritten by mailers.
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# module Billing
|
|
||||||
# class Invoice
|
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# subject 'Invoice'
|
|
||||||
# from 'noreply@example.com'
|
|
||||||
# to ''
|
|
||||||
#
|
|
||||||
# def prepare
|
|
||||||
# mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def recipient
|
|
||||||
# user.email
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# invoice = Invoice.new
|
|
||||||
# user = User.new(name: 'L', email: 'user@example.com')
|
|
||||||
def prepare
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def method_missing(m)
|
|
||||||
@locals.fetch(m) { super }
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
attr_reader :mail
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
attr_reader :charset
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# rubocop:disable Metrics/MethodLength
|
# @api unstable
|
||||||
# rubocop:disable Metrics/AbcSize
|
# @since next
|
||||||
# @api private
|
attr_reader :configuration
|
||||||
def build
|
|
||||||
Mail.new.tap do |m|
|
|
||||||
m.from = __dsl(:from)
|
|
||||||
m.to = __dsl(:to)
|
|
||||||
m.cc = __dsl(:cc)
|
|
||||||
m.bcc = __dsl(:bcc)
|
|
||||||
m.subject = __dsl(:subject)
|
|
||||||
|
|
||||||
m.charset = charset
|
# @api unstable
|
||||||
m.html_part = __part(:html)
|
# @since next
|
||||||
m.text_part = __part(:txt)
|
def mail(locals)
|
||||||
|
Mail.new.tap do |mail|
|
||||||
m.delivery_method(*Hanami::Mailer.configuration.delivery_method)
|
instance_exec(mail, locals, &self.class.before)
|
||||||
|
bind(mail, locals)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
|
||||||
# rubocop:enable Metrics/AbcSize
|
|
||||||
|
|
||||||
# @api private
|
# @api unstable
|
||||||
|
# @since next
|
||||||
|
def bind(mail, locals) # rubocop:disable Metrics/AbcSize
|
||||||
|
charset = locals.fetch(:charset, configuration.default_charset)
|
||||||
|
|
||||||
|
mail.from = __dsl(:from, locals)
|
||||||
|
mail.to = __dsl(:to, locals)
|
||||||
|
mail.cc = __dsl(:cc, locals)
|
||||||
|
mail.bcc = __dsl(:bcc, locals)
|
||||||
|
mail.subject = __dsl(:subject, locals)
|
||||||
|
|
||||||
|
mail.html_part = __part(:html, charset, locals)
|
||||||
|
mail.text_part = __part(:txt, charset, locals)
|
||||||
|
|
||||||
|
mail.charset = charset
|
||||||
|
mail.delivery_method(*configuration.delivery_method)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def template(format)
|
||||||
|
configuration.template(self.class, format)
|
||||||
|
end
|
||||||
|
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
def __dsl(method_name)
|
# @api unstable
|
||||||
|
def __dsl(method_name, locals)
|
||||||
case result = self.class.__send__(method_name)
|
case result = self.class.__send__(method_name)
|
||||||
when Symbol
|
when Proc
|
||||||
__send__(result)
|
result.call(locals)
|
||||||
else
|
else
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
def __part(format)
|
# @api unstable
|
||||||
return unless __part?(format)
|
def __part(format, charset, locals)
|
||||||
|
return unless __part?(format, locals)
|
||||||
|
|
||||||
Mail::Part.new.tap do |part|
|
Mail::Part.new.tap do |part|
|
||||||
part.content_type = "#{CONTENT_TYPES.fetch(format)}; charset=#{charset}"
|
part.content_type = "#{CONTENT_TYPES.fetch(format)}; charset=#{charset}"
|
||||||
part.body = render(format)
|
part.body = render(format, locals)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
def __part?(format)
|
# @api unstable
|
||||||
@format == format ||
|
def __part?(format, locals)
|
||||||
(!@format && !self.class.templates(format).nil?)
|
wanted = locals.fetch(:format, nil)
|
||||||
end
|
wanted == format ||
|
||||||
|
(!wanted && !template(format).nil?)
|
||||||
# @api private
|
|
||||||
# @since 0.4.0
|
|
||||||
def respond_to_missing?(m, _include_all)
|
|
||||||
@locals.key?(m)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
require 'set'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'hanami/utils/kernel'
|
require 'hanami/utils/kernel'
|
||||||
|
require 'hanami/mailer/template_name'
|
||||||
|
require 'hanami/mailer/templates_finder'
|
||||||
|
|
||||||
module Hanami
|
module Hanami
|
||||||
module Mailer
|
class Mailer
|
||||||
# Framework configuration
|
# Framework configuration
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
|
@ -11,7 +14,7 @@ module Hanami
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
# @api private
|
# @api private
|
||||||
DEFAULT_ROOT = '.'.freeze
|
DEFAULT_ROOT = '.'
|
||||||
|
|
||||||
# Default delivery method
|
# Default delivery method
|
||||||
#
|
#
|
||||||
|
@ -23,24 +26,33 @@ module Hanami
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
# @api private
|
# @api private
|
||||||
DEFAULT_CHARSET = 'UTF-8'.freeze
|
DEFAULT_CHARSET = 'UTF-8'
|
||||||
|
|
||||||
# @since 0.1.0
|
private_constant(*constants(false))
|
||||||
# @api private
|
|
||||||
attr_reader :mailers
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
attr_reader :modules
|
|
||||||
|
|
||||||
# Initialize a configuration instance
|
# Initialize a configuration instance
|
||||||
#
|
#
|
||||||
|
# @yield [config] the new initialized configuration instance
|
||||||
# @return [Hanami::Mailer::Configuration] a new configuration's instance
|
# @return [Hanami::Mailer::Configuration] a new configuration's instance
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
|
#
|
||||||
|
# @example Basic Usage
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
# config.delivery_method :smtp, ...
|
||||||
|
# end
|
||||||
def initialize
|
def initialize
|
||||||
@namespace = Object
|
@mailers = {}
|
||||||
reset!
|
|
||||||
|
self.namespace = Object
|
||||||
|
self.root = DEFAULT_ROOT
|
||||||
|
self.delivery_method = DEFAULT_DELIVERY_METHOD
|
||||||
|
self.default_charset = DEFAULT_CHARSET
|
||||||
|
|
||||||
|
yield(self) if block_given?
|
||||||
|
@finder = TemplatesFinder.new(root)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the Ruby namespace where to lookup for mailers.
|
# Set the Ruby namespace where to lookup for mailers.
|
||||||
|
@ -49,91 +61,52 @@ module Hanami
|
||||||
# that if a `MyApp` wants a `Mailers::Signup` mailer, we are loading the
|
# that if a `MyApp` wants a `Mailers::Signup` mailer, we are loading the
|
||||||
# right one.
|
# right one.
|
||||||
#
|
#
|
||||||
# If not set, this value defaults to `Object`.
|
# @!attribute namespace
|
||||||
|
# @return [Class,Module,String] the Ruby namespace where the mailers
|
||||||
|
# are located
|
||||||
#
|
#
|
||||||
# This is part of a DSL, for this reason when this method is called with
|
# @since next
|
||||||
# an argument, it will set the corresponding instance variable. When
|
# @api unstable
|
||||||
# called without, it will return the already set value, or the default.
|
|
||||||
#
|
#
|
||||||
# @overload namespace(value)
|
# @example
|
||||||
# Sets the given value
|
|
||||||
# @param value [Class, Module, String] a valid Ruby namespace identifier
|
|
||||||
#
|
|
||||||
# @overload namespace
|
|
||||||
# Gets the value
|
|
||||||
# @return [Class, Module, String]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @example Getting the value
|
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configuration.namespace # => Object
|
# Hanami::Mailer::Configuration.new do |config|
|
||||||
#
|
# config.namespace = MyApp::Mailers
|
||||||
# @example Setting the value
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# Hanami::Mailer.configure do
|
|
||||||
# namespace 'MyApp::Mailers'
|
|
||||||
# end
|
# end
|
||||||
def namespace(value = nil)
|
attr_accessor :namespace
|
||||||
if value
|
|
||||||
@namespace = value
|
|
||||||
else
|
|
||||||
@namespace
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the root path where to search for templates
|
# Set the root path where to search for templates
|
||||||
#
|
#
|
||||||
# If not set, this value defaults to the current directory.
|
# If not set, this value defaults to the current directory.
|
||||||
#
|
#
|
||||||
# When this method is called with an argument, it will set the corresponding instance variable.
|
# @param value [String, Pathname] the root path for mailer templates
|
||||||
# When called without, it will return the already set value, or the default.
|
|
||||||
#
|
#
|
||||||
# @overload root(value)
|
# @raise [Errno::ENOENT] if the path doesn't exist
|
||||||
# Sets the given value
|
|
||||||
# @param value [String, Pathname, #to_pathname] an object that can be
|
|
||||||
# coerced to Pathname
|
|
||||||
#
|
#
|
||||||
# @overload root
|
# @since next
|
||||||
# Gets the value
|
# @api unstable
|
||||||
# @return [Pathname]
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
#
|
||||||
# @see http://www.ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html
|
# @see http://www.ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html
|
||||||
# @see http://rdoc.info/gems/hanami-utils/Hanami/Utils/Kernel#Pathname-class_method
|
# @see http://rdoc.info/gems/hanami-utils/Hanami/Utils/Kernel#Pathname-class_method
|
||||||
#
|
#
|
||||||
# @example Getting the value
|
# @example
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configuration.root # => #<Pathname:.>
|
# Hanami::Mailer::Configuration.new do |config|
|
||||||
#
|
# config.root = 'path/to/templates'
|
||||||
# @example Setting the value
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# Hanami::Mailer.configure do
|
|
||||||
# root '/path/to/templates'
|
|
||||||
# end
|
# end
|
||||||
#
|
def root=(value)
|
||||||
# Hanami::Mailer.configuration.root # => #<Pathname:/path/to/templates>
|
@root = Utils::Kernel.Pathname(value).realpath
|
||||||
def root(value = nil)
|
|
||||||
if value
|
|
||||||
@root = Utils::Kernel.Pathname(value).realpath
|
|
||||||
else
|
|
||||||
@root
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Prepare the mailers.
|
# @!attribute [r] root
|
||||||
#
|
# @return [Pathname] the root path for mailer templates
|
||||||
# The given block will be yielded when `Hanami::Mailer` will be included by
|
|
||||||
# a mailer.
|
|
||||||
#
|
|
||||||
# This method can be called multiple times.
|
|
||||||
#
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
attr_reader :root
|
||||||
|
|
||||||
# @param blk [Proc] the code block
|
# @param blk [Proc] the code block
|
||||||
#
|
#
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
@ -144,19 +117,8 @@ module Hanami
|
||||||
#
|
#
|
||||||
# @see Hanami::Mailer.configure
|
# @see Hanami::Mailer.configure
|
||||||
def prepare(&blk)
|
def prepare(&blk)
|
||||||
if block_given? # rubocop:disable Style/GuardClause
|
raise ArgumentError.new('Please provide a block') unless block_given?
|
||||||
@modules.push(blk)
|
@modules.push(blk)
|
||||||
else
|
|
||||||
raise ArgumentError.new('Please provide a block')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add a mailer to the registry
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def add_mailer(mailer)
|
|
||||||
@mailers.add(mailer)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Duplicate by copying the settings in a new instance.
|
# Duplicate by copying the settings in a new instance.
|
||||||
|
@ -211,40 +173,41 @@ module Hanami
|
||||||
#
|
#
|
||||||
# It supports the following delivery methods:
|
# It supports the following delivery methods:
|
||||||
#
|
#
|
||||||
# * Exim (<tt>:exim</tt>)
|
# * Exim (`:exim`)
|
||||||
# * Sendmail (<tt>:sendmail</tt>)
|
# * Sendmail (`:sendmail`)
|
||||||
# * SMTP (<tt>:smtp</tt>, for local installations)
|
# * SMTP (`:smtp`, for local installations)
|
||||||
# * SMTP Connection (<tt>:smtp_connection</tt>,
|
# * SMTP Connection (`:smtp_connection`,
|
||||||
# via <tt>Net::SMTP</tt> - for remote installations)
|
# via `Net::SMTP` - for remote installations)
|
||||||
# * Test (<tt>:test</tt>, for testing purposes)
|
# * Test (`:test`, for testing purposes)
|
||||||
#
|
#
|
||||||
# The default delivery method is SMTP (<tt>:smtp</tt>).
|
# The default delivery method is SMTP (`:smtp`).
|
||||||
#
|
#
|
||||||
# Custom delivery methods can be specified by passing the class policy and
|
# Custom delivery methods can be specified by passing the class policy and
|
||||||
# a set of optional configurations. This class MUST respond to:
|
# a set of optional configurations. This class MUST respond to:
|
||||||
#
|
#
|
||||||
# * <tt>initialize(options = {})</tt>
|
# * `initialize(options = {})`
|
||||||
# * <tt>deliver!(mail)<tt>
|
# * `deliver!(mail)`
|
||||||
#
|
#
|
||||||
# @param method [Symbol, #initialize, deliver!] delivery method
|
# @param method [Symbol, #initialize, deliver!] delivery method
|
||||||
# @param options [Hash] optional settings
|
# @param options [Hash] optional settings
|
||||||
#
|
#
|
||||||
# @return [Array] an array containing the delivery method and the optional settings as an Hash
|
# @return [Array] an array containing the delivery method and the optional settings as an Hash
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since next
|
||||||
|
# @api unstable
|
||||||
#
|
#
|
||||||
# @example Setup delivery method with supported symbol
|
# @example Setup delivery method with supported symbol
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configure do
|
# Hanami::Mailer::Configuration.new do |config|
|
||||||
# delivery_method :sendmail
|
# config.delivery_method = :sendmail
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Setup delivery method with supported symbol and options
|
# @example Setup delivery method with supported symbol and options
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configure do
|
# Hanami::Mailer::Configuration.new do |config|
|
||||||
# delivery_method :smtp, address: "localhost", port: 1025
|
# config.delivery_method = :smtp, address: "localhost", port: 1025
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Setup custom delivery method with options
|
# @example Setup custom delivery method with options
|
||||||
|
@ -260,49 +223,77 @@ module Hanami
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Hanami::Mailer.configure do
|
# Hanami::Mailer.Configuration.new do |config|
|
||||||
# delivery_method MandrillDeliveryMethod,
|
# config.delivery_method = MandrillDeliveryMethod,
|
||||||
# username: ENV['MANDRILL_USERNAME'],
|
# username: ENV['MANDRILL_USERNAME'],
|
||||||
# password: ENV['MANDRILL_API_KEY']
|
# password: ENV['MANDRILL_API_KEY']
|
||||||
# end
|
# end
|
||||||
def delivery_method(method = nil, options = {})
|
attr_accessor :delivery_method
|
||||||
if method.nil?
|
|
||||||
@delivery_method
|
# Specify a default charset for all the delivered emails
|
||||||
else
|
#
|
||||||
@delivery_method = [method, options]
|
# If not set, it defaults to `UTF-8`
|
||||||
end
|
#
|
||||||
|
# @!attribute default_charset
|
||||||
|
# @return [String] the charset
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# Hanami::Mailer::Configuration.new do |config|
|
||||||
|
# config.default_charset = "iso-8859-1"
|
||||||
|
# end
|
||||||
|
attr_accessor :default_charset
|
||||||
|
|
||||||
|
# Add a mailer to the registry
|
||||||
|
#
|
||||||
|
# @param mailer [Hanami::Mailer] a mailer
|
||||||
|
#
|
||||||
|
# @since 0.1.0
|
||||||
|
# @api unstable
|
||||||
|
def add_mailer(mailer)
|
||||||
|
template_name = TemplateName[mailer.template_name, namespace]
|
||||||
|
templates = finder.find(template_name)
|
||||||
|
|
||||||
|
mailers[mailer] = templates
|
||||||
end
|
end
|
||||||
|
|
||||||
# @since 0.1.0
|
# @param mailer [Hanami::Mailer] a mailer
|
||||||
def default_charset(value = nil)
|
# @param format [Symbol] the wanted format (eg. `:html`, `:txt`)
|
||||||
if value.nil?
|
#
|
||||||
@default_charset
|
# @raise [Hanami::Mailer::UnknownMailerError] if the given mailer is not
|
||||||
else
|
# present in the configuration. This happens when the configuration is
|
||||||
@default_charset = value
|
# used before to being finalized.
|
||||||
end
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def template(mailer, format)
|
||||||
|
mailers.fetch(mailer) { raise UnknownMailerError.new(mailer) }[format]
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
# Deep freeze the important instance variables
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def freeze
|
||||||
|
delivery_method.freeze
|
||||||
|
default_charset.freeze
|
||||||
|
mailers.freeze
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
# @api private
|
private
|
||||||
# @since 0.1.0
|
|
||||||
attr_writer :root
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
attr_writer :delivery_method
|
# @api private
|
||||||
|
attr_reader :mailers
|
||||||
|
|
||||||
# @api private
|
# @since next
|
||||||
# @since 0.1.0
|
# @api unstable
|
||||||
attr_writer :default_charset
|
attr_reader :finder
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
attr_writer :namespace
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
attr_writer :modules
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,89 +1,31 @@
|
||||||
require 'hanami/mailer/rendering/template_name'
|
# frozen_string_literal: true
|
||||||
require 'hanami/mailer/rendering/templates_finder'
|
|
||||||
|
|
||||||
module Hanami
|
module Hanami
|
||||||
module Mailer
|
# Hanami::Mailer
|
||||||
|
#
|
||||||
|
# @since 0.1.0
|
||||||
|
class Mailer
|
||||||
|
require 'hanami/mailer/template_name'
|
||||||
|
|
||||||
# Class level DSL
|
# Class level DSL
|
||||||
#
|
#
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
module Dsl
|
module Dsl
|
||||||
# @since 0.3.0
|
# @since 0.3.0
|
||||||
# @api private
|
# @api unstable
|
||||||
def self.extended(base)
|
def self.extended(base)
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
@from = nil
|
@from = nil
|
||||||
@to = nil
|
@to = nil
|
||||||
@cc = nil
|
@cc = nil
|
||||||
@bcc = nil
|
@bcc = nil
|
||||||
@subject = nil
|
@subject = nil
|
||||||
|
@template = nil
|
||||||
|
@before = ->(*) {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the template name IF it differs from the convention.
|
private_class_method :extended
|
||||||
#
|
|
||||||
# For a given mailer named <tt>Signup::Welcome</tt> it will look for
|
|
||||||
# <tt>signup/welcome.*.*</tt> templates under the root directory.
|
|
||||||
#
|
|
||||||
# If for some reason, we need to specify a different template name, we can
|
|
||||||
# use this method.
|
|
||||||
#
|
|
||||||
# This is part of a DSL, for this reason when this method is called with
|
|
||||||
# an argument, it will set the corresponding class variable. When
|
|
||||||
# called without, it will return the already set value, or the default.
|
|
||||||
#
|
|
||||||
# @overload template(value)
|
|
||||||
# Sets the given value
|
|
||||||
# @param value [String, #to_s] relative template path, under root
|
|
||||||
# @return [NilClass]
|
|
||||||
#
|
|
||||||
# @overload template
|
|
||||||
# Gets the template name
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @see Hanami::Mailers::Configuration.root
|
|
||||||
#
|
|
||||||
# @example Custom template name
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# class MyMailer
|
|
||||||
# include Hanami::Mailer
|
|
||||||
# template 'mailer'
|
|
||||||
# end
|
|
||||||
def template(value = nil)
|
|
||||||
if value.nil?
|
|
||||||
@template ||= ::Hanami::Mailer::Rendering::TemplateName.new(name, configuration.namespace).to_s
|
|
||||||
else
|
|
||||||
@template = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a set of associated templates or only one for the given format
|
|
||||||
#
|
|
||||||
# This is part of a DSL, for this reason when this method is called with
|
|
||||||
# an argument, it will set the corresponding class variable. When
|
|
||||||
# called without, it will return the already set value, or the default.
|
|
||||||
#
|
|
||||||
# @overload templates(format)
|
|
||||||
# Returns the template associated with the given format
|
|
||||||
# @param value [Symbol] the format
|
|
||||||
# @return [Hash]
|
|
||||||
#
|
|
||||||
# @overload templates
|
|
||||||
# Returns all the associated templates
|
|
||||||
# Gets the template name
|
|
||||||
# @return [Hash] a set of templates
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def templates(format = nil)
|
|
||||||
if format.nil?
|
|
||||||
@templates = ::Hanami::Mailer::Rendering::TemplatesFinder.new(self).find
|
|
||||||
else
|
|
||||||
@templates.fetch(format, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the sender for mail messages
|
# Sets the sender for mail messages
|
||||||
#
|
#
|
||||||
|
@ -114,24 +56,15 @@ module Hanami
|
||||||
# @example Hardcoded value (String)
|
# @example Hardcoded value (String)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# from "noreply@example.com"
|
# from "noreply@example.com"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Method (Symbol)
|
# @example Lazy (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# from ->(locals) { locals.fetch(:sender).email }
|
||||||
# from :sender
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def sender
|
|
||||||
# "noreply@example.com"
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
def from(value = nil)
|
def from(value = nil)
|
||||||
if value.nil?
|
if value.nil?
|
||||||
|
@ -141,6 +74,74 @@ module Hanami
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Sets the recipient for mail messages
|
||||||
|
#
|
||||||
|
# It accepts a hardcoded value as a string or array of strings.
|
||||||
|
# For dynamic values, you can specify a symbol that represents an instance
|
||||||
|
# method.
|
||||||
|
#
|
||||||
|
# This value MUST be set, otherwise an exception is raised at the delivery
|
||||||
|
# time.
|
||||||
|
#
|
||||||
|
# When a value is given, specify the recipient of the email
|
||||||
|
# Otherwise, it returns the recipient of the email
|
||||||
|
#
|
||||||
|
# This is part of a DSL, for this reason when this method is called with
|
||||||
|
# an argument, it will set the corresponding class variable. When
|
||||||
|
# called without, it will return the already set value, or the default.
|
||||||
|
#
|
||||||
|
# @overload to(value)
|
||||||
|
# Sets the recipient
|
||||||
|
# @param value [String, Array, Symbol] the hardcoded value or method name
|
||||||
|
# @return [NilClass]
|
||||||
|
#
|
||||||
|
# @overload to
|
||||||
|
# Returns the recipient
|
||||||
|
# @return [String, Array, Symbol] the recipient
|
||||||
|
#
|
||||||
|
# @since 0.1.0
|
||||||
|
#
|
||||||
|
# @example Hardcoded value (String)
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
|
# to "user@example.com"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @example Hardcoded value (Array)
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
|
# to ["user-1@example.com", "user-2@example.com"]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @example Lazy value (Proc)
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
|
# to ->(locals) { locals.fetch(:user).email }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# user = User.new(name: 'L')
|
||||||
|
# WelcomeMailer.new(configuration: configuration).deliver(user: user)
|
||||||
|
#
|
||||||
|
# @example Lazy values (Proc)
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
|
# to ->(locals) { locals.fetch(:users).map(&:email) }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# users = [User.new(name: 'L'), User.new(name: 'MG')]
|
||||||
|
# WelcomeMailer.new(configuration: configuration).deliver(users: users)
|
||||||
|
def to(value = nil)
|
||||||
|
if value.nil?
|
||||||
|
@to
|
||||||
|
else
|
||||||
|
@to = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Sets the cc (carbon copy) for mail messages
|
# Sets the cc (carbon copy) for mail messages
|
||||||
#
|
#
|
||||||
# It accepts a hardcoded value as a string or array of strings.
|
# It accepts a hardcoded value as a string or array of strings.
|
||||||
|
@ -170,58 +171,36 @@ module Hanami
|
||||||
# @example Hardcoded value (String)
|
# @example Hardcoded value (String)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to "user@example.com"
|
|
||||||
# cc "other.user@example.com"
|
# cc "other.user@example.com"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Hardcoded value (Array)
|
# @example Hardcoded value (Array)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to ["user-1@example.com", "user-2@example.com"]
|
|
||||||
# cc ["other.user-1@example.com", "other.user-2@example.com"]
|
# cc ["other.user-1@example.com", "other.user-2@example.com"]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Method (Symbol)
|
# @example Lazy value (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# cc ->(locals) { locals.fetch(:user).email }
|
||||||
# to "user@example.com"
|
|
||||||
# cc :email_address
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def email_address
|
|
||||||
# user.email
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# other_user = User.new(name: 'L')
|
# user = User.new(name: 'L')
|
||||||
# WelcomeMailer.deliver(user: other_user)
|
# WelcomeMailer.new(configuration: configuration).deliver(user: user)
|
||||||
#
|
#
|
||||||
# @example Method that returns a collection of recipients
|
# @example Lazy values (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# cc ->(locals) { locals.fetch(:users).map(&:email) }
|
||||||
# to "user@example.com"
|
|
||||||
# cc :recipients
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def recipients
|
|
||||||
# users.map(&:email)
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# other_users = [User.new(name: 'L'), User.new(name: 'MG')]
|
# users = [User.new(name: 'L'), User.new(name: 'MG')]
|
||||||
# WelcomeMailer.deliver(users: other_users)
|
# WelcomeMailer.new(configuration: configuration).deliver(users: users)
|
||||||
def cc(value = nil)
|
def cc(value = nil)
|
||||||
if value.nil?
|
if value.nil?
|
||||||
@cc
|
@cc
|
||||||
|
@ -259,58 +238,36 @@ module Hanami
|
||||||
# @example Hardcoded value (String)
|
# @example Hardcoded value (String)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to "user@example.com"
|
|
||||||
# bcc "other.user@example.com"
|
# bcc "other.user@example.com"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Hardcoded value (Array)
|
# @example Hardcoded value (Array)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to ["user-1@example.com", "user-2@example.com"]
|
|
||||||
# bcc ["other.user-1@example.com", "other.user-2@example.com"]
|
# bcc ["other.user-1@example.com", "other.user-2@example.com"]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Method (Symbol)
|
# @example Lazy value (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# bcc ->(locals) { locals.fetch(:user).email }
|
||||||
# to "user@example.com"
|
|
||||||
# bcc :email_address
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def email_address
|
|
||||||
# user.email
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# other_user = User.new(name: 'L')
|
# user = User.new(name: 'L')
|
||||||
# WelcomeMailer.deliver(user: other_user)
|
# WelcomeMailer.new(configuration: configuration).deliver(user: user)
|
||||||
#
|
#
|
||||||
# @example Method that returns a collection of recipients
|
# @example Lazy values (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# bcc ->(locals) { locals.fetch(:users).map(&:email) }
|
||||||
# to "user@example.com"
|
|
||||||
# bcc :recipients
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def recipients
|
|
||||||
# users.map(&:email)
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# other_users = [User.new(name: 'L'), User.new(name: 'MG')]
|
# users = [User.new(name: 'L'), User.new(name: 'MG')]
|
||||||
# WelcomeMailer.deliver(users: other_users)
|
# WelcomeMailer.new(configuration: configuration).deliver(users: users)
|
||||||
def bcc(value = nil)
|
def bcc(value = nil)
|
||||||
if value.nil?
|
if value.nil?
|
||||||
@bcc
|
@bcc
|
||||||
|
@ -319,92 +276,6 @@ module Hanami
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the recipient for mail messages
|
|
||||||
#
|
|
||||||
# It accepts a hardcoded value as a string or array of strings.
|
|
||||||
# For dynamic values, you can specify a symbol that represents an instance
|
|
||||||
# method.
|
|
||||||
#
|
|
||||||
# This value MUST be set, otherwise an exception is raised at the delivery
|
|
||||||
# time.
|
|
||||||
#
|
|
||||||
# When a value is given, specify the recipient of the email
|
|
||||||
# Otherwise, it returns the recipient of the email
|
|
||||||
#
|
|
||||||
# This is part of a DSL, for this reason when this method is called with
|
|
||||||
# an argument, it will set the corresponding class variable. When
|
|
||||||
# called without, it will return the already set value, or the default.
|
|
||||||
#
|
|
||||||
# @overload to(value)
|
|
||||||
# Sets the recipient
|
|
||||||
# @param value [String, Array, Symbol] the hardcoded value or method name
|
|
||||||
# @return [NilClass]
|
|
||||||
#
|
|
||||||
# @overload to
|
|
||||||
# Returns the recipient
|
|
||||||
# @return [String, Array, Symbol] the recipient
|
|
||||||
#
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @example Hardcoded value (String)
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# class WelcomeMailer
|
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to "user@example.com"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# @example Hardcoded value (Array)
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# class WelcomeMailer
|
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# to ["user-1@example.com", "user-2@example.com"]
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# @example Method (Symbol)
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# class WelcomeMailer
|
|
||||||
# include Hanami::Mailer
|
|
||||||
# to :email_address
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def email_address
|
|
||||||
# user.email
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# user = User.new(name: 'L')
|
|
||||||
# WelcomeMailer.deliver(user: user)
|
|
||||||
#
|
|
||||||
# @example Method that returns a collection of recipients
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# class WelcomeMailer
|
|
||||||
# include Hanami::Mailer
|
|
||||||
# to :recipients
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def recipients
|
|
||||||
# users.map(&:email)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# users = [User.new(name: 'L'), User.new(name: 'MG')]
|
|
||||||
# WelcomeMailer.deliver(users: users)
|
|
||||||
def to(value = nil)
|
|
||||||
if value.nil?
|
|
||||||
@to
|
|
||||||
else
|
|
||||||
@to = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the subject for mail messages
|
# Sets the subject for mail messages
|
||||||
#
|
#
|
||||||
# It accepts a hardcoded value as a string, or a symbol that represents
|
# It accepts a hardcoded value as a string, or a symbol that represents
|
||||||
|
@ -431,28 +302,19 @@ module Hanami
|
||||||
# @example Hardcoded value (String)
|
# @example Hardcoded value (String)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
|
||||||
#
|
|
||||||
# subject "Welcome"
|
# subject "Welcome"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# @example Method (Symbol)
|
# @example Lazy value (Proc)
|
||||||
# require 'hanami/mailer'
|
# require 'hanami/mailer'
|
||||||
#
|
#
|
||||||
# class WelcomeMailer
|
# class WelcomeMailer < Hanami::Mailer
|
||||||
# include Hanami::Mailer
|
# subject ->(locals) { "Hello #{locals.fetch(:user).name}" }
|
||||||
# subject :greeting
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def greeting
|
|
||||||
# "Hello, #{ user.name }"
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# user = User.new(name: 'L')
|
# user = User.new(name: 'L')
|
||||||
# WelcomeMailer.deliver(user: user)
|
# WelcomeMailer.new(configuration: configuration).deliver(user: user)
|
||||||
def subject(value = nil)
|
def subject(value = nil)
|
||||||
if value.nil?
|
if value.nil?
|
||||||
@subject
|
@subject
|
||||||
|
@ -461,17 +323,70 @@ module Hanami
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
# Set the template name **IF** it differs from the naming convention.
|
||||||
|
#
|
||||||
# Loading mechanism hook.
|
# For a given mailer named `Signup::Welcome` it will look for
|
||||||
|
# `signup/welcome.*.*` templates under the root directory.
|
||||||
|
#
|
||||||
|
# If for some reason, we need to specify a different template name, we can
|
||||||
|
# use this method.
|
||||||
|
#
|
||||||
|
# @param value [String] the template name
|
||||||
#
|
#
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
|
# @api unstable
|
||||||
#
|
#
|
||||||
# @see Hanami::Mailer.load!
|
# @example Custom template name
|
||||||
def load!
|
# require 'hanami/mailer'
|
||||||
templates.freeze
|
#
|
||||||
configuration.freeze
|
# class MyMailer < Hanami::Mailer
|
||||||
|
# template 'mailer'
|
||||||
|
# end
|
||||||
|
def template(value)
|
||||||
|
@template = value
|
||||||
|
end
|
||||||
|
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def template_name
|
||||||
|
@template || name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Before callback for email delivery
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# module Billing
|
||||||
|
# class InvoiceMailer < Hanami::Mailer
|
||||||
|
# subject 'Invoice'
|
||||||
|
# from 'noreply@example.com'
|
||||||
|
# to ->(locals) { locals.fetch(:user).email }
|
||||||
|
#
|
||||||
|
# before do |mail, locals|
|
||||||
|
# user = locals.fetch(:user)
|
||||||
|
# mail.attachments["invoice-#{invoice_code}-#{user.id}.pdf"] = File.read('/path/to/invoice.pdf')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def invoice_code
|
||||||
|
# "123"
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# invoice = Invoice.new
|
||||||
|
# user = User.new(name: 'L', email: 'user@example.com')
|
||||||
|
#
|
||||||
|
# InvoiceMailer.new(configuration: configuration).deliver(invoice: invoice, user: user)
|
||||||
|
def before(&blk)
|
||||||
|
if block_given?
|
||||||
|
@before = blk
|
||||||
|
else
|
||||||
|
@before
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'mail'
|
||||||
|
# require 'ice_nine'
|
||||||
|
|
||||||
|
module Hanami
|
||||||
|
class Mailer
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
class Finalizer
|
||||||
|
# Finalize the given configuration before to start to use the mailers
|
||||||
|
#
|
||||||
|
# @param mailers [Array<Hanami::Mailer>] all the subclasses of
|
||||||
|
# `Hanami::Mailer`
|
||||||
|
# @param configuration [Hanami::Mailer::Configuration] the configuration
|
||||||
|
# to finalize
|
||||||
|
#
|
||||||
|
# @return configuration [Hanami::Mailer::Configuration] the finalized
|
||||||
|
# configuration
|
||||||
|
#
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def self.finalize(mailers, configuration)
|
||||||
|
Mail.eager_autoload!
|
||||||
|
mailers.each { |mailer| configuration.add_mailer(mailer) }
|
||||||
|
|
||||||
|
configuration.freeze
|
||||||
|
configuration
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,53 +0,0 @@
|
||||||
require 'hanami/utils/string'
|
|
||||||
|
|
||||||
module Hanami
|
|
||||||
module Mailer
|
|
||||||
module Rendering
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
# TODO this is identical to Hanami::View, consider to move into Hanami::Utils
|
|
||||||
class TemplateName
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
NAMESPACE_SEPARATOR = '::'.freeze
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def initialize(name, namespace)
|
|
||||||
@name = name
|
|
||||||
compile!(namespace)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def to_s
|
|
||||||
@name
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def compile!(namespace)
|
|
||||||
tokens(namespace) { |token| replace!(token) }
|
|
||||||
@name = Utils::String.underscore(@name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def tokens(namespace)
|
|
||||||
namespace.to_s.split(NAMESPACE_SEPARATOR).each do |token|
|
|
||||||
yield token
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.1.0
|
|
||||||
# @api private
|
|
||||||
def replace!(token)
|
|
||||||
@name.gsub!(/\A#{token}#{NAMESPACE_SEPARATOR}/, '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,133 +0,0 @@
|
||||||
require 'hanami/mailer/template'
|
|
||||||
|
|
||||||
module Hanami
|
|
||||||
module Mailer
|
|
||||||
module Rendering
|
|
||||||
# Find templates for a mailer
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @see Mailer::Template
|
|
||||||
class TemplatesFinder
|
|
||||||
# Default format
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
FORMAT = '*'.freeze
|
|
||||||
|
|
||||||
# Default template engines
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
ENGINES = '*'.freeze
|
|
||||||
|
|
||||||
# Recursive pattern
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
RECURSIVE = '**'.freeze
|
|
||||||
|
|
||||||
# Initialize a finder
|
|
||||||
#
|
|
||||||
# @param mailer [Class] the mailer class
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def initialize(mailer)
|
|
||||||
@mailer = mailer
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find all the associated templates to the mailer.
|
|
||||||
# It recursively looks for templates under the root path of the mailer,
|
|
||||||
# that are matching the template name
|
|
||||||
#
|
|
||||||
# @return [Hash] the templates
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
#
|
|
||||||
# @see Hanami::Mailer::Dsl#root
|
|
||||||
# @see Hanami::Mailer::Dsl#templates
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# require 'hanami/mailer'
|
|
||||||
#
|
|
||||||
# module Mailers
|
|
||||||
# class Welcome
|
|
||||||
# include Hanami::Mailer
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Mailers::Welcome.root # => "/path/to/templates"
|
|
||||||
# Mailers::Welcome.templates # => {[:html] => "welcome"}
|
|
||||||
#
|
|
||||||
# # This mailer has a template:
|
|
||||||
# #
|
|
||||||
# # "/path/to/templates/welcome.html.erb"
|
|
||||||
#
|
|
||||||
# Hanami::Mailer::Rendering::TemplatesFinder.new(Mailers::Welcome).find
|
|
||||||
# # => [#<Hanami::Mailer::Template:0x007f8a0a86a970 ... @file="/path/to/templates/welcome.html.erb">]
|
|
||||||
def find
|
|
||||||
templates = Hash[]
|
|
||||||
_find.map do |template|
|
|
||||||
name = File.basename(template)
|
|
||||||
format = (name.split('.')[-2]).to_sym
|
|
||||||
templates[format] = Mailer::Template.new(template)
|
|
||||||
end
|
|
||||||
templates
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def _find(lookup = search_path)
|
|
||||||
Dir.glob("#{[root, lookup, template_name].join(separator)}.#{format}.#{engines}")
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def template_name
|
|
||||||
Rendering::TemplateName.new(@mailer.template, @mailer.configuration.namespace).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def root
|
|
||||||
@mailer.configuration.root
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def search_path
|
|
||||||
recursive
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def recursive
|
|
||||||
RECURSIVE
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def separator
|
|
||||||
::File::SEPARATOR
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def format
|
|
||||||
FORMAT
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def engines
|
|
||||||
ENGINES
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'tilt'
|
require 'tilt'
|
||||||
|
|
||||||
module Hanami
|
module Hanami
|
||||||
module Mailer
|
class Mailer
|
||||||
# A logic-less template.
|
# A logic-less template.
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
|
@ -11,29 +13,20 @@ module Hanami
|
||||||
class Template
|
class Template
|
||||||
def initialize(template)
|
def initialize(template)
|
||||||
@_template = Tilt.new(template)
|
@_template = Tilt.new(template)
|
||||||
|
freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render the template within the context of the given scope.
|
# Render the template within the context of the given scope.
|
||||||
#
|
#
|
||||||
# @param scope [Class] the rendering scope
|
# @param scope [Object] the rendering scope
|
||||||
# @param locals [Hash] set of objects passed to the constructor
|
# @param locals [Hash] set of objects passed to the constructor
|
||||||
#
|
#
|
||||||
# @return [String] the output of the rendering process
|
# @return [String] the output of the rendering process
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
def render(scope = Object.new, locals = {})
|
def render(scope, locals = {})
|
||||||
@_template.render(scope, locals)
|
@_template.render(scope.dup, locals)
|
||||||
end
|
|
||||||
|
|
||||||
# Get the path to the template
|
|
||||||
#
|
|
||||||
# @return [String] the pathname
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
# @since 0.1.0
|
|
||||||
def file
|
|
||||||
@_template.file
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'hanami/utils/string'
|
||||||
|
|
||||||
|
module Hanami
|
||||||
|
class Mailer
|
||||||
|
# @since 0.1.0
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
# TODO this is identical to Hanami::View, consider to move into Hanami::Utils
|
||||||
|
class TemplateName
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
def self.call(name, namespace)
|
||||||
|
Utils::String.new(name.gsub(/\A#{namespace}(::)*/, '')).underscore.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
# @since next
|
||||||
|
# @api unstable
|
||||||
|
alias [] call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,152 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'hanami/mailer/template'
|
||||||
|
require 'hanami/utils/file_list'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
module Hanami
|
||||||
|
class Mailer
|
||||||
|
# Find templates for a mailer
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
#
|
||||||
|
# @see Mailer::Template
|
||||||
|
class TemplatesFinder
|
||||||
|
# Default format
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
FORMAT = '*'
|
||||||
|
|
||||||
|
# Default template engines
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
ENGINES = '*'
|
||||||
|
|
||||||
|
# Recursive pattern
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
RECURSIVE = '**'
|
||||||
|
|
||||||
|
# Format separator
|
||||||
|
#
|
||||||
|
# @api unstable
|
||||||
|
# @since next
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# welcome.html.erb
|
||||||
|
FORMAT_SEPARATOR = '.'
|
||||||
|
|
||||||
|
private_constant(*constants(true))
|
||||||
|
|
||||||
|
# Initialize a finder
|
||||||
|
#
|
||||||
|
# @param root [String,Pathname] the root directory where to recursively
|
||||||
|
# look for templates
|
||||||
|
#
|
||||||
|
# @raise [Errno::ENOENT] if the directory doesn't exist
|
||||||
|
#
|
||||||
|
# @api unstable
|
||||||
|
# @since 0.1.0
|
||||||
|
def initialize(root)
|
||||||
|
@root = Pathname.new(root).realpath
|
||||||
|
freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
# Find all the associated templates to the mailer.
|
||||||
|
#
|
||||||
|
# It starts under the root path and it **recursively** looks for templates
|
||||||
|
# that are matching the given template name.
|
||||||
|
#
|
||||||
|
# @param template_name [String] the template name
|
||||||
|
#
|
||||||
|
# @return [Hash] the templates
|
||||||
|
#
|
||||||
|
# @api unstable
|
||||||
|
# @since 0.1.0
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# require 'hanami/mailer'
|
||||||
|
#
|
||||||
|
# module Mailers
|
||||||
|
# class Welcome < Hanami::Mailer
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
# config.root = "path/to/templates"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # This mailer has a template:
|
||||||
|
# #
|
||||||
|
# # "path/to/templates/welcome.html.erb"
|
||||||
|
#
|
||||||
|
# Hanami::Mailer::Rendering::TemplatesFinder.new(root).find("welcome")
|
||||||
|
# # => [#<Hanami::Mailer::Template:0x007f8a0a86a970 ... @file="path/to/templates/welcome.html.erb">]
|
||||||
|
def find(template_name)
|
||||||
|
templates(template_name).each_with_object({}) do |template, result|
|
||||||
|
format = extract_format(template)
|
||||||
|
result[format] = Mailer::Template.new(template)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# @api unstable
|
||||||
|
# @since 0.1.0
|
||||||
|
def templates(template_name, lookup = search_path)
|
||||||
|
Utils::FileList["#{[root, lookup, template_name].join(separator)}#{format_separator}#{format}#{format_separator}#{engines}"]
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api unstable
|
||||||
|
# @since 0.1.0
|
||||||
|
attr_reader :root
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
def search_path
|
||||||
|
recursive
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
def recursive
|
||||||
|
RECURSIVE
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
def separator
|
||||||
|
::File::SEPARATOR
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
def format
|
||||||
|
FORMAT
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
# @since 0.1.0
|
||||||
|
def engines
|
||||||
|
ENGINES
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api unstable
|
||||||
|
# @since next
|
||||||
|
def format_separator
|
||||||
|
FORMAT_SEPARATOR
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api unstable
|
||||||
|
# @since next
|
||||||
|
def extract_format(template)
|
||||||
|
filename = File.basename(template)
|
||||||
|
filename.split(format_separator)[-2].to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Hanami
|
module Hanami
|
||||||
module Mailer
|
class Mailer
|
||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
VERSION = '1.1.0'.freeze
|
VERSION = '1.1.0'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer do
|
||||||
|
describe '.deliver' do
|
||||||
|
it 'can deliver with specified charset' do
|
||||||
|
mail = CharsetMailer.new(configuration: configuration).deliver(charset: charset = 'iso-2022-jp')
|
||||||
|
|
||||||
|
expect(mail.charset).to eq(charset)
|
||||||
|
expect(mail.parts.first.charset).to eq(charset)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error when 'from' isn't specified" do
|
||||||
|
expect { MissingFromMailer.new(configuration: configuration).deliver({}) }.to raise_error(Hanami::Mailer::MissingDeliveryDataError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error when 'to' isn't specified" do
|
||||||
|
expect { MissingToMailer.new(configuration: configuration).deliver({}) }.to raise_error(Hanami::Mailer::MissingDeliveryDataError)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'test delivery with hardcoded values' do
|
||||||
|
subject { WelcomeMailer.new(configuration: configuration).deliver({}) }
|
||||||
|
|
||||||
|
it 'sends the correct information' do
|
||||||
|
expect(subject.from).to eq(['noreply@sender.com'])
|
||||||
|
expect(subject.to).to eq(['noreply@recipient.com', 'owner@recipient.com'])
|
||||||
|
expect(subject.cc).to eq(['cc@recipient.com'])
|
||||||
|
expect(subject.bcc).to eq(['bcc@recipient.com'])
|
||||||
|
expect(subject.subject).to eq('Welcome')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has the correct templates' do
|
||||||
|
expect(subject.html_part.to_s).to include(%(template))
|
||||||
|
expect(subject.text_part.to_s).to include(%(template))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'interprets the prepare statement' do
|
||||||
|
attachment = subject.attachments['invoice.pdf']
|
||||||
|
|
||||||
|
expect(attachment).to be_kind_of(Mail::Part)
|
||||||
|
|
||||||
|
expect(attachment).to be_attachment
|
||||||
|
expect(attachment).to_not be_inline
|
||||||
|
expect(attachment).to_not be_multipart
|
||||||
|
|
||||||
|
expect(attachment.filename).to eq('invoice.pdf')
|
||||||
|
|
||||||
|
expect(attachment.content_type).to match('application/pdf')
|
||||||
|
expect(attachment.content_type).to match('filename=invoice.pdf')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'test delivery with procs' do
|
||||||
|
subject { ProcMailer.new(configuration: configuration).deliver(user: user) }
|
||||||
|
let(:user) { User.new('Name', 'student@deigirls.com') }
|
||||||
|
|
||||||
|
it 'sends the correct information' do
|
||||||
|
expect(subject.from).to eq(["hello-#{user.name.downcase}@example.com"])
|
||||||
|
expect(subject.to).to eq([user.email])
|
||||||
|
expect(subject.subject).to eq("[Hanami] Hello, #{user.name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'test delivery with locals' do
|
||||||
|
subject { EventMailer.new(configuration: configuration) }
|
||||||
|
let(:count) { 100 }
|
||||||
|
|
||||||
|
it 'delivers the message' do
|
||||||
|
threads = []
|
||||||
|
mails = {}
|
||||||
|
|
||||||
|
count.times do |i|
|
||||||
|
threads << Thread.new do
|
||||||
|
user = OpenStruct.new(name: "Luca #{i}", email: "luca-#{i}@domain.test")
|
||||||
|
event = OpenStruct.new(id: i, title: "Event ##{i}")
|
||||||
|
|
||||||
|
mails[i] = subject.deliver(user: user, event: event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
threads.map(&:join)
|
||||||
|
|
||||||
|
expect(mails.count).to eq(count)
|
||||||
|
mails.each do |i, mail|
|
||||||
|
expect(mail.to).to eq(["luca-#{i}@domain.test"])
|
||||||
|
expect(mail.subject).to eq("Invitation: Event ##{i}")
|
||||||
|
expect(mail.attachments[0].filename).to eq("invitation-#{i}.ics")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'multipart' do
|
||||||
|
it 'delivers all the parts by default' do
|
||||||
|
mail = WelcomeMailer.new(configuration: configuration).deliver({})
|
||||||
|
body = mail.body.encoded
|
||||||
|
|
||||||
|
expect(body).to include(%(<h1>Hello World!</h1>))
|
||||||
|
expect(body).to include(%(This is a txt template))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can deliver only the text part' do
|
||||||
|
mail = WelcomeMailer.new(configuration: configuration).deliver(format: :txt)
|
||||||
|
body = mail.body.encoded
|
||||||
|
|
||||||
|
expect(body).to_not include(%(<h1>Hello World!</h1>))
|
||||||
|
expect(body).to include(%(This is a txt template))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can deliver only the html part' do
|
||||||
|
mail = WelcomeMailer.new(configuration: configuration).deliver(format: :html)
|
||||||
|
body = mail.body.encoded
|
||||||
|
|
||||||
|
expect(body).to include(%(<h1>Hello World!</h1>))
|
||||||
|
expect(body).to_not include(%(This is a txt template))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'custom delivery' do
|
||||||
|
before do
|
||||||
|
mailer.deliver({})
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { options.fetch(:deliveries).first }
|
||||||
|
let(:mailer) { WelcomeMailer.new(configuration: configuration) }
|
||||||
|
let(:options) { { deliveries: [] } }
|
||||||
|
|
||||||
|
let(:configuration) do
|
||||||
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.root = 'spec/support/fixtures'
|
||||||
|
config.delivery_method = MandrillDeliveryMethod, options
|
||||||
|
end
|
||||||
|
|
||||||
|
Hanami::Mailer.finalize(configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'delivers the mail' do
|
||||||
|
expect(options.fetch(:deliveries).size).to be(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends the correct information' do
|
||||||
|
expect(subject.from).to eq(['noreply@sender.com'])
|
||||||
|
expect(subject.to).to eq(['noreply@recipient.com', 'owner@recipient.com'])
|
||||||
|
expect(subject.subject).to eq("Welcome")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has the correct templates' do
|
||||||
|
expect(subject.html_part.to_s).to include(%(template))
|
||||||
|
expect(subject.text_part.to_s).to include(%(template))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'runs the before callback' do
|
||||||
|
expect(subject.attachments['invoice.pdf']).to_not be(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.expect_with :rspec do |expectations|
|
config.expect_with :rspec do |expectations|
|
||||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||||
|
@ -22,35 +24,9 @@ RSpec.configure do |config|
|
||||||
Kernel.srand config.seed
|
Kernel.srand config.seed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'ostruct'
|
||||||
require 'hanami/utils'
|
require 'hanami/utils'
|
||||||
|
|
||||||
$LOAD_PATH.unshift 'lib'
|
$LOAD_PATH.unshift 'lib'
|
||||||
require 'hanami/mailer'
|
require 'hanami/mailer'
|
||||||
|
|
||||||
Hanami::Mailer.configure do
|
|
||||||
root Pathname.new __dir__ + '/support/fixtures/templates'
|
|
||||||
end
|
|
||||||
|
|
||||||
Hanami::Mailer.class_eval do
|
|
||||||
def self.reset!
|
|
||||||
self.configuration = configuration.duplicate
|
|
||||||
configuration.reset!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Hanami::Mailer::Dsl.class_eval do
|
|
||||||
def reset!
|
|
||||||
@templates = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Hanami::Utils.require!("spec/support")
|
Hanami::Utils.require!("spec/support")
|
||||||
|
|
||||||
Hanami::Mailer.configure do
|
|
||||||
root __dir__ + '/support/fixtures'
|
|
||||||
delivery_method :test
|
|
||||||
|
|
||||||
prepare do
|
|
||||||
include DefaultSubject
|
|
||||||
end
|
|
||||||
end.load!
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
module RSpec
|
||||||
|
module Support
|
||||||
|
module Context
|
||||||
|
def self.included(base)
|
||||||
|
base.class_eval do
|
||||||
|
let(:configuration) do
|
||||||
|
configuration = Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.root = 'spec/support/fixtures'
|
||||||
|
config.delivery_method = :test
|
||||||
|
end
|
||||||
|
|
||||||
|
Hanami::Mailer.finalize(configuration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.include(RSpec::Support::Context)
|
||||||
|
end
|
|
@ -1,42 +1,36 @@
|
||||||
class InvoiceMailer
|
# frozen_string_literal: true
|
||||||
include Hanami::Mailer
|
|
||||||
|
class InvoiceMailer < Hanami::Mailer
|
||||||
template 'invoice'
|
template 'invoice'
|
||||||
end
|
end
|
||||||
|
|
||||||
class RenderMailer
|
class RenderMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TemplateEngineMailer
|
class TemplateEngineMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class CharsetMailer
|
class CharsetMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
from 'noreply@example.com'
|
from 'noreply@example.com'
|
||||||
to 'user@example.com'
|
to 'user@example.com'
|
||||||
subject 'こんにちは'
|
subject 'こんにちは'
|
||||||
end
|
end
|
||||||
|
|
||||||
class MissingFromMailer
|
class MissingFromMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
template 'missing'
|
template 'missing'
|
||||||
|
|
||||||
to 'recipient@example.com'
|
to 'recipient@example.com'
|
||||||
subject 'Hello'
|
subject 'Hello'
|
||||||
end
|
end
|
||||||
|
|
||||||
class MissingToMailer
|
class MissingToMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
template 'missing'
|
template 'missing'
|
||||||
|
|
||||||
from 'sender@example.com'
|
from 'sender@example.com'
|
||||||
subject 'Hello'
|
subject 'Hello'
|
||||||
end
|
end
|
||||||
|
|
||||||
class CcOnlyMailer
|
class CcOnlyMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
template 'missing'
|
template 'missing'
|
||||||
|
|
||||||
cc 'recipient@example.com'
|
cc 'recipient@example.com'
|
||||||
|
@ -44,8 +38,7 @@ class CcOnlyMailer
|
||||||
subject 'Hello'
|
subject 'Hello'
|
||||||
end
|
end
|
||||||
|
|
||||||
class BccOnlyMailer
|
class BccOnlyMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
template 'missing'
|
template 'missing'
|
||||||
|
|
||||||
bcc 'recipient@example.com'
|
bcc 'recipient@example.com'
|
||||||
|
@ -55,35 +48,20 @@ end
|
||||||
|
|
||||||
User = Struct.new(:name, :email)
|
User = Struct.new(:name, :email)
|
||||||
|
|
||||||
class LazyMailer
|
class LazyMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class MethodMailer
|
class ProcMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
from ->(locals) { "hello-#{locals.fetch(:user).name.downcase}@example.com" }
|
||||||
|
to ->(locals) { locals.fetch(:user).email }
|
||||||
|
subject ->(locals) { "[Hanami] #{locals.fetch(:greeting)}" }
|
||||||
|
|
||||||
from :sender
|
before do |_, locals|
|
||||||
to :recipient
|
locals[:greeting] = "Hello, #{locals.fetch(:user).name}"
|
||||||
subject :greeting
|
|
||||||
|
|
||||||
def greeting
|
|
||||||
"Hello, #{user.name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def sender
|
|
||||||
"hello-#{user.name.downcase}@example.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
def recipient
|
|
||||||
user.email
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class WelcomeMailer
|
class WelcomeMailer < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
|
||||||
|
|
||||||
from 'noreply@sender.com'
|
from 'noreply@sender.com'
|
||||||
to ['noreply@recipient.com', 'owner@recipient.com']
|
to ['noreply@recipient.com', 'owner@recipient.com']
|
||||||
cc 'cc@recipient.com'
|
cc 'cc@recipient.com'
|
||||||
|
@ -91,12 +69,34 @@ class WelcomeMailer
|
||||||
|
|
||||||
subject 'Welcome'
|
subject 'Welcome'
|
||||||
|
|
||||||
|
before do |mail|
|
||||||
|
mail.attachments['invoice.pdf'] = "/path/to/invoice-#{invoice_code}.pdf"
|
||||||
|
end
|
||||||
|
|
||||||
def greeting
|
def greeting
|
||||||
'Ahoy'
|
'Ahoy'
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare
|
def invoice_code
|
||||||
mail.attachments['invoice.pdf'] = '/path/to/invoice.pdf'
|
"123"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class EventMailer < Hanami::Mailer
|
||||||
|
from 'events@domain.test'
|
||||||
|
to ->(locals) { locals.fetch(:user).email }
|
||||||
|
subject ->(locals) { "Invitation: #{locals.fetch(:event).title}" }
|
||||||
|
|
||||||
|
before do |mail, locals|
|
||||||
|
mail.attachments["invitation-#{locals.fetch(:event).id}.ics"] = generate_invitation_attachment(locals)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Simulate on-the-fly creation of an attachment file.
|
||||||
|
# For speed purposes we're not gonna create the file, but only return a path.
|
||||||
|
def generate_invitation_attachment(locals)
|
||||||
|
"invitation-#{locals.fetch(:event).id}.ics"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -111,8 +111,14 @@ class MandrillDeliveryMethod
|
||||||
end
|
end
|
||||||
|
|
||||||
module Users
|
module Users
|
||||||
class Welcome
|
class Welcome < Hanami::Mailer
|
||||||
include Hanami::Mailer
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Web
|
||||||
|
module Mailers
|
||||||
|
class SignupMailer < Hanami::Mailer
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixture used by
|
||||||
|
spec/unit/hanami/mailer/templates_finder_spec.rb
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixture used by
|
||||||
|
spec/unit/hanami/mailer/templates_finder_spec.rb
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixture used by
|
||||||
|
spec/unit/hanami/mailer/templates_finder_spec.rb
|
|
@ -1,21 +1,21 @@
|
||||||
RSpec.describe Hanami::Mailer::Configuration do
|
# frozen_string_literal: true
|
||||||
before do
|
|
||||||
@configuration = Hanami::Mailer::Configuration.new
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#root' do
|
RSpec.describe Hanami::Mailer::Configuration do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
describe '#root=' do
|
||||||
describe 'when a value is given' do
|
describe 'when a value is given' do
|
||||||
describe 'and it is a string' do
|
describe 'and it is a string' do
|
||||||
it 'sets it as a Pathname' do
|
it 'sets it as a Pathname' do
|
||||||
@configuration.root 'spec'
|
subject.root = 'spec'
|
||||||
expect(@configuration.root).to eq(Pathname.new('spec').realpath)
|
expect(subject.root).to eq(Pathname.new('spec').realpath)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'and it is a pathname' do
|
describe 'and it is a pathname' do
|
||||||
it 'sets it' do
|
it 'sets it' do
|
||||||
@configuration.root Pathname.new('spec')
|
subject.root = Pathname.new('spec')
|
||||||
expect(@configuration.root).to eq(Pathname.new('spec').realpath)
|
expect(subject.root).to eq(Pathname.new('spec').realpath)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -33,15 +33,15 @@ RSpec.describe Hanami::Mailer::Configuration do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the converted value' do
|
it 'sets the converted value' do
|
||||||
@configuration.root RootPath.new('spec')
|
subject.root = RootPath.new('spec')
|
||||||
expect(@configuration.root).to eq(Pathname.new('spec').realpath)
|
expect(subject.root).to eq(Pathname.new('spec').realpath)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'and it is an unexisting path' do
|
describe 'and it is an unexisting path' do
|
||||||
it 'raises an error' do
|
it 'raises an error' do
|
||||||
expect do
|
expect do
|
||||||
@configuration.root '/path/to/unknown'
|
subject.root = '/path/to/unknown'
|
||||||
end.to raise_error(Errno::ENOENT)
|
end.to raise_error(Errno::ENOENT)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -49,146 +49,69 @@ RSpec.describe Hanami::Mailer::Configuration do
|
||||||
|
|
||||||
describe 'when a value is not given' do
|
describe 'when a value is not given' do
|
||||||
it 'defaults to the current path' do
|
it 'defaults to the current path' do
|
||||||
expect(@configuration.root).to eq(Pathname.new('.').realpath)
|
expect(subject.root).to eq(Pathname.new('.').realpath)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#mailers' do
|
|
||||||
it 'defaults to an empty set' do
|
|
||||||
expect(@configuration.mailers).to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'allows to add mailers' do
|
|
||||||
@configuration.add_mailer(InvoiceMailer)
|
|
||||||
expect(@configuration.mailers).to include(InvoiceMailer)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'eliminates duplications' do
|
|
||||||
@configuration.add_mailer(RenderMailer)
|
|
||||||
@configuration.add_mailer(RenderMailer)
|
|
||||||
|
|
||||||
expect(@configuration.mailers.size).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#prepare' do
|
|
||||||
before do
|
|
||||||
module FooRendering
|
|
||||||
def render
|
|
||||||
'foo'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PrepareMailer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
Object.__send__(:remove_const, :FooRendering)
|
|
||||||
Object.__send__(:remove_const, :PrepareMailer)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'raises error in case of missing block' do
|
|
||||||
expect { @configuration.prepare }.to raise_error(ArgumentError, 'Please provide a block')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# describe '#reset!' do
|
|
||||||
# before do
|
|
||||||
# @configuration.root 'test'
|
|
||||||
# @configuration.add_mailer(InvoiceMailer)
|
|
||||||
|
|
||||||
# @configuration.reset!
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it 'resets root' do
|
|
||||||
# root = Pathname.new('.').realpath
|
|
||||||
|
|
||||||
# @configuration.root.must_equal root
|
|
||||||
# @configuration.mailers.must_be_empty
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it "doesn't reset namespace" do
|
|
||||||
# @configuration.namespace(InvoiceMailer)
|
|
||||||
# @configuration.reset!
|
|
||||||
|
|
||||||
# @configuration.namespace.must_equal(InvoiceMailer)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# end
|
|
||||||
|
|
||||||
describe '#load!' do
|
|
||||||
before do
|
|
||||||
@configuration.root 'spec'
|
|
||||||
@configuration.load!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'loads root' do
|
|
||||||
root = Pathname.new('spec').realpath
|
|
||||||
expect(@configuration.root).to eq(root)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#delivery_method' do
|
describe '#delivery_method' do
|
||||||
describe 'when not previously set' do
|
describe 'when not previously set' do
|
||||||
before do
|
|
||||||
@configuration.reset!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'defaults to SMTP' do
|
it 'defaults to SMTP' do
|
||||||
expect(@configuration.delivery_method).to eq([:smtp, {}])
|
expect(subject.delivery_method).to eq(:smtp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'set with a symbol' do
|
describe 'set with a symbol' do
|
||||||
before do
|
before do
|
||||||
@configuration.delivery_method :exim, location: '/path/to/exim'
|
subject.delivery_method = :exim, { location: '/path/to/exim' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'saves the delivery method in the configuration' do
|
it 'saves the delivery method in the configuration' do
|
||||||
expect(@configuration.delivery_method).to eq([:exim, { location: '/path/to/exim' }])
|
expect(subject.delivery_method).to eq([:exim, { location: '/path/to/exim' }])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'set with a class' do
|
describe 'set with a class' do
|
||||||
before do
|
before do
|
||||||
@configuration.delivery_method MandrillDeliveryMethod,
|
subject.delivery_method = MandrillDeliveryMethod,
|
||||||
username: 'mandrill-username', password: 'mandrill-api-key'
|
{ username: 'mandrill-username', password: 'mandrill-api-key' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'saves the delivery method in the configuration' do
|
it 'saves the delivery method in the configuration' do
|
||||||
expect(@configuration.delivery_method).to eq([MandrillDeliveryMethod, username: 'mandrill-username', password: 'mandrill-api-key'])
|
expect(subject.delivery_method).to eq([MandrillDeliveryMethod, username: 'mandrill-username', password: 'mandrill-api-key'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#default_charset' do
|
describe '#default_charset' do
|
||||||
describe 'when not previously set' do
|
describe 'when not previously set' do
|
||||||
before do
|
|
||||||
@configuration.reset!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'defaults to UTF-8' do
|
it 'defaults to UTF-8' do
|
||||||
expect(@configuration.default_charset).to eq('UTF-8')
|
expect(subject.default_charset).to eq('UTF-8')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when set' do
|
describe 'when set' do
|
||||||
before do
|
before do
|
||||||
@configuration.default_charset 'iso-8859-1'
|
subject.default_charset = 'iso-8859-1'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'saves the delivery method in the configuration' do
|
it 'saves the delivery method in the configuration' do
|
||||||
expect(@configuration.default_charset).to eq('iso-8859-1')
|
expect(subject.default_charset).to eq('iso-8859-1')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#prepare' do
|
describe "#freeze" do
|
||||||
it 'injects code in each mailer'
|
before do
|
||||||
# it 'injects code in each mailer' do
|
subject.freeze
|
||||||
# InvoiceMailer.subject.must_equal 'default subject'
|
end
|
||||||
# end
|
|
||||||
|
it "is frozen" do
|
||||||
|
expect(subject).to be_frozen
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error if trying to add a mailer" do
|
||||||
|
expect { subject.add_mailer(WelcomeMailer) }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,184 +0,0 @@
|
||||||
RSpec.describe Hanami::Mailer do
|
|
||||||
describe '.deliver' do
|
|
||||||
before do
|
|
||||||
Hanami::Mailer.deliveries.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can deliver with specified charset' do
|
|
||||||
CharsetMailer.deliver(charset: charset = 'iso-2022-jp')
|
|
||||||
|
|
||||||
mail = Hanami::Mailer.deliveries.first
|
|
||||||
expect(mail.charset).to eq(charset)
|
|
||||||
expect(mail.parts.first.charset).to eq(charset)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises error when 'from' isn't specified" do
|
|
||||||
expect { MissingFromMailer.deliver }.to raise_error(Hanami::Mailer::MissingDeliveryDataError)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises error when 'to' isn't specified" do
|
|
||||||
expect { MissingToMailer.deliver }.to raise_error(Hanami::Mailer::MissingDeliveryDataError)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't raise error when 'cc' is specified but 'to' isn't" do
|
|
||||||
CcOnlyMailer.deliver
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't raise error when 'bcc' is specified but 'to' isn't" do
|
|
||||||
BccOnlyMailer.deliver
|
|
||||||
end
|
|
||||||
|
|
||||||
it "lets other errors to bubble up" do
|
|
||||||
mailer = CharsetMailer.new({})
|
|
||||||
mail = Class.new do
|
|
||||||
def deliver
|
|
||||||
raise ArgumentError, "ouch"
|
|
||||||
end
|
|
||||||
end.new
|
|
||||||
|
|
||||||
expect(mailer).to receive(:mail).and_return(mail)
|
|
||||||
expect { mailer.deliver }.to raise_error(ArgumentError, "ouch")
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'test delivery with hardcoded values' do
|
|
||||||
before do
|
|
||||||
WelcomeMailer.deliver
|
|
||||||
@mail = Hanami::Mailer.deliveries.first
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
Hanami::Mailer.deliveries.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'delivers the mail' do
|
|
||||||
expect(Hanami::Mailer.deliveries.length).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sends the correct information' do
|
|
||||||
expect(@mail.from).to eq(['noreply@sender.com'])
|
|
||||||
expect(@mail.to).to eq(['noreply@recipient.com', 'owner@recipient.com'])
|
|
||||||
expect(@mail.cc).to eq(['cc@recipient.com'])
|
|
||||||
expect(@mail.bcc).to eq(['bcc@recipient.com'])
|
|
||||||
expect(@mail.subject).to eq('Welcome')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has the correct templates' do
|
|
||||||
expect(@mail.html_part.to_s).to include(%(template))
|
|
||||||
expect(@mail.text_part.to_s).to include(%(template))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'interprets the prepare statement' do
|
|
||||||
attachment = @mail.attachments['invoice.pdf']
|
|
||||||
|
|
||||||
expect(attachment).to be_kind_of(Mail::Part)
|
|
||||||
|
|
||||||
expect(attachment).to be_attachment
|
|
||||||
expect(attachment).to_not be_inline
|
|
||||||
expect(attachment).to_not be_multipart
|
|
||||||
|
|
||||||
expect(attachment.filename).to eq('invoice.pdf')
|
|
||||||
|
|
||||||
expect(attachment.content_type).to match('application/pdf')
|
|
||||||
expect(attachment.content_type).to match('filename=invoice.pdf')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'test delivery with methods' do
|
|
||||||
before do
|
|
||||||
@user = User.new('Name', 'student@deigirls.com')
|
|
||||||
MethodMailer.deliver(user: @user)
|
|
||||||
|
|
||||||
@mail = Hanami::Mailer.deliveries.first
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
Hanami::Mailer.deliveries.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'delivers the mail' do
|
|
||||||
expect(Hanami::Mailer.deliveries.length).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sends the correct information' do
|
|
||||||
expect(@mail.from).to eq(["hello-#{@user.name.downcase}@example.com"])
|
|
||||||
expect(@mail.to).to eq([@user.email])
|
|
||||||
expect(@mail.subject).to eq("Hello, #{@user.name}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'multipart' do
|
|
||||||
after do
|
|
||||||
Hanami::Mailer.deliveries.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'delivers all the parts by default' do
|
|
||||||
WelcomeMailer.deliver
|
|
||||||
|
|
||||||
mail = Hanami::Mailer.deliveries.first
|
|
||||||
body = mail.body.encoded
|
|
||||||
|
|
||||||
expect(body).to include(%(<h1>Hello World!</h1>))
|
|
||||||
expect(body).to include(%(This is a txt template))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can deliver only the text part' do
|
|
||||||
WelcomeMailer.deliver(format: :txt)
|
|
||||||
|
|
||||||
mail = Hanami::Mailer.deliveries.first
|
|
||||||
body = mail.body.encoded
|
|
||||||
|
|
||||||
expect(body).to_not include(%(<h1>Hello World!</h1>))
|
|
||||||
expect(body).to include(%(This is a txt template))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can deliver only the html part' do
|
|
||||||
WelcomeMailer.deliver(format: :html)
|
|
||||||
|
|
||||||
mail = Hanami::Mailer.deliveries.first
|
|
||||||
body = mail.body.encoded
|
|
||||||
|
|
||||||
expect(body).to include(%(<h1>Hello World!</h1>))
|
|
||||||
expect(body).to_not include(%(This is a txt template))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'custom delivery' do
|
|
||||||
before do
|
|
||||||
@options = options = { deliveries: [] }
|
|
||||||
|
|
||||||
# Hanami::Mailer.reset!
|
|
||||||
# Hanami::Mailer.configure do
|
|
||||||
# delivery_method MandrillDeliveryMethod, options
|
|
||||||
# end.load!
|
|
||||||
|
|
||||||
WelcomeMailer.deliver
|
|
||||||
|
|
||||||
@mail = options.fetch(:deliveries).first
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'delivers the mail'
|
|
||||||
# it 'delivers the mail' do
|
|
||||||
# @options.fetch(:deliveries).length.must_equal 1
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it 'sends the correct information' do
|
|
||||||
# @mail.from.must_equal ['noreply@sender.com']
|
|
||||||
# @mail.to.must_equal ['noreply@recipient.com']
|
|
||||||
# @mail.subject.must_equal "Welcome"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it 'has the correct templates' do
|
|
||||||
# @mail.html_part.to_s.must_include %(template)
|
|
||||||
# @mail.text_part.to_s.must_include %(template)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it 'interprets the prepare statement' do
|
|
||||||
# @mail.attachments['invoice.pdf'].wont_be_nil
|
|
||||||
# end
|
|
||||||
|
|
||||||
# it 'adds the attachment to the mail object' do
|
|
||||||
# @mail.attachments['render_mailer.html.erb'].wont_be_nil
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,47 +1,123 @@
|
||||||
RSpec.describe Hanami::Mailer do
|
# frozen_string_literal: true
|
||||||
describe '.template' do
|
|
||||||
describe 'when no value is set' do
|
|
||||||
it 'returns the convention name' do
|
|
||||||
expect(RenderMailer.template).to eq('render_mailer')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns correct namespaced value' do
|
RSpec.describe Hanami::Mailer::Dsl do
|
||||||
expect(Users::Welcome.template).to eq('users/welcome')
|
let(:mailer) { Class.new { extend Hanami::Mailer::Dsl } }
|
||||||
end
|
|
||||||
|
describe '.from' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.from).to be(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when a value is set' do
|
it 'sets the value' do
|
||||||
it 'returns that name' do
|
sender = 'sender@hanami.test'
|
||||||
expect(InvoiceMailer.template).to eq('invoice')
|
mailer.from sender
|
||||||
end
|
|
||||||
|
expect(mailer.from).to eq(sender)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.templates' do
|
describe '.to' do
|
||||||
describe 'when no value is set' do
|
it 'returns the default value' do
|
||||||
it 'returns a set of templates' do
|
expect(mailer.to).to be(nil)
|
||||||
template_formats = LazyMailer.templates.keys
|
|
||||||
expect(template_formats).to eq(%i[html txt])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns only the template for the given format' do
|
|
||||||
template = LazyMailer.templates(:txt)
|
|
||||||
expect(template).to be_kind_of(Hanami::Mailer::Template)
|
|
||||||
expect(template.file).to match(%r{spec/support/fixtures/templates/lazy_mailer.txt.erb\z})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when a value is set' do
|
it 'sets a single value' do
|
||||||
it 'returns a set of templates' do
|
recipient = 'recipient@hanami.test'
|
||||||
template_formats = InvoiceMailer.templates.keys
|
mailer.to recipient
|
||||||
expect(template_formats).to eq([:html])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns only the template for the given format' do
|
expect(mailer.to).to eq(recipient)
|
||||||
template = InvoiceMailer.templates(:html)
|
end
|
||||||
expect(template).to be_kind_of(Hanami::Mailer::Template)
|
|
||||||
expect(template.file).to match(%r{spec/support/fixtures/templates/invoice.html.erb\z})
|
it 'sets an array of values' do
|
||||||
end
|
recipients = ['recipient@hanami.test']
|
||||||
|
mailer.to recipients
|
||||||
|
|
||||||
|
expect(mailer.to).to eq(recipients)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.cc' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.cc).to be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets a single value' do
|
||||||
|
recipient = 'cc@hanami.test'
|
||||||
|
mailer.cc recipient
|
||||||
|
|
||||||
|
expect(mailer.cc).to eq(recipient)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets an array of values' do
|
||||||
|
recipients = ['cc@hanami.test']
|
||||||
|
mailer.cc recipients
|
||||||
|
|
||||||
|
expect(mailer.cc).to eq(recipients)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.bcc' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.bcc).to be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets a single value' do
|
||||||
|
recipient = 'bcc@hanami.test'
|
||||||
|
mailer.bcc recipient
|
||||||
|
|
||||||
|
expect(mailer.bcc).to eq(recipient)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets an array of values' do
|
||||||
|
recipients = ['bcc@hanami.test']
|
||||||
|
mailer.bcc recipients
|
||||||
|
|
||||||
|
expect(mailer.bcc).to eq(recipients)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.subject' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.subject).to be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets a value' do
|
||||||
|
mail_subject = 'Hello'
|
||||||
|
mailer.subject mail_subject
|
||||||
|
|
||||||
|
expect(mailer.subject).to eq(mail_subject)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.template' do
|
||||||
|
it 'sets a value' do
|
||||||
|
mailer.template 'file'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.template_name' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.template_name).to be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns value, if set' do
|
||||||
|
template = 'file'
|
||||||
|
mailer.template template
|
||||||
|
|
||||||
|
expect(mailer.template_name).to eq(template)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.before' do
|
||||||
|
it 'returns the default value' do
|
||||||
|
expect(mailer.before).to be_kind_of(Proc)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets a value' do
|
||||||
|
blk = ->(*) {}
|
||||||
|
mailer.before(&blk)
|
||||||
|
|
||||||
|
expect(mailer.before).to eq(blk)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::Error do
|
||||||
|
it "inherits from StandardError" do
|
||||||
|
expect(described_class.ancestors).to include(StandardError)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::Finalizer do
|
||||||
|
let(:configuration) do
|
||||||
|
Hanami::Mailer::Configuration.new do |config|
|
||||||
|
config.root = 'spec/support/fixtures'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:mailers) { [double('mailer', template_name: "invoice")] }
|
||||||
|
|
||||||
|
describe '.finalize' do
|
||||||
|
it 'eager autoloads modules from mail gem' do
|
||||||
|
expect(Mail).to receive(:eager_autoload!)
|
||||||
|
described_class.finalize(mailers, configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds the mailer to the configuration" do
|
||||||
|
expect(configuration).to receive(:add_mailer).with(mailers.first)
|
||||||
|
described_class.finalize(mailers, configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns frozen configuration" do
|
||||||
|
actual = described_class.finalize(mailers, configuration)
|
||||||
|
expect(actual).to be_frozen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::MissingDeliveryDataError do
|
||||||
|
it "inherits from Hanami::Error" do
|
||||||
|
expect(described_class.ancestors).to include(Hanami::Mailer::Error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a custom error message" do
|
||||||
|
expect { raise described_class }.to raise_error(described_class, "Missing delivery data, please check 'from', or 'to'")
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,44 +0,0 @@
|
||||||
RSpec.describe Hanami::Mailer do
|
|
||||||
describe '#render' do
|
|
||||||
describe 'when template is explicitly declared' do
|
|
||||||
let(:mailer) { InvoiceMailer.new }
|
|
||||||
|
|
||||||
it 'renders the given template' do
|
|
||||||
expect(mailer.render(:html)).to include(%(<h1>Invoice template</h1>))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'when template is implicitly declared' do
|
|
||||||
let(:mailer) { LazyMailer.new }
|
|
||||||
|
|
||||||
it 'looks for template with same name with inflected classname and render it' do
|
|
||||||
expect(mailer.render(:html)).to include(%(Hello World))
|
|
||||||
expect(mailer.render(:txt)).to include(%(This is a txt template))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'when mailer defines context' do
|
|
||||||
let(:mailer) { WelcomeMailer.new }
|
|
||||||
|
|
||||||
it 'renders template with defined context' do
|
|
||||||
expect(mailer.render(:txt)).to include(%(Ahoy))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'when locals are parsed in' do
|
|
||||||
let(:mailer) { RenderMailer.new(user: User.new('Luca')) }
|
|
||||||
|
|
||||||
it 'renders template with parsed locals' do
|
|
||||||
expect(mailer.render(:html)).to include(%(Luca))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'with HAML template engine' do
|
|
||||||
let(:mailer) { TemplateEngineMailer.new(user: User.new('Luca')) }
|
|
||||||
|
|
||||||
it 'renders template with parsed locals' do
|
|
||||||
expect(mailer.render(:html)).to include(%(<h1>\nLuca\n</h1>\n))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::TemplateName do
|
||||||
|
describe ".call" do
|
||||||
|
context "with top level namespace" do
|
||||||
|
let(:namespace) { Object }
|
||||||
|
|
||||||
|
it "returns an instance of ::String" do
|
||||||
|
template_name = described_class.call("template", namespace)
|
||||||
|
expect(template_name).to be_kind_of(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from plain name" do
|
||||||
|
template_name = described_class.call("template", namespace)
|
||||||
|
expect(template_name).to eq("template")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from camel case name" do
|
||||||
|
template_name = described_class.call("ATemplate", namespace)
|
||||||
|
expect(template_name).to eq("a_template")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from snake case name" do
|
||||||
|
template_name = described_class.call("a_template", namespace)
|
||||||
|
expect(template_name).to eq("a_template")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from modulized name" do
|
||||||
|
template_name = described_class.call("Mailers::WelcomeMailer", namespace)
|
||||||
|
expect(template_name).to eq("mailers/welcome_mailer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from class" do
|
||||||
|
template_name = described_class.call(InvoiceMailer.name, namespace)
|
||||||
|
expect(template_name).to eq("invoice_mailer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from modulized class" do
|
||||||
|
template_name = described_class.call(Users::Welcome.name, namespace)
|
||||||
|
expect(template_name).to eq("users/welcome")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns blank string from blank name" do
|
||||||
|
template_name = described_class.call("", namespace)
|
||||||
|
expect(template_name).to eq("")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error with nil name" do
|
||||||
|
expect { described_class.call(nil, namespace) }.to raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with application namespace" do
|
||||||
|
let(:namespace) { Web::Mailers }
|
||||||
|
|
||||||
|
it "returns name from class name" do
|
||||||
|
template_name = described_class.call("SignupMailer", namespace)
|
||||||
|
expect(template_name).to eq("signup_mailer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns name from modulized class name" do
|
||||||
|
template_name = described_class.call(Web::Mailers::SignupMailer.name, namespace)
|
||||||
|
expect(template_name).to eq("signup_mailer")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::Template do
|
||||||
|
subject { described_class.new(file) }
|
||||||
|
let(:file) { "spec/support/fixtures/templates/welcome_mailer.txt.erb" }
|
||||||
|
|
||||||
|
describe "#initialize" do
|
||||||
|
context "with existing file" do
|
||||||
|
it "instantiates template" do
|
||||||
|
expect(subject).to be_kind_of(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "initialize frozen instance" do
|
||||||
|
expect(subject).to be_frozen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with missing template engine" do
|
||||||
|
it "returns error" do
|
||||||
|
expect { described_class.new("Gemfile") }.to raise_error(RuntimeError, "No template engine registered for Gemfile")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with unexisting file" do
|
||||||
|
it "returns error" do
|
||||||
|
expect { described_class.new("foo.erb") }.to raise_error(Errno::ENOENT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#render" do
|
||||||
|
it "renders template" do
|
||||||
|
scope = Object.new
|
||||||
|
actual = subject.render(scope, greeting: "Hello")
|
||||||
|
|
||||||
|
expect(actual).to eq("This is a txt template\nHello")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "renders with unfrozen object" do
|
||||||
|
scope = Object.new
|
||||||
|
expect(scope).to receive(:dup)
|
||||||
|
|
||||||
|
subject.render(scope, greeting: "Hello")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,75 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::TemplatesFinder do
|
||||||
|
subject { described_class.new(root) }
|
||||||
|
# NOTE: please do not change this name, because `#find` specs are relying on
|
||||||
|
# template fixtures. See all the fixtures under: spec/support/fixtures/templates
|
||||||
|
let(:template_name) { "welcome_mailer" }
|
||||||
|
let(:root) { configuration.root }
|
||||||
|
|
||||||
|
describe "#initialize" do
|
||||||
|
context "with valid root" do
|
||||||
|
it "instantiates a new finder instance" do
|
||||||
|
expect(subject).to be_kind_of(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a frozen object" do
|
||||||
|
expect(subject).to be_frozen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with unexisting root" do
|
||||||
|
let(:root) { "path/to/unexisting" }
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect { subject }.to raise_error(Errno::ENOENT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with nil root" do
|
||||||
|
let(:root) { nil }
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect { subject }.to raise_error(TypeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#find" do
|
||||||
|
context "with valid template name" do
|
||||||
|
it "returns templates" do
|
||||||
|
actual = subject.find(template_name)
|
||||||
|
|
||||||
|
# It excludes all the files that aren't matching the convention:
|
||||||
|
#
|
||||||
|
# `<template_name>.<format>.<template_engine>`
|
||||||
|
#
|
||||||
|
# Under `spec/support/fixtures/templates` we have the following files:
|
||||||
|
#
|
||||||
|
# * welcome_mailer
|
||||||
|
# * welcome_mailer.erb
|
||||||
|
# * welcome_mailer.html
|
||||||
|
# * welcome_mailer.html.erb
|
||||||
|
# * welcome_mailer.txt.erb
|
||||||
|
#
|
||||||
|
# Only the last two are matching the pattern, here's why we have only
|
||||||
|
# two templates loaded.
|
||||||
|
expect(actual.keys).to eq(%i[html txt])
|
||||||
|
actual.each_value do |template|
|
||||||
|
expect(template).to be_kind_of(Hanami::Mailer::Template)
|
||||||
|
expect(template.instance_variable_get(:@_template).__send__(:file)).to match(%r{spec/support/fixtures/templates/welcome_mailer.(html|txt).erb})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with missing template" do
|
||||||
|
let(:template_name) { "missing_template" }
|
||||||
|
|
||||||
|
it "doesn't return templates" do
|
||||||
|
actual = subject.find(template_name)
|
||||||
|
|
||||||
|
expect(actual).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer::UnknownMailerError do
|
||||||
|
it "inherits from Hanami::Error" do
|
||||||
|
expect(described_class.ancestors).to include(Hanami::Mailer::Error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a custom error message" do
|
||||||
|
mailer = InvoiceMailer
|
||||||
|
expect { raise described_class.new(mailer) }.to raise_error(described_class, "Unknown mailer: #{mailer}. Please finalize the configuration before to use it.")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has explicit handling for nil" do
|
||||||
|
mailer = nil
|
||||||
|
expect { raise described_class.new(mailer) }.to raise_error(described_class, "Unknown mailer: #{mailer.inspect}. Please finalize the configuration before to use it.")
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe "Hanami::Mailer::VERSION" do
|
RSpec.describe "Hanami::Mailer::VERSION" do
|
||||||
it "returns current version" do
|
it "returns current version" do
|
||||||
expect(Hanami::Mailer::VERSION).to eq("1.1.0")
|
expect(Hanami::Mailer::VERSION).to eq("1.1.0")
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Mailer do
|
||||||
|
context "constants" do
|
||||||
|
it "marks them as private" do
|
||||||
|
expect { described_class::CONTENT_TYPES }.to raise_error(NameError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context ".finalize" do
|
||||||
|
let(:configuration) { Hanami::Mailer::Configuration.new }
|
||||||
|
|
||||||
|
it "finalizes the given configuration" do
|
||||||
|
actual = described_class.finalize(configuration)
|
||||||
|
expect(actual).to be_frozen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "#initialize" do
|
||||||
|
let(:configuration) { Hanami::Mailer::Configuration.new }
|
||||||
|
|
||||||
|
it "builds an frozen instance" do
|
||||||
|
mailer = described_class.new(configuration: configuration)
|
||||||
|
expect(mailer).to be_frozen
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prevents memoization mutations" do
|
||||||
|
mailer = Class.new(described_class) do
|
||||||
|
def self.name
|
||||||
|
"memoization_attempt"
|
||||||
|
end
|
||||||
|
|
||||||
|
def foo
|
||||||
|
@foo ||= "foo"
|
||||||
|
end
|
||||||
|
end.new(configuration: configuration)
|
||||||
|
|
||||||
|
expect { mailer.foo }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prevents accidental configuration removal" do
|
||||||
|
mailer = Class.new(described_class) do
|
||||||
|
def self.name
|
||||||
|
"configuration_removal"
|
||||||
|
end
|
||||||
|
|
||||||
|
def foo
|
||||||
|
@configuration = nil
|
||||||
|
end
|
||||||
|
end.new(configuration: configuration)
|
||||||
|
|
||||||
|
expect { mailer.foo }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "#deliver" do
|
||||||
|
context "when mailer has from/to defined with DSL" do
|
||||||
|
let(:mailer) { CharsetMailer.new(configuration: configuration) }
|
||||||
|
|
||||||
|
it "delivers email with valid locals" do
|
||||||
|
mail = mailer.deliver({})
|
||||||
|
expect(mail).to be_kind_of(Mail::Message)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is aliased as #call" do
|
||||||
|
mail = mailer.call({})
|
||||||
|
expect(mail).to be_kind_of(Mail::Message)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error when locals are nil" do
|
||||||
|
expect { mailer.deliver(nil) }.to raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when from/to are missing" do
|
||||||
|
let(:mailer) { InvoiceMailer.new(configuration: configuration) }
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect { mailer.deliver({}) }.to raise_error(Hanami::Mailer::MissingDeliveryDataError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when using #{described_class} directly" do
|
||||||
|
let(:mailer) do
|
||||||
|
described_class.new(configuration: configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect { mailer.deliver({}) }.to raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-finalized configuration" do
|
||||||
|
let(:configuration) { Hanami::Mailer::Configuration.new }
|
||||||
|
|
||||||
|
let(:mailer) do
|
||||||
|
Class.new(described_class) do
|
||||||
|
def self.name
|
||||||
|
"anonymous_mailer"
|
||||||
|
end
|
||||||
|
end.new(configuration: configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect { mailer.deliver({}) }.to raise_error(Hanami::Mailer::UnknownMailerError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "locals" do
|
||||||
|
let(:mailer) { EventMailer.new(configuration: configuration) }
|
||||||
|
let(:user) { OpenStruct.new(name: "Luca", email: "luca@domain.test") }
|
||||||
|
let(:event) { OpenStruct.new(id: 23, title: "Event #23") }
|
||||||
|
|
||||||
|
it "uses locals during the delivery process" do
|
||||||
|
mail = mailer.deliver(user: user, event: event)
|
||||||
|
|
||||||
|
expect(mail.to).to eq(["luca@domain.test"])
|
||||||
|
expect(mail.subject).to eq("Invitation: Event #23")
|
||||||
|
expect(mail.attachments[0].filename).to eq("invitation-23.ics")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#render' do
|
||||||
|
describe 'when template is explicitly declared' do
|
||||||
|
let(:mailer) { InvoiceMailer.new(configuration: configuration) }
|
||||||
|
|
||||||
|
it 'renders the given template' do
|
||||||
|
expect(mailer.render(:html, {})).to include(%(<h1>Invoice template</h1>))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when template is implicitly declared' do
|
||||||
|
let(:mailer) { LazyMailer.new(configuration: configuration) }
|
||||||
|
|
||||||
|
it 'looks for template with same name with inflected classname and render it' do
|
||||||
|
expect(mailer.render(:html, {})).to include(%(Hello World))
|
||||||
|
expect(mailer.render(:txt, {})).to include(%(This is a txt template))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when mailer defines context' do
|
||||||
|
let(:mailer) { WelcomeMailer.new(configuration: configuration) }
|
||||||
|
|
||||||
|
it 'renders template with defined context' do
|
||||||
|
expect(mailer.render(:txt, {})).to include(%(Ahoy))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when locals are parsed in' do
|
||||||
|
let(:mailer) { RenderMailer.new(configuration: configuration) }
|
||||||
|
let(:locals) { { user: User.new('Luca') } }
|
||||||
|
|
||||||
|
it 'renders template with parsed locals' do
|
||||||
|
expect(mailer.render(:html, locals)).to include(locals.fetch(:user).name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with HAML template engine' do
|
||||||
|
let(:mailer) { TemplateEngineMailer.new(configuration: configuration) }
|
||||||
|
let(:locals) { { user: User.new('MG') } }
|
||||||
|
|
||||||
|
it 'renders template with parsed locals' do
|
||||||
|
expect(mailer.render(:html, locals)).to include(%(<h1>\n#{locals.fetch(:user).name}\n</h1>\n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue