actionmailer 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionmailer might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +54 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +175 -0
- data/lib/action_mailer.rb +69 -0
- data/lib/action_mailer/base.rb +991 -0
- data/lib/action_mailer/collector.rb +32 -0
- data/lib/action_mailer/delivery_job.rb +36 -0
- data/lib/action_mailer/delivery_methods.rb +82 -0
- data/lib/action_mailer/gem_version.rb +17 -0
- data/lib/action_mailer/inline_preview_interceptor.rb +59 -0
- data/lib/action_mailer/log_subscriber.rb +41 -0
- data/lib/action_mailer/mail_helper.rb +72 -0
- data/lib/action_mailer/message_delivery.rb +144 -0
- data/lib/action_mailer/parameterized.rb +154 -0
- data/lib/action_mailer/preview.rb +126 -0
- data/lib/action_mailer/railtie.rb +82 -0
- data/lib/action_mailer/rescuable.rb +29 -0
- data/lib/action_mailer/test_case.rb +123 -0
- data/lib/action_mailer/test_helper.rb +144 -0
- data/lib/action_mailer/version.rb +11 -0
- data/lib/rails/generators/mailer/USAGE +17 -0
- data/lib/rails/generators/mailer/mailer_generator.rb +38 -0
- data/lib/rails/generators/mailer/templates/application_mailer.rb.tt +6 -0
- data/lib/rails/generators/mailer/templates/mailer.rb.tt +17 -0
- metadata +146 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMailer
|
4
|
+
# Provides the option to parameterize mailers in order to share instance variable
|
5
|
+
# setup, processing, and common headers.
|
6
|
+
#
|
7
|
+
# Consider this example that does not use parameterization:
|
8
|
+
#
|
9
|
+
# class InvitationsMailer < ApplicationMailer
|
10
|
+
# def account_invitation(inviter, invitee)
|
11
|
+
# @account = inviter.account
|
12
|
+
# @inviter = inviter
|
13
|
+
# @invitee = invitee
|
14
|
+
#
|
15
|
+
# subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
|
16
|
+
#
|
17
|
+
# mail \
|
18
|
+
# subject: subject,
|
19
|
+
# to: invitee.email_address,
|
20
|
+
# from: common_address(inviter),
|
21
|
+
# reply_to: inviter.email_address_with_name
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def project_invitation(project, inviter, invitee)
|
25
|
+
# @account = inviter.account
|
26
|
+
# @project = project
|
27
|
+
# @inviter = inviter
|
28
|
+
# @invitee = invitee
|
29
|
+
# @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
|
30
|
+
#
|
31
|
+
# subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
|
32
|
+
#
|
33
|
+
# mail \
|
34
|
+
# subject: subject,
|
35
|
+
# to: invitee.email_address,
|
36
|
+
# from: common_address(inviter),
|
37
|
+
# reply_to: inviter.email_address_with_name
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def bulk_project_invitation(projects, inviter, invitee)
|
41
|
+
# @account = inviter.account
|
42
|
+
# @projects = projects.sort_by(&:name)
|
43
|
+
# @inviter = inviter
|
44
|
+
# @invitee = invitee
|
45
|
+
#
|
46
|
+
# subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
|
47
|
+
#
|
48
|
+
# mail \
|
49
|
+
# subject: subject,
|
50
|
+
# to: invitee.email_address,
|
51
|
+
# from: common_address(inviter),
|
52
|
+
# reply_to: inviter.email_address_with_name
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# InvitationsMailer.account_invitation(person_a, person_b).deliver_later
|
57
|
+
#
|
58
|
+
# Using parameterized mailers, this can be rewritten as:
|
59
|
+
#
|
60
|
+
# class InvitationsMailer < ApplicationMailer
|
61
|
+
# before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
|
62
|
+
# before_action { @account = params[:inviter].account }
|
63
|
+
#
|
64
|
+
# default to: -> { @invitee.email_address },
|
65
|
+
# from: -> { common_address(@inviter) },
|
66
|
+
# reply_to: -> { @inviter.email_address_with_name }
|
67
|
+
#
|
68
|
+
# def account_invitation
|
69
|
+
# mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def project_invitation
|
73
|
+
# @project = params[:project]
|
74
|
+
# @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
|
75
|
+
#
|
76
|
+
# mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def bulk_project_invitation
|
80
|
+
# @projects = params[:projects].sort_by(&:name)
|
81
|
+
#
|
82
|
+
# mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
|
87
|
+
module Parameterized
|
88
|
+
extend ActiveSupport::Concern
|
89
|
+
|
90
|
+
included do
|
91
|
+
attr_accessor :params
|
92
|
+
end
|
93
|
+
|
94
|
+
module ClassMethods
|
95
|
+
# Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
|
96
|
+
#
|
97
|
+
# InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
|
98
|
+
#
|
99
|
+
# See Parameterized documentation for full example.
|
100
|
+
def with(params)
|
101
|
+
ActionMailer::Parameterized::Mailer.new(self, params)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Mailer # :nodoc:
|
106
|
+
def initialize(mailer, params)
|
107
|
+
@mailer, @params = mailer, params
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def method_missing(method_name, *args)
|
112
|
+
if @mailer.action_methods.include?(method_name.to_s)
|
113
|
+
ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def respond_to_missing?(method, include_all = false)
|
120
|
+
@mailer.respond_to?(method, include_all)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
|
125
|
+
def initialize(mailer_class, action, params, *args)
|
126
|
+
super(mailer_class, action, *args)
|
127
|
+
@params = params
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
def processed_mailer
|
132
|
+
@processed_mailer ||= @mailer_class.new.tap do |mailer|
|
133
|
+
mailer.params = @params
|
134
|
+
mailer.process @action, *@args
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def enqueue_delivery(delivery_method, options = {})
|
139
|
+
if processed?
|
140
|
+
super
|
141
|
+
else
|
142
|
+
args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
|
143
|
+
ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
|
149
|
+
def perform(mailer, mail_method, delivery_method, params, *args)
|
150
|
+
mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/descendants_tracker"
|
4
|
+
|
5
|
+
module ActionMailer
|
6
|
+
module Previews #:nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
# Set the location of mailer previews through app configuration:
|
11
|
+
#
|
12
|
+
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
|
13
|
+
#
|
14
|
+
mattr_accessor :preview_path, instance_writer: false
|
15
|
+
|
16
|
+
# Enable or disable mailer previews through app configuration:
|
17
|
+
#
|
18
|
+
# config.action_mailer.show_previews = true
|
19
|
+
#
|
20
|
+
# Defaults to +true+ for development environment
|
21
|
+
#
|
22
|
+
mattr_accessor :show_previews, instance_writer: false
|
23
|
+
|
24
|
+
# :nodoc:
|
25
|
+
mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# Register one or more Interceptors which will be called before mail is previewed.
|
30
|
+
def register_preview_interceptors(*interceptors)
|
31
|
+
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Register an Interceptor which will be called before mail is previewed.
|
35
|
+
# Either a class or a string can be passed in as the Interceptor. If a
|
36
|
+
# string is passed in it will be constantized.
|
37
|
+
def register_preview_interceptor(interceptor)
|
38
|
+
preview_interceptor = \
|
39
|
+
case interceptor
|
40
|
+
when String, Symbol
|
41
|
+
interceptor.to_s.camelize.constantize
|
42
|
+
else
|
43
|
+
interceptor
|
44
|
+
end
|
45
|
+
|
46
|
+
unless preview_interceptors.include?(preview_interceptor)
|
47
|
+
preview_interceptors << preview_interceptor
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Preview
|
54
|
+
extend ActiveSupport::DescendantsTracker
|
55
|
+
|
56
|
+
attr_reader :params
|
57
|
+
|
58
|
+
def initialize(params = {})
|
59
|
+
@params = params
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
# Returns all mailer preview classes.
|
64
|
+
def all
|
65
|
+
load_previews if descendants.empty?
|
66
|
+
descendants
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the mail object for the given email name. The registered preview
|
70
|
+
# interceptors will be informed so that they can transform the message
|
71
|
+
# as they would if the mail was actually being delivered.
|
72
|
+
def call(email, params = {})
|
73
|
+
preview = new(params)
|
74
|
+
message = preview.public_send(email)
|
75
|
+
inform_preview_interceptors(message)
|
76
|
+
message
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns all of the available email previews.
|
80
|
+
def emails
|
81
|
+
public_instance_methods(false).map(&:to_s).sort
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns +true+ if the email exists.
|
85
|
+
def email_exists?(email)
|
86
|
+
emails.include?(email)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns +true+ if the preview exists.
|
90
|
+
def exists?(preview)
|
91
|
+
all.any? { |p| p.preview_name == preview }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Find a mailer preview by its underscored class name.
|
95
|
+
def find(preview)
|
96
|
+
all.find { |p| p.preview_name == preview }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the underscored name of the mailer preview without the suffix.
|
100
|
+
def preview_name
|
101
|
+
name.sub(/Preview$/, "").underscore
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def load_previews
|
106
|
+
if preview_path
|
107
|
+
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def preview_path
|
112
|
+
Base.preview_path
|
113
|
+
end
|
114
|
+
|
115
|
+
def show_previews
|
116
|
+
Base.show_previews
|
117
|
+
end
|
118
|
+
|
119
|
+
def inform_preview_interceptors(message)
|
120
|
+
Base.preview_interceptors.each do |interceptor|
|
121
|
+
interceptor.previewing_email(message)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_job/railtie"
|
4
|
+
require "action_mailer"
|
5
|
+
require "rails"
|
6
|
+
require "abstract_controller/railties/routes_helpers"
|
7
|
+
|
8
|
+
module ActionMailer
|
9
|
+
class Railtie < Rails::Railtie # :nodoc:
|
10
|
+
config.action_mailer = ActiveSupport::OrderedOptions.new
|
11
|
+
config.eager_load_namespaces << ActionMailer
|
12
|
+
|
13
|
+
initializer "action_mailer.logger" do
|
14
|
+
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer "action_mailer.set_configs" do |app|
|
18
|
+
paths = app.config.paths
|
19
|
+
options = app.config.action_mailer
|
20
|
+
|
21
|
+
if app.config.force_ssl
|
22
|
+
options.default_url_options ||= {}
|
23
|
+
options.default_url_options[:protocol] ||= "https"
|
24
|
+
end
|
25
|
+
|
26
|
+
options.assets_dir ||= paths["public"].first
|
27
|
+
options.javascripts_dir ||= paths["public/javascripts"].first
|
28
|
+
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
29
|
+
options.show_previews = Rails.env.development? if options.show_previews.nil?
|
30
|
+
options.cache_store ||= Rails.cache
|
31
|
+
|
32
|
+
if options.show_previews
|
33
|
+
options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# make sure readers methods get compiled
|
37
|
+
options.asset_host ||= app.config.asset_host
|
38
|
+
options.relative_url_root ||= app.config.relative_url_root
|
39
|
+
|
40
|
+
ActiveSupport.on_load(:action_mailer) do
|
41
|
+
include AbstractController::UrlFor
|
42
|
+
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
|
43
|
+
include app.routes.mounted_helpers
|
44
|
+
|
45
|
+
register_interceptors(options.delete(:interceptors))
|
46
|
+
register_preview_interceptors(options.delete(:preview_interceptors))
|
47
|
+
register_observers(options.delete(:observers))
|
48
|
+
|
49
|
+
options.each { |k, v| send("#{k}=", v) }
|
50
|
+
end
|
51
|
+
|
52
|
+
ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries }
|
53
|
+
end
|
54
|
+
|
55
|
+
initializer "action_mailer.compile_config_methods" do
|
56
|
+
ActiveSupport.on_load(:action_mailer) do
|
57
|
+
config.compile_methods! if config.respond_to?(:compile_methods!)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
initializer "action_mailer.eager_load_actions" do
|
62
|
+
ActiveSupport.on_load(:after_initialize) do
|
63
|
+
ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
config.after_initialize do |app|
|
68
|
+
options = app.config.action_mailer
|
69
|
+
|
70
|
+
if options.show_previews
|
71
|
+
app.routes.prepend do
|
72
|
+
get "/rails/mailers" => "rails/mailers#index", internal: true
|
73
|
+
get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
|
74
|
+
end
|
75
|
+
|
76
|
+
if options.preview_path
|
77
|
+
ActiveSupport::Dependencies.autoload_paths << options.preview_path
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMailer #:nodoc:
|
4
|
+
# Provides +rescue_from+ for mailers. Wraps mailer action processing,
|
5
|
+
# mail job processing, and mail delivery.
|
6
|
+
module Rescuable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveSupport::Rescuable
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def handle_exception(exception) #:nodoc:
|
12
|
+
rescue_with_handler(exception) || raise(exception)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_exceptions #:nodoc:
|
17
|
+
yield
|
18
|
+
rescue => exception
|
19
|
+
rescue_with_handler(exception) || raise
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def process(*)
|
24
|
+
handle_exceptions do
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/test_case"
|
4
|
+
require "rails-dom-testing"
|
5
|
+
|
6
|
+
module ActionMailer
|
7
|
+
class NonInferrableMailerError < ::StandardError
|
8
|
+
def initialize(name)
|
9
|
+
super "Unable to determine the mailer to test from #{name}. " \
|
10
|
+
"You'll need to specify it using tests YourMailer in your " \
|
11
|
+
"test case definition"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestCase < ActiveSupport::TestCase
|
16
|
+
module ClearTestDeliveries
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
setup :clear_test_deliveries
|
21
|
+
teardown :clear_test_deliveries
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def clear_test_deliveries
|
27
|
+
if ActionMailer::Base.delivery_method == :test
|
28
|
+
ActionMailer::Base.deliveries.clear
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Behavior
|
34
|
+
extend ActiveSupport::Concern
|
35
|
+
|
36
|
+
include ActiveSupport::Testing::ConstantLookup
|
37
|
+
include TestHelper
|
38
|
+
include Rails::Dom::Testing::Assertions::SelectorAssertions
|
39
|
+
include Rails::Dom::Testing::Assertions::DomAssertions
|
40
|
+
|
41
|
+
included do
|
42
|
+
class_attribute :_mailer_class
|
43
|
+
setup :initialize_test_deliveries
|
44
|
+
setup :set_expected_mail
|
45
|
+
teardown :restore_test_deliveries
|
46
|
+
ActiveSupport.run_load_hooks(:action_mailer_test_case, self)
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
def tests(mailer)
|
51
|
+
case mailer
|
52
|
+
when String, Symbol
|
53
|
+
self._mailer_class = mailer.to_s.camelize.constantize
|
54
|
+
when Module
|
55
|
+
self._mailer_class = mailer
|
56
|
+
else
|
57
|
+
raise NonInferrableMailerError.new(mailer)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def mailer_class
|
62
|
+
if mailer = _mailer_class
|
63
|
+
mailer
|
64
|
+
else
|
65
|
+
tests determine_default_mailer(name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def determine_default_mailer(name)
|
70
|
+
mailer = determine_constant_from_test_name(name) do |constant|
|
71
|
+
Class === constant && constant < ActionMailer::Base
|
72
|
+
end
|
73
|
+
raise NonInferrableMailerError.new(name) if mailer.nil?
|
74
|
+
mailer
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def initialize_test_deliveries
|
81
|
+
set_delivery_method :test
|
82
|
+
@old_perform_deliveries = ActionMailer::Base.perform_deliveries
|
83
|
+
ActionMailer::Base.perform_deliveries = true
|
84
|
+
ActionMailer::Base.deliveries.clear
|
85
|
+
end
|
86
|
+
|
87
|
+
def restore_test_deliveries
|
88
|
+
restore_delivery_method
|
89
|
+
ActionMailer::Base.perform_deliveries = @old_perform_deliveries
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_delivery_method(method)
|
93
|
+
@old_delivery_method = ActionMailer::Base.delivery_method
|
94
|
+
ActionMailer::Base.delivery_method = method
|
95
|
+
end
|
96
|
+
|
97
|
+
def restore_delivery_method
|
98
|
+
ActionMailer::Base.deliveries.clear
|
99
|
+
ActionMailer::Base.delivery_method = @old_delivery_method
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_expected_mail
|
103
|
+
@expected = Mail.new
|
104
|
+
@expected.content_type ["text", "plain", { "charset" => charset }]
|
105
|
+
@expected.mime_version = "1.0"
|
106
|
+
end
|
107
|
+
|
108
|
+
def charset
|
109
|
+
"UTF-8"
|
110
|
+
end
|
111
|
+
|
112
|
+
def encode(subject)
|
113
|
+
Mail::Encodings.q_value_encode(subject, charset)
|
114
|
+
end
|
115
|
+
|
116
|
+
def read_fixture(action)
|
117
|
+
IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
include Behavior
|
122
|
+
end
|
123
|
+
end
|