From 6ba944608e527b8d7fc564a6e450e2d4c4355ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim=20and=20Mikel=20Lindsaar?= Date: Sat, 23 Jan 2010 12:20:20 +0100 Subject: Make implicit and explicit templates pass through the same part creation process. --- actionmailer/lib/action_mailer/base.rb | 126 ++++++++++++++-------------- actionmailer/lib/action_mailer/collector.rb | 25 +++--- actionmailer/test/base_test.rb | 37 +++++--- 3 files changed, 104 insertions(+), 84 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 28e4c88b5a..5e61b8693a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/class' -require "active_support/core_ext/module/delegation" +require 'active_support/core_ext/module/delegation' require 'mail' require 'action_mailer/tmail_compat' @@ -391,70 +391,63 @@ module ActionMailer #:nodoc: end end + # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer + # will be initialized according to the named method. If not, the mailer will + # remain uninitialized (useful when you only need to invoke the "receive" + # method, for instance). + def initialize(method_name=nil, *args) + super() + @message = Mail.new + process(method_name, *args) if method_name + end + + # Delivers a Mail object. By default, it delivers the cached mail + # object (from the create! method). If no cached mail object exists, and + # no alternate has been given as the parameter, this will fail. + def deliver!(mail = @message) + self.class.deliver(mail) + end + # TODO Add new delivery method goodness def mail(headers = {}) # Guard flag to prevent both the old and the new API from firing # Should be removed when old API is deprecated @mail_was_called = true - m = @message - # Get default subject from I18n if none is set - headers[:subject] ||= default_subject + m, sort_parts = @message, true - # Give preference to headers and fallbacks to the ones set in mail + # Give preference to headers and fallback to the ones set in mail content_type = headers[:content_type] || m.content_type charset = headers[:charset] || m.charset || self.class.default_charset.dup mime_version = headers[:mime_version] || m.mime_version || self.class.default_mime_version.dup - m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject] - m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to] - m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from] - m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc] - m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc] - m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to] - m.date ||= headers[:date] if headers[:date] + headers[:subject] ||= default_subject + quote_fields(m, headers, charset) - if headers[:body] - templates = [ActionView::Template::Text.new(headers[:body], format_for_text)] + responses = if headers[:body] + [ { :body => headers[:body], :content_type => self.class.default_content_type.dup } ] elsif block_given? - collector = ActionMailer::Collector.new(self, {:charset => charset}) do - render action_name - end - yield collector - - collector.responses.each do |response| - part = Mail::Part.new(response) - m.add_part(part) - end - + sort_parts = false + collector = ActionMailer::Collector.new(self) { render(action_name) } + yield(collector) + collector.responses else # TODO Ensure that we don't need to pass I18n.locale as detail templates = self.class.template_root.find_all(action_name, {}, self.class.mailer_name) - end - - if templates - if templates.size == 1 && !m.has_attachments? - content_type ||= templates[0].mime_type.to_s - m.body = render_to_body(:_template => templates[0]) - elsif templates.size > 1 && m.has_attachments? - container = Mail::Part.new - container.content_type = "multipart/alternate" - templates.each { |t| insert_part(container, t, charset) } - m.add_part(container) - else - templates.each { |t| insert_part(m, t, charset) } + + templates.map do |template| + { :body => render_to_body(:_template => template), + :content_type => template.mime_type.to_s } end end - content_type ||= (m.has_attachments? ? "multipart/mixed" : "multipart/alternate") + content_type ||= create_parts_from_responses(m, responses, charset) - # Check if the content_type was not overwriten along the way and if so, - # fallback to default. - m.content_type = content_type || self.class.default_content_type.dup + m.content_type = content_type m.charset = charset m.mime_version = mime_version - if m.parts.present? && templates + if sort_parts && m.parts.present? m.body.set_sort_order(headers[:parts_order] || self.class.default_implicit_parts_order.dup) m.body.sort_parts! end @@ -462,34 +455,43 @@ module ActionMailer #:nodoc: m end - def default_subject + protected + + def default_subject #:nodoc: mailer_scope = self.class.mailer_name.gsub('/', '.') I18n.t(:subject, :scope => [:actionmailer, mailer_scope, action_name], :default => action_name.humanize) end - def insert_part(container, template, charset) - part = Mail::Part.new - part.content_type = template.mime_type.to_s - part.charset = charset - part.body = render_to_body(:_template => template) - container.add_part(part) + def quote_fields(m, headers, charset) #:nodoc: + m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject] + m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to] + m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from] + m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc] + m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc] + m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to] + m.date ||= headers[:date] if headers[:date] end - # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer - # will be initialized according to the named method. If not, the mailer will - # remain uninitialized (useful when you only need to invoke the "receive" - # method, for instance). - def initialize(method_name=nil, *args) - super() - @message = Mail.new - process(method_name, *args) if method_name + def create_parts_from_responses(m, responses, charset) #:nodoc: + if responses.size == 1 && !m.has_attachments? + m.body = responses[0][:body] + return responses[0][:content_type] + elsif responses.size > 1 && m.has_attachments? + container = Mail::Part.new + container.content_type = "multipart/alternate" + responses.each { |r| insert_part(container, r, charset) } + m.add_part(container) + else + responses.each { |r| insert_part(m, r, charset) } + end + + m.has_attachments? ? "multipart/mixed" : "multipart/alternate" end - # Delivers a Mail object. By default, it delivers the cached mail - # object (from the create! method). If no cached mail object exists, and - # no alternate has been given as the parameter, this will fail. - def deliver!(mail = @message) - self.class.deliver(mail) + def insert_part(container, response, charset) #:nodoc: + response[:charset] ||= charset + part = Mail::Part.new(response) + container.add_part(part) end end diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index 49c3f04bad..0891b6f123 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -1,23 +1,29 @@ require 'abstract_controller/collector' +require 'active_support/core_ext/hash/reverse_merge' +require 'active_support/core_ext/array/extract_options' module ActionMailer #:nodoc: - class Collector - include AbstractController::Collector - - attr_accessor :responses + attr_reader :responses - def initialize(context, options, &block) - @default_options = options - @default_render = block - @default_formats = context.formats + def initialize(context, &block) @context = context @responses = [] + @default_render = block + @default_formats = context.formats + end + + # TODO Test me + def any(*args, &block) + options = args.extract_options! + raise "You have to supply at least one format" if args.empty? + args.each { |type| send(type, options, &block) } end + alias :all :any def custom(mime, options={}, &block) - options = @default_options.merge(:content_type => mime.to_s).merge(options) + options.reverse_merge!(:content_type => mime.to_s) @context.formats = [mime.to_sym] options[:body] = if block block.call @@ -27,6 +33,5 @@ module ActionMailer #:nodoc: @responses << options @context.formats = @default_formats end - end end \ No newline at end of file diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index bae50868ae..3033b2db0f 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -54,9 +54,9 @@ class BaseTest < ActiveSupport::TestCase mail(DEFAULT_HEADERS.merge(hash)) end - def attachment_with_content + def attachment_with_content(hash = {}) attachments['invoice.pdf'] = 'This is test File content' - mail(DEFAULT_HEADERS) + mail(DEFAULT_HEADERS.merge(hash)) end def attachment_with_hash @@ -69,8 +69,9 @@ class BaseTest < ActiveSupport::TestCase attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) mail(DEFAULT_HEADERS.merge(hash)) end - + def explicit_multipart(hash = {}) + attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) mail(DEFAULT_HEADERS.merge(hash)) do |format| format.text { render :text => "TEXT Explicit Multipart" } format.html { render :text => "HTML Explicit Multipart" } @@ -116,6 +117,7 @@ class BaseTest < ActiveSupport::TestCase test "can pass in :body to the mail method hash" do email = BaseMailer.deliver_welcome(:body => "Hello there") + assert_equal("text/plain", email.mime_type) assert_equal("Hello there", email.body.encoded) end @@ -154,12 +156,23 @@ class BaseTest < ActiveSupport::TestCase test "adds the rendered template as part" do email = BaseMailer.deliver_attachment_with_content assert_equal(2, email.parts.length) + assert_equal("multipart/mixed", email.mime_type) assert_equal("text/html", email.parts[0].mime_type) assert_equal("Attachment with content", email.parts[0].body.encoded) assert_equal("application/pdf", email.parts[1].mime_type) assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) end + test "adds the given :body as part" do + email = BaseMailer.deliver_attachment_with_content(:body => "I'm the eggman") + assert_equal(2, email.parts.length) + assert_equal("multipart/mixed", email.mime_type) + assert_equal("text/plain", email.parts[0].mime_type) + assert_equal("I'm the eggman", email.parts[0].body.encoded) + assert_equal("application/pdf", email.parts[1].mime_type) + assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) + end + # Defaults values test "uses default charset from class" do swap BaseMailer, :default_charset => "US-ASCII" do @@ -268,15 +281,15 @@ class BaseTest < ActiveSupport::TestCase end end - #test "explicit multipart with attachments creates nested parts" do - # email = BaseMailer.deliver_explicit_multipart(:attachments => true) - # assert_equal("application/pdf", email.parts[0].mime_type) - # assert_equal("multipart/alternate", email.parts[1].mime_type) - # assert_equal("text/plain", email.parts[1].parts[0].mime_type) - # assert_equal("TEXT Implicit Multipart", email.parts[1].parts[0].body.encoded) - # assert_equal("text/html", email.parts[1].parts[1].mime_type) - # assert_equal("HTML Implicit Multipart", email.parts[1].parts[1].body.encoded) - #end + test "explicit multipart with attachments creates nested parts" do + email = BaseMailer.deliver_explicit_multipart(:attachments => true) + assert_equal("application/pdf", email.parts[0].mime_type) + assert_equal("multipart/alternate", email.parts[1].mime_type) + assert_equal("text/plain", email.parts[1].parts[0].mime_type) + assert_equal("TEXT Explicit Multipart", email.parts[1].parts[0].body.encoded) + assert_equal("text/html", email.parts[1].parts[1].mime_type) + assert_equal("HTML Explicit Multipart", email.parts[1].parts[1].body.encoded) + end protected -- cgit v1.2.3