actionmailer 4.2.11 → 5.1.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.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
- require 'abstract_controller/collector'
2
- require 'active_support/core_ext/hash/reverse_merge'
3
- require 'active_support/core_ext/array/extract_options'
1
+ require "abstract_controller/collector"
2
+ require "active_support/core_ext/hash/reverse_merge"
3
+ require "active_support/core_ext/array/extract_options"
4
4
 
5
5
  module ActionMailer
6
6
  class Collector
@@ -1,13 +1,34 @@
1
- require 'active_job'
1
+ require "active_job"
2
2
 
3
3
  module ActionMailer
4
4
  # The <tt>ActionMailer::DeliveryJob</tt> class is used when you
5
5
  # want to send emails outside of the request-response cycle.
6
+ #
7
+ # Exceptions are rescued and handled by the mailer class.
6
8
  class DeliveryJob < ActiveJob::Base # :nodoc:
7
- queue_as :mailers
9
+ queue_as { ActionMailer::Base.deliver_later_queue_name }
8
10
 
9
- def perform(mailer, mail_method, delivery_method, *args) # :nodoc:
11
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
12
+
13
+ def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
10
14
  mailer.constantize.public_send(mail_method, *args).send(delivery_method)
11
15
  end
16
+
17
+ private
18
+ # "Deserialize" the mailer class name by hand in case another argument
19
+ # (like a Global ID reference) raised DeserializationError.
20
+ def mailer_class
21
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
22
+ mailer.constantize
23
+ end
24
+ end
25
+
26
+ def handle_exception_with_mailer_class(exception)
27
+ if klass = mailer_class
28
+ klass.handle_exception exception
29
+ else
30
+ raise exception
31
+ end
32
+ end
12
33
  end
13
34
  end
@@ -1,4 +1,4 @@
1
- require 'tmpdir'
1
+ require "tmpdir"
2
2
 
3
3
  module ActionMailer
4
4
  # This module handles everything related to mail delivery, from registering
@@ -16,13 +16,16 @@ module ActionMailer
16
16
  cattr_accessor :perform_deliveries
17
17
  self.perform_deliveries = true
18
18
 
19
+ cattr_accessor :deliver_later_queue_name
20
+ self.deliver_later_queue_name = :mailers
21
+
19
22
  self.delivery_methods = {}.freeze
20
23
  self.delivery_method = :smtp
21
24
 
22
25
  add_delivery_method :smtp, Mail::SMTP,
23
26
  address: "localhost",
24
27
  port: 25,
25
- domain: 'localhost.localdomain',
28
+ domain: "localhost.localdomain",
26
29
  user_name: nil,
27
30
  password: nil,
28
31
  authentication: nil,
@@ -32,8 +35,8 @@ module ActionMailer
32
35
  location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
33
36
 
34
37
  add_delivery_method :sendmail, Mail::Sendmail,
35
- location: '/usr/sbin/sendmail',
36
- arguments: '-i'
38
+ location: "/usr/sbin/sendmail",
39
+ arguments: "-i"
37
40
 
38
41
  add_delivery_method :test, Mail::TestMailer
39
42
  end
@@ -48,15 +51,15 @@ module ActionMailer
48
51
  #
49
52
  # add_delivery_method :sendmail, Mail::Sendmail,
50
53
  # location: '/usr/sbin/sendmail',
51
- # arguments: '-i -t'
52
- def add_delivery_method(symbol, klass, default_options={})
54
+ # arguments: '-i'
55
+ def add_delivery_method(symbol, klass, default_options = {})
53
56
  class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
54
57
  send(:"#{symbol}_settings=", default_options)
55
58
  self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
56
59
  end
57
60
 
58
- def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc:
59
- method ||= self.delivery_method
61
+ def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
62
+ method ||= delivery_method
60
63
  mail.delivery_handler = self
61
64
 
62
65
  case method
@@ -1,13 +1,13 @@
1
1
  module ActionMailer
2
- # Returns the version of the currently loaded Action Mailer as a <tt>Gem::Version</tt>
2
+ # Returns the version of the currently loaded Action Mailer as a <tt>Gem::Version</tt>.
3
3
  def self.gem_version
4
4
  Gem::Version.new VERSION::STRING
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
10
- TINY = 11
8
+ MAJOR = 5
9
+ MINOR = 1
10
+ TINY = 0
11
11
  PRE = nil
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -1,17 +1,17 @@
1
- require 'base64'
1
+ require "base64"
2
2
 
3
3
  module ActionMailer
4
4
  # Implements a mailer preview interceptor that converts image tag src attributes
5
5
  # that use inline cid: style urls to data: style urls so that they are visible
6
- # when previewing a HTML email in a web browser.
6
+ # when previewing an HTML email in a web browser.
7
7
  #
8
- # This interceptor is not enabled by default, to use it just register it like any
9
- # other mailer preview interceptor:
8
+ # This interceptor is enabled by default. To disable it, delete it from the
9
+ # <tt>ActionMailer::Base.preview_interceptors</tt> array:
10
10
  #
11
- # ActionMailer::Base.register_preview_interceptor(ActionMailer::InlinePreviewInterceptor)
11
+ # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
12
12
  #
13
13
  class InlinePreviewInterceptor
14
- PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
14
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
15
15
 
16
16
  include Base64
17
17
 
@@ -26,7 +26,7 @@ module ActionMailer
26
26
  def transform! #:nodoc:
27
27
  return message if html_part.blank?
28
28
 
29
- html_source.gsub!(PATTERN) do |match|
29
+ html_part.body = html_part.decoded.gsub(PATTERN) do |match|
30
30
  if part = find_part(match[9..-2])
31
31
  %[src="#{data_url(part)}"]
32
32
  else
@@ -46,16 +46,12 @@ module ActionMailer
46
46
  @html_part ||= message.html_part
47
47
  end
48
48
 
49
- def html_source
50
- html_part.body.raw_source
51
- end
52
-
53
49
  def data_url(part)
54
50
  "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
55
51
  end
56
52
 
57
53
  def find_part(cid)
58
- message.all_parts.find{ |p| p.attachment? && p.cid == cid }
54
+ message.all_parts.find { |p| p.attachment? && p.cid == cid }
59
55
  end
60
56
  end
61
57
  end
@@ -1,14 +1,14 @@
1
- require 'active_support/log_subscriber'
1
+ require "active_support/log_subscriber"
2
2
 
3
3
  module ActionMailer
4
4
  # Implements the ActiveSupport::LogSubscriber for logging notifications when
5
- # email is delivered and received.
5
+ # email is delivered or received.
6
6
  class LogSubscriber < ActiveSupport::LogSubscriber
7
7
  # An email was delivered.
8
8
  def deliver(event)
9
9
  info do
10
- recipients = Array(event.payload[:to]).join(', ')
11
- "\nSent mail to #{recipients} (#{event.duration.round(1)}ms)"
10
+ recipients = Array(event.payload[:to]).join(", ")
11
+ "Sent mail to #{recipients} (#{event.duration.round(1)}ms)"
12
12
  end
13
13
 
14
14
  debug { event.payload[:mail] }
@@ -16,7 +16,7 @@ module ActionMailer
16
16
 
17
17
  # An email was received.
18
18
  def receive(event)
19
- info { "\nReceived mail (#{event.duration.round(1)}ms)" }
19
+ info { "Received mail (#{event.duration.round(1)}ms)" }
20
20
  debug { event.payload[:mail] }
21
21
  end
22
22
 
@@ -25,11 +25,11 @@ module ActionMailer
25
25
  debug do
26
26
  mailer = event.payload[:mailer]
27
27
  action = event.payload[:action]
28
- "\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
28
+ "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
29
29
  end
30
30
  end
31
31
 
32
- # Use the logger configured for ActionMailer::Base
32
+ # Use the logger configured for ActionMailer::Base.
33
33
  def logger
34
34
  ActionMailer::Base.logger
35
35
  end
@@ -4,7 +4,17 @@ module ActionMailer
4
4
  # attachments list.
5
5
  module MailHelper
6
6
  # Take the text and format it, indented two spaces for each line, and
7
- # wrapped at 72 columns.
7
+ # wrapped at 72 columns:
8
+ #
9
+ # text = <<-TEXT
10
+ # This is
11
+ # the paragraph.
12
+ #
13
+ # * item1 * item2
14
+ # TEXT
15
+ #
16
+ # block_format text
17
+ # # => " This is the paragraph.\n\n * item1\n * item2\n"
8
18
  def block_format(text)
9
19
  formatted = text.split(/\n\r?\n/).collect { |paragraph|
10
20
  format_paragraph(paragraph)
@@ -33,6 +43,8 @@ module ActionMailer
33
43
  end
34
44
 
35
45
  # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
46
+ # By default column length +len+ equals 72 characters and indent
47
+ # +indent+ equal two spaces.
36
48
  #
37
49
  # my_text = 'Here is a sample text with more than 40 characters'
38
50
  #
@@ -42,7 +54,7 @@ module ActionMailer
42
54
  sentences = [[]]
43
55
 
44
56
  text.split.each do |word|
45
- if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
57
+ if sentences.first.present? && (sentences.last + [word]).join(" ").length > len
46
58
  sentences << [word]
47
59
  else
48
60
  sentences.last << word
@@ -1,8 +1,6 @@
1
- require 'delegate'
2
- require 'active_support/core_ext/string/filters'
1
+ require "delegate"
3
2
 
4
3
  module ActionMailer
5
-
6
4
  # The <tt>ActionMailer::MessageDelivery</tt> class is used by
7
5
  # <tt>ActionMailer::Base</tt> when creating a new mailer.
8
6
  # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
@@ -15,25 +13,35 @@ module ActionMailer
15
13
  # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
16
14
  # Notifier.welcome(User.first).message # a Mail::Message object
17
15
  class MessageDelivery < Delegator
18
- def initialize(mailer, mail_method, *args) #:nodoc:
19
- @mailer = mailer
20
- @mail_method = mail_method
21
- @args = args
16
+ def initialize(mailer_class, action, *args) #:nodoc:
17
+ @mailer_class, @action, @args = mailer_class, action, args
18
+
19
+ # The mail is only processed if we try to call any methods on it.
20
+ # Typical usage will leave it unloaded and call deliver_later.
21
+ @processed_mailer = nil
22
+ @mail_message = nil
22
23
  end
23
24
 
25
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
24
26
  def __getobj__ #:nodoc:
25
- @obj ||= @mailer.send(:new, @mail_method, *@args).message
27
+ @mail_message ||= processed_mailer.message
26
28
  end
27
29
 
28
- def __setobj__(obj) #:nodoc:
29
- @obj = obj
30
+ # Unused except for delegator internals (dup, marshaling).
31
+ def __setobj__(mail_message) #:nodoc:
32
+ @mail_message = mail_message
30
33
  end
31
34
 
32
- # Returns the Mail::Message object
35
+ # Returns the resulting Mail::Message
33
36
  def message
34
37
  __getobj__
35
38
  end
36
39
 
40
+ # Was the delegate loaded, causing the mailer action to be processed?
41
+ def processed?
42
+ @processed_mailer || @mail_message
43
+ end
44
+
37
45
  # Enqueues the email to be delivered through Active Job. When the
38
46
  # job runs it will send the email using +deliver_now!+. That means
39
47
  # that the message will be sent bypassing checking +perform_deliveries+
@@ -48,7 +56,7 @@ module ActionMailer
48
56
  # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
49
57
  # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
50
58
  # * <tt>:queue</tt> - Enqueue the email on the specified queue
51
- def deliver_later!(options={})
59
+ def deliver_later!(options = {})
52
60
  enqueue_delivery :deliver_now!, options
53
61
  end
54
62
 
@@ -61,10 +69,10 @@ module ActionMailer
61
69
  #
62
70
  # Options:
63
71
  #
64
- # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
65
- # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
66
- # * <tt>:queue</tt> - Enqueue the email on the specified queue
67
- def deliver_later(options={})
72
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
73
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
74
+ # * <tt>:queue</tt> - Enqueue the email on the specified queue.
75
+ def deliver_later(options = {})
68
76
  enqueue_delivery :deliver_now, options
69
77
  end
70
78
 
@@ -74,7 +82,9 @@ module ActionMailer
74
82
  # Notifier.welcome(User.first).deliver_now!
75
83
  #
76
84
  def deliver_now!
77
- message.deliver!
85
+ processed_mailer.handle_exceptions do
86
+ message.deliver!
87
+ end
78
88
  end
79
89
 
80
90
  # Delivers an email:
@@ -82,34 +92,34 @@ module ActionMailer
82
92
  # Notifier.welcome(User.first).deliver_now
83
93
  #
84
94
  def deliver_now
85
- message.deliver
86
- end
87
-
88
- def deliver! #:nodoc:
89
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
90
- `#deliver!` is deprecated and will be removed in Rails 5. Use
91
- `#deliver_now!` to deliver immediately or `#deliver_later!` to
92
- deliver through Active Job.
93
- MSG
94
-
95
- deliver_now!
96
- end
97
-
98
- def deliver #:nodoc:
99
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
100
- `#deliver` is deprecated and will be removed in Rails 5. Use
101
- `#deliver_now` to deliver immediately or `#deliver_later` to
102
- deliver through Active Job.
103
- MSG
104
-
105
- deliver_now
95
+ processed_mailer.handle_exceptions do
96
+ message.deliver
97
+ end
106
98
  end
107
99
 
108
100
  private
101
+ # Returns the processed Mailer instance. We keep this instance
102
+ # on hand so we can delegate exception handling to it.
103
+ def processed_mailer
104
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
105
+ mailer.process @action, *@args
106
+ end
107
+ end
109
108
 
110
- def enqueue_delivery(delivery_method, options={})
111
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
112
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
109
+ def enqueue_delivery(delivery_method, options = {})
110
+ if processed?
111
+ ::Kernel.raise "You've accessed the message before asking to " \
112
+ "deliver it later, so you may have made local changes that would " \
113
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
114
+ "the mailer method *arguments* are passed with the delivery job! " \
115
+ "Do not access the message in any way if you mean to deliver it " \
116
+ "later. Workarounds: 1. don't touch the message before calling " \
117
+ "#deliver_later, 2. only touch the message *within your mailer " \
118
+ "method*, or 3. use a custom Active Job instead of #deliver_later."
119
+ else
120
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
121
+ ::ActionMailer::DeliveryJob.set(options).perform_later(*args)
122
+ end
113
123
  end
114
124
  end
115
125
  end
@@ -0,0 +1,152 @@
1
+ module ActionMailer
2
+ # Provides the option to parameterize mailers in order to share instance variable
3
+ # setup, processing, and common headers.
4
+ #
5
+ # Consider this example that does not use parameterization:
6
+ #
7
+ # class InvitationsMailer < ApplicationMailer
8
+ # def account_invitation(inviter, invitee)
9
+ # @account = inviter.account
10
+ # @inviter = inviter
11
+ # @invitee = invitee
12
+ #
13
+ # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
14
+ #
15
+ # mail \
16
+ # subject: subject,
17
+ # to: invitee.email_address,
18
+ # from: common_address(inviter),
19
+ # reply_to: inviter.email_address_with_name
20
+ # end
21
+ #
22
+ # def project_invitation(project, inviter, invitee)
23
+ # @account = inviter.account
24
+ # @project = project
25
+ # @inviter = inviter
26
+ # @invitee = invitee
27
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
28
+ #
29
+ # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
30
+ #
31
+ # mail \
32
+ # subject: subject,
33
+ # to: invitee.email_address,
34
+ # from: common_address(inviter),
35
+ # reply_to: inviter.email_address_with_name
36
+ # end
37
+ #
38
+ # def bulk_project_invitation(projects, inviter, invitee)
39
+ # @account = inviter.account
40
+ # @projects = projects.sort_by(&:name)
41
+ # @inviter = inviter
42
+ # @invitee = invitee
43
+ #
44
+ # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
45
+ #
46
+ # mail \
47
+ # subject: subject,
48
+ # to: invitee.email_address,
49
+ # from: common_address(inviter),
50
+ # reply_to: inviter.email_address_with_name
51
+ # end
52
+ # end
53
+ #
54
+ # InvitationsMailer.account_invitation(person_a, person_b).deliver_later
55
+ #
56
+ # Using parameterized mailers, this can be rewritten as:
57
+ #
58
+ # class InvitationsMailer < ApplicationMailer
59
+ # before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
60
+ # before_action { @account = params[:inviter].account }
61
+ #
62
+ # default to: -> { @invitee.email_address },
63
+ # from: -> { common_address(@inviter) },
64
+ # reply_to: -> { @inviter.email_address_with_name }
65
+ #
66
+ # def account_invitation
67
+ # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
68
+ # end
69
+ #
70
+ # def project_invitation
71
+ # @project = params[:project]
72
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
73
+ #
74
+ # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
75
+ # end
76
+ #
77
+ # def bulk_project_invitation
78
+ # @projects = params[:projects].sort_by(&:name)
79
+ #
80
+ # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
81
+ # end
82
+ # end
83
+ #
84
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
85
+ module Parameterized
86
+ extend ActiveSupport::Concern
87
+
88
+ included do
89
+ attr_accessor :params
90
+ end
91
+
92
+ module ClassMethods
93
+ # Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
94
+ #
95
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
96
+ #
97
+ # See Parameterized documentation for full example.
98
+ def with(params)
99
+ ActionMailer::Parameterized::Mailer.new(self, params)
100
+ end
101
+ end
102
+
103
+ class Mailer # :nodoc:
104
+ def initialize(mailer, params)
105
+ @mailer, @params = mailer, params
106
+ end
107
+
108
+ private
109
+ def method_missing(method_name, *args)
110
+ if @mailer.action_methods.include?(method_name.to_s)
111
+ ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
112
+ else
113
+ super
114
+ end
115
+ end
116
+
117
+ def respond_to_missing?(method, include_all = false)
118
+ @mailer.respond_to?(method, include_all)
119
+ end
120
+ end
121
+
122
+ class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
123
+ def initialize(mailer_class, action, params, *args)
124
+ super(mailer_class, action, *args)
125
+ @params = params
126
+ end
127
+
128
+ private
129
+ def processed_mailer
130
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
131
+ mailer.params = @params
132
+ mailer.process @action, *@args
133
+ end
134
+ end
135
+
136
+ def enqueue_delivery(delivery_method, options = {})
137
+ if processed?
138
+ super
139
+ else
140
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
141
+ ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args)
142
+ end
143
+ end
144
+ end
145
+
146
+ class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
147
+ def perform(mailer, mail_method, delivery_method, params, *args)
148
+ mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
149
+ end
150
+ end
151
+ end
152
+ end