# The way ActionMailer is coded in terms of finding templates is very restrictive, to the point # where all templates for rendering must exist under the single base path. This is difficult to # work around without re-coding significant parts of the action mailer code. # # --- # # The MailTemplates module overrides two (private) methods from ActionMailer to enable mail # templates within plugins: # # [+template_path+] which now produces the contents of #template_paths # [+initialize_template_class+] which now find the first matching template and creates # an ActionVew::Base instance with the correct view_paths # # Ideally ActionMailer would use the same template-location logic as ActionView, and the same # view paths as ActionController::Base.view_paths, but it currently does not. module Engines::RailsExtensions::ActionMailer def self.included(base) #:nodoc: base.class_eval do alias_method_chain :template_path, :engine_additions alias_method_chain :initialize_template_class, :engine_additions end end private #-- # ActionMailer::Base#create uses two mechanisms to determine the proper template file(s) # to load. Firstly, it searches within the template_root for files that much the explicit # (or implicit) part encodings (like signup.text.plain.erb for the signup action). # This is how implicit multipart emails are built, by the way. # # Secondly, it then creates an ActionMailer::Base instance with it's view_paths parameter # set to the template_root, so that ActionMailer will then take over rendering the # templates. # # Ideally, ActionMailer would pass the same set of view paths as it gets in a normal # request (i.e. ActionController::Base.view_paths), so that all possible view paths # were searched. However, this seems to introduce some problems with helper modules. # # So instead, and because we have to fool these two independent parts of ActionMailer, # we fudge with the mechanisms it uses to find the templates (via template_paths, and # template_path_with_engine_additions), and then intercept the creation of the ActionView # instance so we can set the view_paths (in initialize_template_class_with_engine_additions). #++ # Returns all possible template paths for the current mailer, including those # within the loaded plugins. def template_paths paths = Engines.plugins.by_precedence.map { |p| "#{p.directory}/app/views/#{mailer_name}" } paths.unshift(template_path_without_engine_additions) unless Engines.disable_application_view_loading paths end # Return something that Dir[] can glob against. This method is called in # ActionMailer::Base#create! and used as part of an argument to Dir. We can # take advantage of this by using some of the features of Dir.glob to search # multiple paths for matching files. def template_path_with_engine_additions "{#{template_paths.join(",")}}" end # Return an instance of ActionView::Base with the view paths set to all paths # in ActionController::Base.view_paths (i.e. including all plugin view paths) def initialize_template_class_with_engine_additions(assigns) # I'd like to just return this, but I get problems finding methods in helper # modules if the method implemention from the regular class is not called # # ActionView::Base.new(ActionController::Base.view_paths.dup, assigns, self) renderer = initialize_template_class_without_engine_additions(assigns) renderer.view_paths.unshift(*ActionController::Base.view_paths.dup) renderer end end # We don't need to do this if ActionMailer hasn't been loaded. if Object.const_defined?(:ActionMailer) module ::ActionMailer #:nodoc: class Base #:nodoc: include Engines::RailsExtensions::ActionMailer end end end