diff options
Diffstat (limited to 'actionmailer/lib')
-rw-r--r-- | actionmailer/lib/action_mailer.rb | 10 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/base.rb | 73 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_job.rb | 8 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/gem_version.rb | 6 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/inline_preview_interceptor.rb | 6 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/log_subscriber.rb | 8 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/mail_delivery_job.rb | 38 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/message_delivery.rb | 36 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/parameterized.rb | 31 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/preview.rb | 27 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/railtie.rb | 25 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/test_helper.rb | 35 | ||||
-rw-r--r-- | actionmailer/lib/rails/generators/mailer/mailer_generator.rb | 2 |
13 files changed, 232 insertions, 73 deletions
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index ade3fe39a2..953c5fb508 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2004-2017 David Heinemeier Hansson +# Copyright (c) 2004-2019 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -52,6 +52,14 @@ module ActionMailer autoload :TestHelper autoload :MessageDelivery autoload :DeliveryJob + autoload :MailDeliveryJob + + def self.eager_load! + super + + require "mail" + Mail.eager_autoload! + end end autoload :Mime, "action_dispatch/http/mime_type" diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index eb8ae59533..033a7f5b9e 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -475,11 +475,21 @@ module ActionMailer observers.flatten.compact.each { |observer| register_observer(observer) } end + # Unregister one or more previously registered Observers. + def unregister_observers(*observers) + observers.flatten.compact.each { |observer| unregister_observer(observer) } + end + # Register one or more Interceptors which will be called before mail is sent. def register_interceptors(*interceptors) interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) } end + # Unregister one or more previously registered Interceptors. + def unregister_interceptors(*interceptors) + interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) } + end + # Register an Observer which will be notified when mail is delivered. # Either a class, string or symbol can be passed in as the Observer. # If a string or symbol is passed in it will be camelized and constantized. @@ -487,6 +497,13 @@ module ActionMailer Mail.register_observer(observer_class_for(observer)) end + # Unregister a previously registered Observer. + # Either a class, string or symbol can be passed in as the Observer. + # If a string or symbol is passed in it will be camelized and constantized. + def unregister_observer(observer) + Mail.unregister_observer(observer_class_for(observer)) + end + # Register an Interceptor which will be called before mail is sent. # Either a class, string or symbol can be passed in as the Interceptor. # If a string or symbol is passed in it will be camelized and constantized. @@ -494,6 +511,13 @@ module ActionMailer Mail.register_interceptor(observer_class_for(interceptor)) end + # Unregister a previously registered Interceptor. + # Either a class, string or symbol can be passed in as the Interceptor. + # If a string or symbol is passed in it will be camelized and constantized. + def unregister_interceptor(interceptor) + Mail.unregister_interceptor(observer_class_for(interceptor)) + end + def observer_class_for(value) # :nodoc: case value when String, Symbol @@ -541,6 +565,11 @@ module ActionMailer # end # end def receive(raw_mail) + ActiveSupport::Deprecation.warn(<<~MESSAGE.squish) + ActionMailer::Base.receive is deprecated and will be removed in Rails 6.1. + Use Action Mailbox to process inbound email. + MESSAGE + ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload| mail = Mail.new(raw_mail) set_payload_for_mail(payload, mail) @@ -564,15 +593,16 @@ module ActionMailer private def set_payload_for_mail(payload, mail) - payload[:mailer] = name - payload[:message_id] = mail.message_id - payload[:subject] = mail.subject - payload[:to] = mail.to - payload[:from] = mail.from - payload[:bcc] = mail.bcc if mail.bcc.present? - payload[:cc] = mail.cc if mail.cc.present? - payload[:date] = mail.date - payload[:mail] = mail.encoded + payload[:mail] = mail.encoded + payload[:mailer] = name + payload[:message_id] = mail.message_id + payload[:subject] = mail.subject + payload[:to] = mail.to + payload[:from] = mail.from + payload[:bcc] = mail.bcc if mail.bcc.present? + payload[:cc] = mail.cc if mail.cc.present? + payload[:date] = mail.date + payload[:perform_deliveries] = mail.perform_deliveries end def method_missing(method_name, *args) @@ -889,7 +919,7 @@ module ActionMailer default_values = self.class.default.map do |key, value| [ key, - value.is_a?(Proc) ? instance_exec(&value) : value + compute_default(value) ] end.to_h @@ -898,6 +928,16 @@ module ActionMailer headers_with_defaults end + def compute_default(value) + return value unless value.is_a?(Proc) + + if value.arity == 1 + instance_exec(self, &value) + else + instance_exec(&value) + end + end + def assign_headers_to_message(message, headers) assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path, :delivery_method, :delivery_method_options) @@ -906,9 +946,7 @@ module ActionMailer def collect_responses(headers) if block_given? - collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } - yield(collector) - collector.responses + collect_responses_from_block(headers, &Proc.new) elsif headers[:body] collect_responses_from_text(headers) else @@ -916,6 +954,13 @@ module ActionMailer end end + def collect_responses_from_block(headers) + templates_name = headers[:template_name] || action_name + collector = ActionMailer::Collector.new(lookup_context) { render(templates_name) } + yield(collector) + collector.responses + end + def collect_responses_from_text(headers) [{ body: headers.delete(:body), @@ -973,7 +1018,7 @@ module ActionMailer end def instrument_name - "action_mailer".freeze + "action_mailer" end ActiveSupport.run_load_hooks(:action_mailer, self) diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index 40f26d8ad1..f228006920 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -12,6 +12,14 @@ module ActionMailer rescue_from StandardError, with: :handle_exception_with_mailer_class + before_perform do + ActiveSupport::Deprecation.warn <<~MSG.squish + Sending mail with DeliveryJob and Parameterized::DeliveryJob + is deprecated and will be removed in Rails 6.1. + Please use MailDeliveryJob instead. + MSG + end + def perform(mailer, mail_method, delivery_method, *args) #:nodoc: mailer.constantize.public_send(mail_method, *args).send(delivery_method) end diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index 6a7dd0a212..4cab52d1f3 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -7,10 +7,10 @@ module ActionMailer end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb index 4bef4a58d3..2b97ac5b94 100644 --- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb +++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb @@ -4,7 +4,7 @@ require "base64" module ActionMailer # Implements a mailer preview interceptor that converts image tag src attributes - # that use inline cid: style urls to data: style urls so that they are visible + # that use inline cid: style URLs to data: style URLs so that they are visible # when previewing an HTML email in a web browser. # # This interceptor is enabled by default. To disable it, delete it from the @@ -40,9 +40,7 @@ module ActionMailer end private - def message - @message - end + attr_reader :message def html_part @html_part ||= message.html_part diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index 87cfbfff28..26910f20f0 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -9,8 +9,12 @@ module ActionMailer # An email was delivered. def deliver(event) info do - recipients = Array(event.payload[:to]).join(", ") - "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" + perform_deliveries = event.payload[:perform_deliveries] + if perform_deliveries + "Delivered mail #{event.payload[:message_id]} (#{event.duration.round(1)}ms)" + else + "Skipped delivery of mail #{event.payload[:message_id]} as `perform_deliveries` is false" + end end debug { event.payload[:mail] } diff --git a/actionmailer/lib/action_mailer/mail_delivery_job.rb b/actionmailer/lib/action_mailer/mail_delivery_job.rb new file mode 100644 index 0000000000..93778edfce --- /dev/null +++ b/actionmailer/lib/action_mailer/mail_delivery_job.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_job" + +module ActionMailer + # The <tt>ActionMailer::NewDeliveryJob</tt> class is used when you + # want to send emails outside of the request-response cycle. It supports + # sending either parameterized or normal mail. + # + # Exceptions are rescued and handled by the mailer class. + class MailDeliveryJob < ActiveJob::Base # :nodoc: + queue_as { ActionMailer::Base.deliver_later_queue_name } + + rescue_from StandardError, with: :handle_exception_with_mailer_class + + def perform(mailer, mail_method, delivery_method, args:, params: nil) #:nodoc: + mailer_class = params ? mailer.constantize.with(params) : mailer.constantize + mailer_class.public_send(mail_method, *args).send(delivery_method) + end + + private + # "Deserialize" the mailer class name by hand in case another argument + # (like a Global ID reference) raised DeserializationError. + def mailer_class + if mailer = Array(@serialized_arguments).first || Array(arguments).first + mailer.constantize + end + end + + def handle_exception_with_mailer_class(exception) + if klass = mailer_class + klass.handle_exception exception + else + raise exception + end + end + end +end diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb index a2ea45dc7b..1e5cab6d47 100644 --- a/actionmailer/lib/action_mailer/message_delivery.rb +++ b/actionmailer/lib/action_mailer/message_delivery.rb @@ -29,7 +29,7 @@ module ActionMailer @mail_message ||= processed_mailer.message end - # Unused except for delegator internals (dup, marshaling). + # Unused except for delegator internals (dup, marshalling). def __setobj__(mail_message) #:nodoc: @mail_message = mail_message end @@ -53,6 +53,12 @@ module ActionMailer # Notifier.welcome(User.first).deliver_later!(wait: 1.hour) # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now) # + # Options: + # + # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay + # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time + # * <tt>:queue</tt> - Enqueue the email on the specified queue + # # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable # +delivery_job+. @@ -60,12 +66,6 @@ module ActionMailer # class AccountRegistrationMailer < ApplicationMailer # self.delivery_job = RegistrationDeliveryJob # end - # - # Options: - # - # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay - # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time - # * <tt>:queue</tt> - Enqueue the email on the specified queue def deliver_later!(options = {}) enqueue_delivery :deliver_now!, options end @@ -77,6 +77,12 @@ module ActionMailer # Notifier.welcome(User.first).deliver_later(wait: 1.hour) # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now) # + # Options: + # + # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay. + # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time. + # * <tt>:queue</tt> - Enqueue the email on the specified queue. + # # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable # +delivery_job+. @@ -84,12 +90,6 @@ module ActionMailer # class AccountRegistrationMailer < ApplicationMailer # self.delivery_job = RegistrationDeliveryJob # end - # - # Options: - # - # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay. - # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time. - # * <tt>:queue</tt> - Enqueue the email on the specified queue. def deliver_later(options = {}) enqueue_delivery :deliver_now, options end @@ -135,10 +135,18 @@ module ActionMailer "#deliver_later, 2. only touch the message *within your mailer " \ "method*, or 3. use a custom Active Job instead of #deliver_later." else - args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args job = @mailer_class.delivery_job + args = arguments_for(job, delivery_method) job.set(options).perform_later(*args) end end + + def arguments_for(delivery_job, delivery_method) + if delivery_job <= MailDeliveryJob + [@mailer_class.name, @action.to_s, delivery_method.to_s, args: @args] + else + [@mailer_class.name, @action.to_s, delivery_method.to_s, *@args] + end + end end end diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb index 5e768e7106..0a97af8105 100644 --- a/actionmailer/lib/action_mailer/parameterized.rb +++ b/actionmailer/lib/action_mailer/parameterized.rb @@ -121,6 +121,12 @@ module ActionMailer end end + class DeliveryJob < ActionMailer::DeliveryJob # :nodoc: + def perform(mailer, mail_method, delivery_method, params, *args) + mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method) + end + end + class MessageDelivery < ActionMailer::MessageDelivery # :nodoc: def initialize(mailer_class, action, params, *args) super(mailer_class, action, *args) @@ -139,16 +145,27 @@ module ActionMailer if processed? super else - args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args - ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args) + job = delivery_job_class + args = arguments_for(job, delivery_method) + job.set(options).perform_later(*args) end end - end - class DeliveryJob < ActionMailer::DeliveryJob # :nodoc: - def perform(mailer, mail_method, delivery_method, params, *args) - mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method) - end + def delivery_job_class + if @mailer_class.delivery_job <= MailDeliveryJob + @mailer_class.delivery_job + else + Parameterized::DeliveryJob + end + end + + def arguments_for(delivery_job, delivery_method) + if delivery_job <= MailDeliveryJob + [@mailer_class.name, @action.to_s, delivery_method.to_s, params: @params, args: @args] + else + [@mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args] + end + end end end end diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index 0aea84fd2b..500b3bede0 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -31,22 +31,39 @@ module ActionMailer interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) } end + # Unregister one or more previously registered Interceptors. + def unregister_preview_interceptors(*interceptors) + interceptors.flatten.compact.each { |interceptor| unregister_preview_interceptor(interceptor) } + end + # Register an Interceptor which will be called before mail is previewed. # Either a class or a string can be passed in as the Interceptor. If a # string is passed in it will be constantized. def register_preview_interceptor(interceptor) - preview_interceptor = \ + preview_interceptor = interceptor_class_for(interceptor) + + unless preview_interceptors.include?(preview_interceptor) + preview_interceptors << preview_interceptor + end + end + + # Unregister a previously registered Interceptor. + # Either a class or a string can be passed in as the Interceptor. If a + # string is passed in it will be constantized. + def unregister_preview_interceptor(interceptor) + preview_interceptors.delete(interceptor_class_for(interceptor)) + end + + private + + def interceptor_class_for(interceptor) case interceptor when String, Symbol interceptor.to_s.camelize.constantize else interceptor end - - unless preview_interceptors.include?(preview_interceptor) - preview_interceptors << preview_interceptor end - end end end diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 69578471b0..893a4a25b1 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -46,10 +46,25 @@ module ActionMailer register_preview_interceptors(options.delete(:preview_interceptors)) register_observers(options.delete(:observers)) + if delivery_job = options.delete(:delivery_job) + self.delivery_job = delivery_job.constantize + end + options.each { |k, v| send("#{k}=", v) } end - ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries } + ActiveSupport.on_load(:action_dispatch_integration_test) do + include ActionMailer::TestHelper + include ActionMailer::TestCase::ClearTestDeliveries + end + end + + initializer "action_mailer.set_autoload_paths" do |app| + options = app.config.action_mailer + + if options.show_previews && options.preview_path + ActiveSupport::Dependencies.autoload_paths << options.preview_path + end end initializer "action_mailer.compile_config_methods" do @@ -69,12 +84,8 @@ module ActionMailer if options.show_previews app.routes.prepend do - get "/rails/mailers" => "rails/mailers#index", internal: true - get "/rails/mailers/*path" => "rails/mailers#preview", internal: true - end - - if options.preview_path - ActiveSupport::Dependencies.autoload_paths << options.preview_path + get "/rails/mailers" => "rails/mailers#index", internal: true + get "/rails/mailers/*path" => "rails/mailers#preview", internal: true end end end diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index 8ee4d06915..e222301dff 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -28,13 +28,13 @@ module ActionMailer # # assert_emails 2 do # ContactMailer.welcome.deliver_now - # ContactMailer.welcome.deliver_now + # ContactMailer.welcome.deliver_later # end # end - def assert_emails(number) + def assert_emails(number, &block) if block_given? original_count = ActionMailer::Base.deliveries.size - yield + perform_enqueued_jobs(only: ->(job) { delivery_job_filter(job) }, &block) new_count = ActionMailer::Base.deliveries.size assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent" else @@ -60,7 +60,7 @@ module ActionMailer # # Note: This assertion is simply a shortcut for: # - # assert_emails 0 + # assert_emails 0, &block def assert_no_emails(&block) assert_emails 0, &block end @@ -90,7 +90,7 @@ module ActionMailer # end # end def assert_enqueued_emails(number, &block) - assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block + assert_enqueued_jobs(number, only: ->(job) { delivery_job_filter(job) }, &block) end # Asserts that a specific email has been enqueued, optionally @@ -115,24 +115,21 @@ module ActionMailer # end # end # - # If `args` is provided as a Hash, a parameterized email is matched. + # If +args+ is provided as a Hash, a parameterized email is matched. # # def test_parameterized_email # assert_enqueued_email_with ContactMailer, :welcome, - # args: {email: 'user@example.com} do + # args: {email: 'user@example.com'} do # ContactMailer.with(email: 'user@example.com').welcome.deliver_later # end # end def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block) - if args.is_a? Hash - job = ActionMailer::Parameterized::DeliveryJob - args = [mailer.to_s, method.to_s, "deliver_now", args] + args = if args.is_a?(Hash) + [mailer.to_s, method.to_s, "deliver_now", params: args, args: []] else - job = ActionMailer::DeliveryJob - args = [mailer.to_s, method.to_s, "deliver_now", *args] + [mailer.to_s, method.to_s, "deliver_now", args: Array(args)] end - - assert_enqueued_with(job: job, args: args, queue: queue, &block) + assert_enqueued_with(job: mailer.delivery_job, args: args, queue: queue, &block) end # Asserts that no emails are enqueued for later delivery. @@ -151,7 +148,15 @@ module ActionMailer # end # end def assert_no_enqueued_emails(&block) - assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block + assert_enqueued_emails 0, &block end + + private + + def delivery_job_filter(job) + job_class = job.is_a?(Hash) ? job.fetch(:job) : job.class + + Base.descendants.map(&:delivery_job).include?(job_class) + end end end diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index 97eac30db1..c37a74c762 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -23,7 +23,7 @@ module Rails private def file_name # :doc: - @_file_name ||= super.gsub(/_mailer/i, "") + @_file_name ||= super.sub(/_mailer\z/i, "") end def application_mailer_file_name |