hanami-mailer 1.3.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +154 -45
- data/LICENSE +20 -0
- data/README.md +518 -303
- data/hanami-mailer.gemspec +23 -18
- data/lib/hanami/mailer/attachment.rb +133 -0
- data/lib/hanami/mailer/attachment_set.rb +38 -0
- data/lib/hanami/mailer/delivery/result.rb +101 -0
- data/lib/hanami/mailer/delivery/smtp.rb +168 -0
- data/lib/hanami/mailer/delivery/test.rb +57 -0
- data/lib/hanami/mailer/dsl/attachments.rb +108 -0
- data/lib/hanami/mailer/dsl/exposure.rb +69 -0
- data/lib/hanami/mailer/dsl/exposures.rb +111 -0
- data/lib/hanami/mailer/dsl/plucky_proc.rb +135 -0
- data/lib/hanami/mailer/errors.rb +73 -0
- data/lib/hanami/mailer/message.rb +101 -0
- data/lib/hanami/mailer/version.rb +4 -3
- data/lib/hanami/mailer/view_integration.rb +205 -0
- data/lib/hanami/mailer.rb +372 -270
- data/lib/hanami-mailer.rb +1 -1
- metadata +40 -97
- data/LICENSE.md +0 -22
- data/lib/hanami/mailer/configuration.rb +0 -310
- data/lib/hanami/mailer/dsl.rb +0 -628
- data/lib/hanami/mailer/rendering/template_name.rb +0 -55
- data/lib/hanami/mailer/rendering/templates_finder.rb +0 -135
- data/lib/hanami/mailer/template.rb +0 -42
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Mailer
|
|
5
|
+
# Integration module for Hanami::View support.
|
|
6
|
+
#
|
|
7
|
+
# This module is included when Hanami::View is available, providing automatic view building and
|
|
8
|
+
# settings inheritance.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
module ViewIntegration
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
# Prepend the initializer module to wrap initialization
|
|
15
|
+
prepend PrependedMethods
|
|
16
|
+
|
|
17
|
+
# Add class-level methods, including the lazily-built, memoized default view
|
|
18
|
+
extend ClassMethods
|
|
19
|
+
|
|
20
|
+
# Whether to automatically build views from exposures
|
|
21
|
+
# Set to false to disable automatic view integration behavior
|
|
22
|
+
setting :integrate_view, default: true
|
|
23
|
+
|
|
24
|
+
# The base class used when building the mailer's view. Defaults to Hanami::View, but
|
|
25
|
+
# may be set to an already-configured view class (such as a view class within a Hanami
|
|
26
|
+
# app), in which case the built view inherits that class's configuration — context,
|
|
27
|
+
# parts, scopes, paths, helpers and so on.
|
|
28
|
+
setting :view_class, default: Hanami::View
|
|
29
|
+
|
|
30
|
+
# Copy all settings from Hanami::View to support default view integration.
|
|
31
|
+
# This allows mailers to configure view-related settings (like layouts_dir,
|
|
32
|
+
# default_format, inflector, etc.) without having to manually redefine them.
|
|
33
|
+
existing_settings = config._settings.keys.to_set
|
|
34
|
+
Hanami::View.config._settings.each do |setting_def|
|
|
35
|
+
next if existing_settings.include?(setting_def.name)
|
|
36
|
+
|
|
37
|
+
setting(
|
|
38
|
+
setting_def.name,
|
|
39
|
+
default: setting_def.default,
|
|
40
|
+
constructor: setting_def.constructor,
|
|
41
|
+
**setting_def.options
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Class-level methods added to mailer classes when Hanami::View is available.
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
module ClassMethods
|
|
51
|
+
# The auto-built default view for this mailer class.
|
|
52
|
+
#
|
|
53
|
+
# Built lazily on first access (the first render) and memoized, since it depends only on
|
|
54
|
+
# class-level state (config, exposures, class name) and is identical for every instance.
|
|
55
|
+
# Returns nil when view integration is disabled or no usable template paths are configured.
|
|
56
|
+
#
|
|
57
|
+
# Note: because the view is memoized on first render, later changes to view-related
|
|
58
|
+
# config or exposures are not reflected. Mailer classes are configured once at definition
|
|
59
|
+
# time, so this is intentional.
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
def default_view
|
|
63
|
+
return @default_view if defined?(@default_view)
|
|
64
|
+
|
|
65
|
+
@default_view = config.integrate_view ? DefaultViewBuilder.call(self) : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Defines an exposure that will be decorated with a matching view part.
|
|
69
|
+
#
|
|
70
|
+
# This is a shorthand for `expose(..., decorate: true)`.
|
|
71
|
+
#
|
|
72
|
+
# @see Mailer.expose
|
|
73
|
+
def decorate(*names, **options, &block)
|
|
74
|
+
expose(*names, **options, decorate: true, &block)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Internal module for prepending view and render behavior.
|
|
79
|
+
# Wraps the base class to provide automatic view building and
|
|
80
|
+
# per-format template error handling.
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
module PrependedMethods
|
|
84
|
+
# The view used for rendering: a per-instance override passed to the constructor, falling
|
|
85
|
+
# back to the mailer class's lazily-built, memoized default view.
|
|
86
|
+
def view
|
|
87
|
+
@view || self.class.default_view
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Renders HTML and text bodies, handling missing templates per format.
|
|
91
|
+
def render(input, format: nil)
|
|
92
|
+
html, html_error = try_render(:html, input) unless format == :text
|
|
93
|
+
text, text_error = try_render(:text, input) unless format == :html
|
|
94
|
+
|
|
95
|
+
# Tolerate one missing template if attempting to render both. Otherwise, consider any
|
|
96
|
+
# error as fatal.
|
|
97
|
+
raise html_error if html_error && (format || text_error)
|
|
98
|
+
raise text_error if text_error && format
|
|
99
|
+
|
|
100
|
+
[html, text]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def try_render(format, input)
|
|
104
|
+
[render_view(format, input), nil]
|
|
105
|
+
rescue Hanami::View::TemplateNotFoundError => exception
|
|
106
|
+
[nil, exception]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Builder class for constructing default views.
|
|
111
|
+
# Keeps view building logic separate from mailer instances.
|
|
112
|
+
#
|
|
113
|
+
# @api private
|
|
114
|
+
class DefaultViewBuilder
|
|
115
|
+
class << self
|
|
116
|
+
# Builds a default view from exposures if Hanami::View is available.
|
|
117
|
+
def call(mailer_class)
|
|
118
|
+
view_class = mailer_class.config.view_class || Hanami::View
|
|
119
|
+
|
|
120
|
+
# A view needs paths to find its templates. These may be configured on the mailer, or
|
|
121
|
+
# inherited from an already-configured `view_class` (e.g. within a Hanami app).
|
|
122
|
+
paths = mailer_class.config.paths
|
|
123
|
+
if (paths.nil? || paths.empty?) && view_class.respond_to?(:config)
|
|
124
|
+
paths = view_class.config.paths
|
|
125
|
+
end
|
|
126
|
+
return nil if paths.nil? || paths.empty?
|
|
127
|
+
|
|
128
|
+
template = mailer_class.config.template
|
|
129
|
+
template ||= inferred_template(mailer_class)
|
|
130
|
+
|
|
131
|
+
build_view_class(
|
|
132
|
+
view_class: view_class,
|
|
133
|
+
template: template,
|
|
134
|
+
exposures: mailer_class.exposures,
|
|
135
|
+
config: mailer_class.config
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
# Infers template path from class name.
|
|
142
|
+
#
|
|
143
|
+
# @example
|
|
144
|
+
# Mailers::WelcomeMailer -> "mailers/welcome_mailer"
|
|
145
|
+
def inferred_template(mailer_class)
|
|
146
|
+
return nil unless mailer_class.name
|
|
147
|
+
|
|
148
|
+
mailer_class.name
|
|
149
|
+
.gsub("::", "/")
|
|
150
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
151
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
152
|
+
.downcase
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
156
|
+
|
|
157
|
+
# Builds a Hanami::View instance from the mailer's configuration.
|
|
158
|
+
#
|
|
159
|
+
# The view is a subclass of the mailer's configured `view_class`, so it inherits that
|
|
160
|
+
# class's configuration. Only view settings the mailer has *explicitly* configured are
|
|
161
|
+
# applied as overrides, leaving an already-configured base class (such as a Hanami app's
|
|
162
|
+
# view) to provide its context, parts, scopes and helpers by inheritance. A standalone
|
|
163
|
+
# mailer (whose `view_class` is the unconfigured `Hanami::View`) still drives its own
|
|
164
|
+
# view via these overrides.
|
|
165
|
+
def build_view_class(view_class:, template:, exposures:, config:)
|
|
166
|
+
view_template = template
|
|
167
|
+
view_exposures = exposures
|
|
168
|
+
mailer_config = config
|
|
169
|
+
|
|
170
|
+
built = Class.new(view_class) do
|
|
171
|
+
Hanami::View.config._settings.each do |setting_def|
|
|
172
|
+
name = setting_def.name
|
|
173
|
+
|
|
174
|
+
# `template` and `layout` are handled explicitly below.
|
|
175
|
+
next if name == :template || name == :layout
|
|
176
|
+
next unless mailer_config.respond_to?(name)
|
|
177
|
+
next unless mailer_config.configured?(name)
|
|
178
|
+
|
|
179
|
+
self.config.public_send(:"#{name}=", mailer_config.public_send(name))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Mailers do not use a layout by default, but one may be configured.
|
|
183
|
+
self.config.layout = mailer_config.configured?(:layout) ? mailer_config.layout : false
|
|
184
|
+
|
|
185
|
+
self.config.template = view_template if view_template
|
|
186
|
+
|
|
187
|
+
# Exposures are evaluated once, by the mailer, which passes their values to the view.
|
|
188
|
+
# The view only needs to pass each value through to the template (decorating it as
|
|
189
|
+
# required), so the procs are dropped here. Private exposures are internal to the
|
|
190
|
+
# mailer's evaluation and are not passed to the view at all.
|
|
191
|
+
view_exposures.each do |name, exposure|
|
|
192
|
+
next if exposure.private?
|
|
193
|
+
|
|
194
|
+
expose(name, **exposure.options)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
built.new
|
|
199
|
+
end
|
|
200
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|