actionmailer 3.2.9 → 6.1.7.6

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,172 @@
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
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
119
+
120
+ def respond_to_missing?(method, include_all = false)
121
+ @mailer.respond_to?(method, include_all)
122
+ end
123
+ end
124
+
125
+ class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
126
+ def perform(mailer, mail_method, delivery_method, params, *args)
127
+ mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
128
+ end
129
+ ruby2_keywords(:perform) if respond_to?(:ruby2_keywords, true)
130
+ end
131
+
132
+ class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
133
+ def initialize(mailer_class, action, params, *args)
134
+ super(mailer_class, action, *args)
135
+ @params = params
136
+ end
137
+ ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
138
+
139
+ private
140
+ def processed_mailer
141
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
142
+ mailer.params = @params
143
+ mailer.process @action, *@args
144
+ end
145
+ end
146
+
147
+ def enqueue_delivery(delivery_method, options = {})
148
+ if processed?
149
+ super
150
+ else
151
+ job = delivery_job_class
152
+
153
+ if job <= MailDeliveryJob
154
+ job.set(options).perform_later(
155
+ @mailer_class.name, @action.to_s, delivery_method.to_s, params: @params, args: @args)
156
+ else
157
+ job.set(options).perform_later(
158
+ @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args)
159
+ end
160
+ end
161
+ end
162
+
163
+ def delivery_job_class
164
+ if @mailer_class.delivery_job <= MailDeliveryJob
165
+ @mailer_class.delivery_job
166
+ else
167
+ Parameterized::DeliveryJob
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,142 @@
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
+ # Unregister one or more previously registered Interceptors.
35
+ def unregister_preview_interceptors(*interceptors)
36
+ interceptors.flatten.compact.each { |interceptor| unregister_preview_interceptor(interceptor) }
37
+ end
38
+
39
+ # Register an Interceptor which will be called before mail is previewed.
40
+ # Either a class or a string can be passed in as the Interceptor. If a
41
+ # string is passed in it will be constantized.
42
+ def register_preview_interceptor(interceptor)
43
+ preview_interceptor = interceptor_class_for(interceptor)
44
+
45
+ unless preview_interceptors.include?(preview_interceptor)
46
+ preview_interceptors << preview_interceptor
47
+ end
48
+ end
49
+
50
+ # Unregister a previously registered Interceptor.
51
+ # Either a class or a string can be passed in as the Interceptor. If a
52
+ # string is passed in it will be constantized.
53
+ def unregister_preview_interceptor(interceptor)
54
+ preview_interceptors.delete(interceptor_class_for(interceptor))
55
+ end
56
+
57
+ private
58
+ def interceptor_class_for(interceptor)
59
+ case interceptor
60
+ when String, Symbol
61
+ interceptor.to_s.camelize.constantize
62
+ else
63
+ interceptor
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ class Preview
70
+ extend ActiveSupport::DescendantsTracker
71
+
72
+ attr_reader :params
73
+
74
+ def initialize(params = {})
75
+ @params = params
76
+ end
77
+
78
+ class << self
79
+ # Returns all mailer preview classes.
80
+ def all
81
+ load_previews if descendants.empty?
82
+ descendants
83
+ end
84
+
85
+ # Returns the mail object for the given email name. The registered preview
86
+ # interceptors will be informed so that they can transform the message
87
+ # as they would if the mail was actually being delivered.
88
+ def call(email, params = {})
89
+ preview = new(params)
90
+ message = preview.public_send(email)
91
+ inform_preview_interceptors(message)
92
+ message
93
+ end
94
+
95
+ # Returns all of the available email previews.
96
+ def emails
97
+ public_instance_methods(false).map(&:to_s).sort
98
+ end
99
+
100
+ # Returns +true+ if the email exists.
101
+ def email_exists?(email)
102
+ emails.include?(email)
103
+ end
104
+
105
+ # Returns +true+ if the preview exists.
106
+ def exists?(preview)
107
+ all.any? { |p| p.preview_name == preview }
108
+ end
109
+
110
+ # Find a mailer preview by its underscored class name.
111
+ def find(preview)
112
+ all.find { |p| p.preview_name == preview }
113
+ end
114
+
115
+ # Returns the underscored name of the mailer preview without the suffix.
116
+ def preview_name
117
+ name.delete_suffix("Preview").underscore
118
+ end
119
+
120
+ private
121
+ def load_previews
122
+ if preview_path
123
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
124
+ end
125
+ end
126
+
127
+ def preview_path
128
+ Base.preview_path
129
+ end
130
+
131
+ def show_previews
132
+ Base.show_previews
133
+ end
134
+
135
+ def inform_preview_interceptors(message)
136
+ Base.preview_interceptors.each do |interceptor|
137
+ interceptor.previewing_email(message)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,10 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/railtie"
1
4
  require "action_mailer"
2
5
  require "rails"
3
6
  require "abstract_controller/railties/routes_helpers"
4
7
 
5
8
  module ActionMailer
6
- class Railtie < Rails::Railtie
9
+ class Railtie < Rails::Railtie # :nodoc:
7
10
  config.action_mailer = ActiveSupport::OrderedOptions.new
11
+ config.eager_load_namespaces << ActionMailer
8
12
 
9
13
  initializer "action_mailer.logger" do
10
14
  ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
@@ -17,21 +21,44 @@ module ActionMailer
17
21
  options.assets_dir ||= paths["public"].first
18
22
  options.javascripts_dir ||= paths["public/javascripts"].first
19
23
  options.stylesheets_dir ||= paths["public/stylesheets"].first
24
+ options.show_previews = Rails.env.development? if options.show_previews.nil?
25
+ options.cache_store ||= Rails.cache
26
+
27
+ if options.show_previews
28
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
29
+ end
20
30
 
21
31
  # make sure readers methods get compiled
22
- options.asset_path ||= app.config.asset_path
23
32
  options.asset_host ||= app.config.asset_host
24
33
  options.relative_url_root ||= app.config.relative_url_root
25
34
 
26
35
  ActiveSupport.on_load(:action_mailer) do
27
36
  include AbstractController::UrlFor
28
- extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
37
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
29
38
  include app.routes.mounted_helpers
30
39
 
31
40
  register_interceptors(options.delete(:interceptors))
41
+ register_preview_interceptors(options.delete(:preview_interceptors))
32
42
  register_observers(options.delete(:observers))
33
43
 
34
- options.each { |k,v| send("#{k}=", v) }
44
+ if delivery_job = options.delete(:delivery_job)
45
+ self.delivery_job = delivery_job.constantize
46
+ end
47
+
48
+ options.each { |k, v| send("#{k}=", v) }
49
+ end
50
+
51
+ ActiveSupport.on_load(:action_dispatch_integration_test) do
52
+ include ActionMailer::TestHelper
53
+ include ActionMailer::TestCase::ClearTestDeliveries
54
+ end
55
+ end
56
+
57
+ initializer "action_mailer.set_autoload_paths" do |app|
58
+ options = app.config.action_mailer
59
+
60
+ if options.show_previews && options.preview_path
61
+ ActiveSupport::Dependencies.autoload_paths << options.preview_path
35
62
  end
36
63
  end
37
64
 
@@ -40,5 +67,22 @@ module ActionMailer
40
67
  config.compile_methods! if config.respond_to?(:compile_methods!)
41
68
  end
42
69
  end
70
+
71
+ initializer "action_mailer.eager_load_actions" do
72
+ ActiveSupport.on_load(:after_initialize) do
73
+ ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load
74
+ end
75
+ end
76
+
77
+ config.after_initialize do |app|
78
+ options = app.config.action_mailer
79
+
80
+ if options.show_previews
81
+ app.routes.prepend do
82
+ get "/rails/mailers" => "rails/mailers#index", internal: true
83
+ get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
84
+ end
85
+ end
86
+ end
43
87
  end
44
88
  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
@@ -1,24 +1,48 @@
1
- require 'active_support/core_ext/class/attribute'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/test_case"
4
+ require "rails-dom-testing"
2
5
 
3
6
  module ActionMailer
4
7
  class NonInferrableMailerError < ::StandardError
5
8
  def initialize(name)
6
- super "Unable to determine the mailer to test from #{name}. " +
7
- "You'll need to specify it using tests YourMailer in your " +
9
+ super "Unable to determine the mailer to test from #{name}. " \
10
+ "You'll need to specify it using tests YourMailer in your " \
8
11
  "test case definition"
9
12
  end
10
13
  end
11
14
 
12
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
+ def clear_test_deliveries
26
+ if ActionMailer::Base.delivery_method == :test
27
+ ActionMailer::Base.deliveries.clear
28
+ end
29
+ end
30
+ end
31
+
13
32
  module Behavior
14
33
  extend ActiveSupport::Concern
15
34
 
35
+ include ActiveSupport::Testing::ConstantLookup
16
36
  include TestHelper
37
+ include Rails::Dom::Testing::Assertions::SelectorAssertions
38
+ include Rails::Dom::Testing::Assertions::DomAssertions
17
39
 
18
40
  included do
19
41
  class_attribute :_mailer_class
20
42
  setup :initialize_test_deliveries
21
43
  setup :set_expected_mail
44
+ teardown :restore_test_deliveries
45
+ ActiveSupport.run_load_hooks(:action_mailer_test_case, self)
22
46
  end
23
47
 
24
48
  module ClassMethods
@@ -34,7 +58,7 @@ module ActionMailer
34
58
  end
35
59
 
36
60
  def mailer_class
37
- if mailer = self._mailer_class
61
+ if mailer = _mailer_class
38
62
  mailer
39
63
  else
40
64
  tests determine_default_mailer(name)
@@ -42,28 +66,43 @@ module ActionMailer
42
66
  end
43
67
 
44
68
  def determine_default_mailer(name)
45
- name.sub(/Test$/, '').constantize
46
- rescue NameError
47
- raise NonInferrableMailerError.new(name)
69
+ mailer = determine_constant_from_test_name(name) do |constant|
70
+ Class === constant && constant < ActionMailer::Base
71
+ end
72
+ raise NonInferrableMailerError.new(name) if mailer.nil?
73
+ mailer
48
74
  end
49
75
  end
50
76
 
51
- protected
52
-
77
+ private
53
78
  def initialize_test_deliveries
54
- ActionMailer::Base.delivery_method = :test
79
+ set_delivery_method :test
80
+ @old_perform_deliveries = ActionMailer::Base.perform_deliveries
55
81
  ActionMailer::Base.perform_deliveries = true
56
82
  ActionMailer::Base.deliveries.clear
57
83
  end
58
84
 
85
+ def restore_test_deliveries
86
+ restore_delivery_method
87
+ ActionMailer::Base.perform_deliveries = @old_perform_deliveries
88
+ end
89
+
90
+ def set_delivery_method(method)
91
+ @old_delivery_method = ActionMailer::Base.delivery_method
92
+ ActionMailer::Base.delivery_method = method
93
+ end
94
+
95
+ def restore_delivery_method
96
+ ActionMailer::Base.deliveries.clear
97
+ ActionMailer::Base.delivery_method = @old_delivery_method
98
+ end
99
+
59
100
  def set_expected_mail
60
101
  @expected = Mail.new
61
102
  @expected.content_type ["text", "plain", { "charset" => charset }]
62
- @expected.mime_version = '1.0'
103
+ @expected.mime_version = "1.0"
63
104
  end
64
105
 
65
- private
66
-
67
106
  def charset
68
107
  "UTF-8"
69
108
  end
@@ -73,7 +112,7 @@ module ActionMailer
73
112
  end
74
113
 
75
114
  def read_fixture(action)
76
- IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
115
+ IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
77
116
  end
78
117
  end
79
118