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.

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