Deep in Rails: ActionMailer#deliver Part III

Posted by admin 29/11/2009 at 10h47

When we last left off we were just about to jump into Renderable#render. Thatâ??s where we are going next:

# (line 193 action_pack/action_view/renderable.rb)
def render(view, local_assigns = {})
  compile(local_assigns)

  view.with_template self do
    view.send(:_evaluate_assigns_and_ivars)
    view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)

    view.send(method_name(local_assigns), local_assigns) do |*names|
      ivar = :@_proc_for_layout
      if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar))
        view.capture(*names, &proc)
      elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}")
        view.instance_variable_get(ivar)
      end
    end
  end
end

Alright, this is where we get real deep into Rails. This particular method does the vast majority of the rendering work in Rails. Letâ??s see whatâ??s going on:

compile(local_assigns)

This is the first line and it sets up a series of methods that we are going to run through. compile is a private method in Renderable:

# (line 193 action_pack/action_view/renderable.rb)
def compile(local_assigns)
  render_symbol = method_name(local_assigns)

  if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
    compile!(render_symbol, local_assigns)
  end
end

which starts out creating a method symbol for this renderable object by calling another private method aptly named method_name. This is the definition for method_name:

# (line 45 action_pack/action_view/renderable.rb)
def method_name(local_assigns)
  if local_assigns && local_assigns.any?
    method_name = method_name_without_locals.dup
    method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
  else
    method_name = method_name_without_locals
  end
  method_name.to_sym
end

Here are the params for our example:

local_assigns => {}

Since our local_assigns is empty, we end up executing the else part of the conditional which sets method_name to the result method_name_without_locals which is yet another private method in Renderable. Here it is:

# (line 22 action_pack/action_view/renderable.rb)
def method_name_without_locals
  ['_run', extension, method_segment].compact.join('_')
end

This method gives us unique method name to use for the definition of the compiled rendered template. You may have seen something like the following in your Rails development logs:

Account Columns (251.9ms)   SHOW FIELDS FROM `accounts`
AccountSettings Columns (4.2ms)   SHOW FIELDS FROM `account_settings`
Account Load (0.5ms)   SELECT * FROM `accounts` WHERE (`accounts`.`id` = 47715) 
  app/controllers/application.rb:144:in `current_user'
  app/views/layouts/bare.rhtml:21:in `_run_rhtml_app47views47layouts47bare46rhtml'
  app/controllers/administration_controller.rb:10:in `index'
  vendor/plugins/mongrel_proctitle/lib/mongrel_proctitle.rb:114:in `process_client'
  vendor/plugins/mongrel_proctitle/lib/mongrel_proctitle.rb:33:in `request'
  vendor/plugins/mongrel_proctitle/lib/mongrel_proctitle.rb:112:in `process_client'
  vendor/plugins/mongrel_proctitle/lib/mongrel_proctitle.rb:102:in `run'

You see that line:

app/views/layouts/bare.rhtml:21:in `_run_rhtml_app47views47layouts47bare46rhtml'

the _run_rhtml_app47views47layouts47bare46rhtml is actually the name of the method that is given to your template. This is done because Rails executes your template, just like any piece of Ruby code. To do that, it needs a method name, and that is what we are doing in the method_name_without_locals method, creating a unique method name for this template we are about to run. method_segment is a method that is included in from ActionView::Template and extension is an attribute in ActionView::Template. Here is method_segment and extension:

# (line 22 action_pack/action_view/template.rb)
def method_segment
  relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
end

extension => "erb"

which calls another method in Template called relative_path displayed here:

# (line 172 action_pack/action_view/template.rb)
def relative_path
  path = File.expand_path(filename)
  path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT)
  path
end

First, we get the full pathname to the current template and then we run sub on the path to remove anything that may look like the full expanded path of RAILS_ROOT if RAILS_ROOT is defined. The templateâ??s filename is stored in the attribute filename. Here are the params:

path => app/views/message_mailer/contact_form.erb
filename => /example_app/app/views/message_mailer/contact_form.erb

As you can see, the main purpose for this method is to give us the relative path to the mailer. Now that we got our path, letâ??s see what method_segment does to it:

relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }

We call gsub on the path, using the less familiar block usage. This particular usage passes each matched string to the block for processing. In this case, we want to match any character that isnt alphanumeric or an â??_â?. We take each of these matches (which are the individual characters that matched our expression) and call ord on them which returns their ASCII number. So in our case, method_segment would end up returning app47views47message_mailer47contact_form46erb.

Good, now we have our method_segment and we know what our extension is, so letâ??s see what we get from method_name_without_locals for our example:

_run_erb_app47views47message_mailer47contact_form46erb

Whew, now weâ??re back to Renderable#method_name with our method name and we return back from that to compile. Letâ??s refresh our memories real quick:

# (line 193 action_pack/action_view/renderable.rb)
def compile(local_assigns)
  render_symbol = method_name(local_assigns)

  if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
    compile!(render_symbol, local_assigns)
  end
end

and we get back for render_symbol:

:_run_erb_app47views47message_mailer47contact_form46erb

The next section is where the super-crazy-secret-sauce magic happens, but first we have to do some checks. First, we check to see if our render_symbol is defined as a method in the module ActionView::Base::CompiledTemplates. Letâ??s take a gander at CompiledTemplates:

# (line 193 action_pack/action_view/renderable.rb)
module CompiledTemplates #:nodoc:
  # holds compiled template code
end

What theâ?¦. Where is the code?

Well, remember that we just created a new method name for our template. Why did we do that? Because Rails needs to execute the template as with everything else. So we have to define the method somehow so that it can be executed, right? Exactly. That is what CompiledTemplates is for. Itâ??s the placeholder module where we will keep these method definitions. Basically, itâ??s a nice container to hold these dynamically created methods so they arent spread out all over ObjectSpace. Plus, it makes clearing the methods cache pretty simple, just undef every method in CompiledTemplates. So first, we check to see if there is a method defined for this particular template. Then we check the private method recompile?, which is so simple, that I think it may be a placeholder for something in the future. Here is recompile?:

# (line 92 action_pack/action_view/renderable.rb)
def recompile?
  false
end

So basically, we are just checking the first part of our condition for the method definition. In our case, we dont have a method defined as such and we go on to compile!, which is a private method in Renderable and looks like this:

# (line 66 action_pack/action_view/renderable.rb)
def compile!(render_symbol, local_assigns)
  locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join

  source = <<-end_src
    def #{render_symbol}(local_assigns)
      old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
    ensure
      self.output_buffer = old_output_buffer
    end
  end_src

  begin
    ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
  rescue Errno::ENOENT => e
    raise e # Missing template file, re-raise for Base to rescue
  rescue Exception => e # errors from template code
    if logger = defined?(ActionController) && Base.logger
      logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
      logger.debug "Function body: #{source}"
      logger.debug "Backtrace: #{e.backtrace.join("\n")}"
    end

    raise ActionView::TemplateError.new(self, {}, e)
  end
end

Here, we start by creating the string that will assign the local variables to their proper values and storing it in the variable locals_code. Then we move on to creating the source code that we want dynamically created and inserted into CompiledTemplates. This code uses the method compiled_source which uses another method named handler. handler is a wrapper method for handler_class_for_extension which is a class method belonging to Template. This method is defined in TemplateHandlers and extended by Template. All of these methods are illustrated below:

# (line 66 action_pack/action_view/renderable.rb)
def handler
  Template.handler_class_for_extension(extension)
end

def compiled_source
  handler.call(self)
end

# (action_pack/action_view/template_handlers_.rb)
module ActionView #:nodoc:
  module TemplateHandlers #:nodoc:
    ...
    def self.extended(base)
      base.register_default_template_handler :erb, TemplateHandlers::ERB
      base.register_template_handler :rjs, TemplateHandlers::RJS
      base.register_template_handler :builder, TemplateHandlers::Builder

      base.register_template_handler :rhtml, TemplateHandlers::ERB
      base.register_template_handler :rxml, TemplateHandlers::Builder
    end
    ...
    def registered_template_handler(extension)
      extension && ``template_handlers[extension.to_sym]
    end
    ...
    def handler_class_for_extension(extension)
      registered_template_handler(extension) || ``default_template_handlers
    end
  end
end

When TemplateHandlers is extended it registers several extension handlers. You are probably familiar with all of them, so we wont go into what each extension is for. handler_class_for_extension returns the result of registered_template_handler or @default_template_handlers. registered_template_handler returns the proper handler (TemplateHandlers::ERB, TemplateHandlers::RJS or TemplateHandlers::MyCustomTemplateHandler) from the class variable @template_handlers which holds the defaults plus any newly registered templates.

So we get the proper handler back (in our case ActionView::TemplateHandlers::ERB) and we execute the call method with our ReloadableTemplate as the parameter. call is a class method in the module TemplateHandlers::Compilable and included into ERB. It looks like this:

# (line 4 action_pack/action_view/template_handlers_.rb)
module Compilable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def call(template)
      new.compile(template)
    end
  end

  def compile(template)
    raise "Need to implement #{self.class.name}#compile(template)"
  end
end

When we include this module we extend the base class with the module ClassMethods which contains the call method. This method creates a new instance of the base class and calls the compile instance method with the template. The compile in this module raises an error to let you know that it is an abstract method and needs to be defined by the base class, in our case ERB. Letâ??s see what ERB looks like:

# (line 4 action_pack/action_view/template_handlers/erb.rb)
class ERB < TemplateHandler
  include Compilable

  cattr_accessor :erb_trim_mode
  self.erb_trim_mode = '-'
  ...
  def compile(template)
    src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
    RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
  end
end

compile is pretty simple. We create a new instance of ERB parser and pass it the template source and the variable that we want to build the output on (@output_buffer) and returning the src attribute which holds the ruby code generated by the ERB parser. @output_buffer is the name of an attribute inside of ActionView::Base that is used as the output buffer (of course) which will get flushed when we do the final rendering (towards the end of the deliver method). In the case of an AM view, the rendering is pretty simple, but when we get to full controller views with partials we need a place to concat all the different views together which is what we use @output_buffer for. The next line checks for Ruby 1.9 and does some cleaning of the result before returning it. Here is what src outputs for our example:

src => "@output_buffer = '';  __in_erb_template=true ; @output_buffer.concat \"Hi,\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat \"I am contacting you with this message:\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat(( `message ).to_s); @output_buffer.concat \"\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat \"Thanks\\n\"\n@output_buffer.concat \"\\n\"\n@output_buffer.concat \"Jake\"; @output_buffer"

This is one long string that represents the compiled version of the ruby code for our mailer view. Now that we have the compiled_source, letâ??s go back compile!:

# (line 66 action_pack/action_view/renderable.rb)
def compile!(render_symbol, local_assigns)
  locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join

  source = <<-end_src
    def #{render_symbol}(local_assigns)
      old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
    ensure
      self.output_buffer = old_output_buffer
    end
  end_src
  ...
end

So now we know what all the pieces mean, letâ??s see what our example becomes:

def _run_erb_app47views47message_mailer47contact_form46erb(local_assigns)
  old_output_buffer = output_buffer;;@output_buffer = '';  __in_erb_template=true ; @output_buffer.concat "Hi,\n"
  @output_buffer.concat "\n"
  @output_buffer.concat "I am contacting you with this message:\n"
  @output_buffer.concat "\n"
  @output_buffer.concat(( `message ).to_s); @output_buffer.concat "\n"
  @output_buffer.concat "\n"
  @output_buffer.concat "\n"
  @output_buffer.concat "\n"@output_buffer.concat "Thanks\n"
  @output_buffer.concat "\n"
  @output_buffer.concat "Jake"; @output_buffer
ensure
  self.output_buffer = old_output_buffer
end

Alright, now we got the code we want evalâ??ed, letâ??s keep going through compile!

# (line 66 action_pack/action_view/renderable.rb)
def compile!(render_symbol, local_assigns)
  ...
  begin
    ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
  rescue Errno::ENOENT => e
    raise e # Missing template file, re-raise for Base to rescue
  rescue Exception => e # errors from template code
    if logger = defined?(ActionController) && Base.logger
      logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
      logger.debug "Function body: #{source}"
      logger.debug "Backtrace: #{e.backtrace.join("\n")}"
    end

    raise ActionView::TemplateError.new(self, {}, e)
  end
end

Now we open up CompiledTemplates and perform a module_eval with the source we just made and the filename of this template (/example_app/app/views/message_mailer/contact_form.erb) and 0. The filename and the number 0 are used in error reporting during module_eval. Letâ??s say something went wrong with the module_eval, in our case, the error would look something like this:

/example_app/app/views/message_mailer/contact_form.erb:0:in `module_eval': the real problem goes here

Thatâ??s it for this week. Next week we continue with our dynamically compiled method and see what Rails does with it.


Deep in Rails: ActionMailer#deliver Part II

Posted by admin 22/11/2009 at 10h34

When we left off last, we were just about to get into rendering the message. Letâ??s recap that code:

(line 553 action_mailer/base.rb)
def render_message(method_name, body)
  if method_name.respond_to?(:content_type)
    @current_template_content_type = method_name.content_type
  end
  render :file => method_name, :body => body
ensure
  @current_template_content_type = nil
end

When we call render_message in create!, we call it with @template and @body, letâ??s see what those get set to inside of render_message for our example:

@template   = method_name     => "contact_form"
@body       = body            => {:message=>"Send this message"}

At the beginning of render_message @body is set to the hash of instance variables that we set in our contact_form method in our mailer. Once we are finished processing, that hash will be gone and @body will be replaced with the full rendered message.

Next, we see that we want to set the @current_template_content_type instance variable to the proper content type. This is mainly used for multipart so that if render is passed one of those fancy ActionView::ReloadableTemplate objects it renders it with the proper parser. Since we did not specify a content_type the method_name â??contact_formâ? is passed and we get to skip over that part and go straight to the meat:

render :file => method_name, :body => body

This is not the same render from ActionController or ActionView, but it does use the AV template render methods. Letâ??s take a look at the definition:

(line 562 action_mailer/base.rb)
def render(opts)
  body = opts.delete(:body)
  if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render))
    opts[:file] = "#{mailer_name}/#{opts[:file]}"
  end
  
  begin
    old_template, @template = @template, initialize_template_class(body)
    layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false
    @template.render(opts.merge(:layout => layout))
  ensure
    @template = old_template
  end
end

and the params with our example:

opts => {
  :file => "contact_form",
  :body => {:message=>"Send this message"}
}

The first thing we do is set the variable body to opts[:body] and delete it from the options hash (this is all done in one stroke by the call delete which returns the value that was deleted). Next, we test to see if opts[:file] does not match â/â? and does not respond to render then we set opts[:file] to the same thing but prepended with the mailer_name, in our case âmessage_mailerâ?.

Now we are getting to the real meat. First, we need to set some quick variables before we render. Here are the 2 lines we will be examining next:

old_template, @template = @template, initialize_template_class(body)
layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false

In the first line we are swapping out the current @template variable with the result of initialize_template_class(body) and storing the old value in old_template. Here is the definition of initialize_template_class:

(line 603 action_mailer/base.rb)
def initialize_template_class(assigns)
  template = ActionView::Base.new(self.class.view_paths, assigns, self)
  template.template_format = default_template_format
  template
end

and assigns gets set to:

body => {:message=>"Send this message"}

The first thing we see is that we create a var named template that is a new instantiation of ActionView::Base with the view_paths, assigns, and the model (MessageMailer in our example) passed to it. Letâ??s dig in and cross over to ActionView::Base and look at initialize:

(line 221 action_pack/action_view/base.rb)
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
  @assigns = assigns_for_first_render
  @assigns_added = nil
  @controller = controller
  @helpers = ProxyModule.new(self)
  self.view_paths = view_paths
  @_first_render = nil
  @_current_render = nil
end

and the params with our example:

view_paths               => ["/example_app/app/views"]
assigns_for_first_render => {:message=>"Send this message"}
controller               => #<MessageMailer:0x34a579c 
                @action_name="contact_form", 
                @subject="Im contacting you", 
                @content_type="text/plain", 
                @implicit_parts_order=["text/html", "text/enriched", "text/plain"], 
                @from="jake@somewhere.com", 
                @sent_on=Sun Nov 08 11:34:17 -0600 2009, 
                @headers={}, 
                @default_template_name="contact_form", 
                @mime_version="1.0", 
                @body={:message=>"Send this message"}, 
                @mailer_name="message_mailer", 
                @recipients="someemail@somedomain.com", 
                @parts=[], 
                @template="contact_form", 
                @charset="utf-8">

This allows us to wrap the mailers view around the ActionView templating system and reuse the rendering module from ActionView in AM. This also stores our body variables for use inside of our view. Now that we have a template, we need to initialize the template_format with our mailerâ??s default template format.

template.template_format = default_template_format

Letâ??s open up the definition for default_template_format

(line 577 action_mailer/base.rb)
def default_template_format
  if @current_template_content_type
    Mime::Type.lookup(@current_template_content_type).to_sym
  else
    :html
  end
end

This method checks the @current_template_content_type ivar and returns the proper mime-type. This is used as a switch by the renderer. If you dont explicitly set the content type AM defaults to HTML and your message is rendered in HTML mode. Since we didnt define a content_type in our example @current_template_content_type is nil and :html will be returned for the default_template_format.

After that we return the created template instance and that gets us through the initialize_template_class method.

So now that we have set the @template ivar in:

old_template, @template = @template, initialize_template_class(body)
layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false

We move down to setting the layout for the mailer. In Rails you have the ability to use layouts just like in regular views. In our case, if we wanted to use a layout, we would have to add a file named message_mailer.html.erb in app/views/layouts. The other option is specifying a layout name in our mailer model (just like we would in a controller). So we could define MessageMailer as:

class MessageMailer < ActionMailer::Base
  layout "my_email_layout"
  def contact_form(msg_from, message)
    ...
  end
end

In that case, we would need a file named my_email_layout.html.erb in our layouts directory. Since we include ActionController::Layout in AM::Base our model will respond to pick_layout. This is the method that will determine the layout needed for the renderer. Letâ??s dive into pick_layout, which is defined in ActionController::Layout

# (line 229 action_pack/action_controller/layout.rb)
def pick_layout(options)
  if options.has_key?(:layout)
    case layout = options.delete(:layout)
    when FalseClass
      nil
    when NilClass, TrueClass
      active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name)
    else
      active_layout(layout, :html_fallback => true)
    end
  else
    active_layout if action_has_layout? && candidate_for_layout?(options)
  end
end

and the params for our example:

options => {:file=>"message_mailer/contact_form"}

The first part of pick_layout checks for the key :layout in options and since we didnt define it in our example we will skip over it and straight to the else part of the conditional:

active_layout if action_has_layout? && candidate_for_layout?(options)

Here, we call the method active_layout if action_has_layout? returns true and candidate_for_layout?(options) returns true. action_has_layout? is a simple method to check if this action or mailer method has a layout and is basically making sure that your :except and :only actions render properly. Letâ??s take a look at action_has_layout? with our example

# (line 244 action_pack/action_controller/layout.rb)
def action_has_layout?
  if conditions = self.class.layout_conditions
    case
      when only = conditions[:only]
        only.include?(action_name)
      when except = conditions[:except]
        !except.include?(action_name)
      else
        true
    end
  else
    true
  end
end

and conditions would be:

conditions => nil

Since we are not setting a layout for our message the method self.class.layout_conditions returns nil. layout_conditions is a class method on AM that reads the attribute accessor hash layout_conditions. These conditions are written when you make the call:

layout "my_template" # or layout "my_template", :except => "some_message"

This writes all your conditions to a attribute hash for easy reading and subclassing. Since our example returns nil for layout_conditions we end up at the outer else statement and return true out of this method and on to candidate_for_layout?(options). This is a base method is written in layout.rb and overridden in AM::base.rb:

# (line 585 action_mailer/base.rb)
def candidate_for_layout?(options)
  !self.view_paths.find_template(default_template_name, default_template_format).exempt_from_layout?
rescue ActionView::MissingTemplate
  return true
end

and our paramaters and extra variables/methods :

options => {:file=>"message_mailer/contact_form"}
default_template_name => "contact_form"
default_template_format => :html

In this method, we are checking to see if there is a template with the above params. Since we dont have any layouts Rails throws an ActionView::MissingTemplate exception which we catch and just return true from it. This sets up the final part of pick_layout which is active_layout. Here is the definition for that:

# (line 201 action_pack/action_controller/layout.rb)
def active_layout(passed_layout = nil, options = {})
  layout = passed_layout || default_layout
  return layout if layout.respond_to?(:render)
  
  active_layout = case layout
    when Symbol then __send__(layout)
    when Proc   then layout.call(self)
    else layout
  end
  
  find_layout(active_layout, default_template_format, options[:html_fallback]) if active_layout
end

In our case, we dont pass anything to active_layout so the first line will get set to default_layout, which is a private method that looks like:

# (line 220 action_pack/action_controller/layout.rb)
def default_layout #:nodoc:
  layout = self.class.read_inheritable_attribute(:layout)
  return layout unless self.class.read_inheritable_attribute(:auto_layout)
  find_layout(layout, default_template_format)
rescue ActionView::MissingTemplate
  nil
end

Since we didnt set a layout, the first line will return nil and the second line will return that value because auto_layout will also be nil. When we get back to active_layout, the variable layout is set to nil. The variable active_layout gets set to nil as well since we are layout-less. Now we get to find_layout which doesnt get called in our example because active_layout is nil. Thatâs it, we are now ready to do the main rendering. Letâs look at the code again:

(line 562 action_mailer/base.rb)
def render(opts)
  ...
  begin
    old_template, @template = @template, initialize_template_class(body)
    layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false
    @template.render(opts.merge(:layout => layout))
  ensure
    @template = old_template
  end
end

and the params and current variables with our example:

opts => {
  :file => "contact_form",
  :body => {:message=>"Send this message"}
}
old_template  => "contact_form"
@template => #<ActionView::Base:0x3496c10 
                @template_format=:html, 
                @assigns={:message=>"Send this message"}, 
                @helpers=#<ActionView::Base::ProxyModule:0x3496b84>, 
                @controller=#<MessageMailer:0x34a5814 
                @action_name="contact_form", 
                @subject="Im contacting you", 
                @content_type="text/plain", 
                @implicit_parts_order=["text/html", "text/enriched", "text/plain"], 
                @from="jake@somewhere.com", 
                @sent_on=Sun Nov 08 18:15:42 -0600 2009, 
                @headers={}, 
                @default_template_name="contact_form", 
                @mime_version="1.0", 
                @body={:message=>"Send this message"}, 
                @mailer_name="message_mailer", 
                @recipients="someemail@somedomain.com", 
                @parts=[], 
                @template=#<ActionView::Base:0x3496c10 ...>, 
                @charset="utf-8">, 
                @_current_render=nil, 
                @assigns_added=nil, 
                @_first_render=nil,
                @view_paths=["/example_app/app/views"]>

So now we see that layout ends up being nil in our example which we then pass on to @template.render along with our original options. Now weâ??ll jump into render:

# (line 201 action_view/base.rb)
def render(options = {}, local_assigns = {}, &block) #:nodoc:
  local_assigns ||= {}
  
  case options
  when Hash
    options = options.reverse_merge(:locals => {})
    if options[:layout]
      _render_with_layout(options, local_assigns, &block)
    elsif options[:file]
      template = self.view_paths.find_template(options[:file], template_format)
      template.render_template(self, options[:locals])
    elsif options[:partial]
      render_partial(options)
    elsif options[:inline]
      InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
    elsif options[:text]
      options[:text]
    end
  when :update
    update_page(&block)
  else
    render_partial(:partial => options, :locals => local_assigns)
  end
end

and our passed in params:

options => {:file=>"message_mailer/contact_form", :layout=>nil}
local_assigns => {}

Since options is a Hash, we jump right in and first merge in :locals => {} with options. options[:layout] returns nil so we skip that statement and go into the options[:file] conditional

template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])

Here, we search out the template from the AM models view_paths with our file and the temlpate_format, which in our case is :html. view_paths is an instance method in ActionView::Base that accesses the attribute @view_paths which holds an instance of @ ActionView::PathSet@. This is a helper class that wraps an array with some pretty handy methods, one of which is find_template. This helper class stores each of the view_paths as a ActionView::ReloadableTemplate::ReloadablePath object. This allows us to do other cooler things which we go over later. In our case, view_paths returns:

self.view_paths => ["/example_app/app/views"]

Letâ??s dive into find_template:

# (line 201 action_pack/action_view/paths.rb)
def find_template(original_template_path, format = nil, html_fallback = true)
  return original_template_path if original_template_path.respond_to?(:render)
  template_path = original_template_path.sub(/^\//, '')
  
  each do |load_path|
    if format && (template = load_path["#{template_path}.#{I18n.locale}.#{format}"])
      return template
    elsif format && (template = load_path["#{template_path}.#{format}"])
      return template
    elsif template = load_path["#{template_path}.#{I18n.locale}"]
      return template
    elsif template = load_path[template_path]
      return template
    # Try to find html version if the format is javascript
    elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"]
      return template
    elsif format == :js && html_fallback && template = load_path["#{template_path}.html"]
      return template
    end
  end
  
  return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path)
  
  raise MissingTemplate.new(self, original_template_path, format)
end

and our params:

original_template_path => "message_mailer/contact_form"
format => :html

The first line just returns back if we have a full fledged ActionView model that responds to render. Since we dont, we keep going and see that the next line makes sure that our original_template_path isnt an absolute path by removing the initial â??/â? if it is there and storing the final result in template_path. Next, we iterate over ourselves and do some checking (remember, PathSet is a subclass of Array, so we can call each and iterate over the contents internally). In our example, we end up executing the line:

elsif template = load_path[template_path]
  return template

This is part of the fanciness of ActionView::ReloadableTemplate::ReloadablePath. One of the methods implemented is [] which works like a hash and pulls out the proper ReloadableTemplate from the paths array, in our case itâ??s

template => #<ActionView::ReloadableTemplate:0x34a2ad8 
              @base_path="message_mailer", 
              @template_path="message_mailer/contact_form.erb", 
              @_memoized_path=["message_mailer/contact_form.erb"], 
              @name="contact_form", 
              @previously_last_modified=Sat Nov 07 10:32:50 -0600 2009, 
              @_memoized_method_segment=["app47views47message_mailer47contact_form46erb"], 
              @format=nil, 
              @filename="/example_app/app/views/message_mailer/contact_form.erb", 
              @_memoized_relative_path=["app/views/message_mailer/contact_form.erb"], 
              @extension="erb", 
              @locale=nil, 
              @_memoized_path_without_extension=["message_mailer/contact_form"], 
              @_memoized_method_name_without_locals=["_run_erb_app47views47message_mailer47contact_form46erb"], 
              @load_path="/example_app/app/views">
              
So we found the proper view and we are now done with @find_template@. Back to @render@:
# (line 201 action_pack/action_view/base.rb)
def render(options = {}, local_assigns = {}, &block) #:nodoc:
  local_assigns ||= {}
  
  case options
  when Hash
    ...
    elsif options[:file]
      template = self.view_paths.find_template(options[:file], template_format)
      template.render_template(self, options[:locals])
    elsif options[:partial]
      ...
    end
  when :update
    ...
  end
end

The next line renders the template with any locals (in our case there are none). Before we dive into render_template âs definition, letâs take a look at the view we created for our example message:


(message_mailer/contact_form.erb)

Hi,

I am contacting you with this message:

<%= @message %>

Thanks

Jake

A ridiculously simple mailer view for our equally ridiculously simple mailer message. Back to render_template

# (line 193 action_pack/action_view/template.rb)
def render_template(view, local_assigns = {})
  render(view, local_assigns)
rescue Exception => e
  raise e unless filename
  if TemplateError === e
    e.sub_template_of(self)
    raise e
  else
    raise TemplateError.new(self, view.assigns, e)
  end
end

and our current params:

view  => #<ActionView::Base:0x3496c10 
              @template_format=:html, 
              @assigns={:message=>"Send this message"}, 
              @helpers=#<ActionView::Base::ProxyModule:0x3496b84>, 
              @controller=#<MessageMailer:0x34a5814 
              @action_name="contact_form", 
              @subject="Im contacting you", 
              @content_type="text/plain", 
              @implicit_parts_order=["text/html", "text/enriched", "text/plain"], 
              @from="jake@somewhere.com", 
              @sent_on=Mon Nov 09 20:09:58 -0600 2009, 
              @headers={}, 
              @default_template_name="contact_form", 
              @mime_version="1.0", 
              @body={:message=>"Send this message"}, 
              @mailer_name="message_mailer", 
              @recipients="someemail@somedomain.com", 
              @parts=[], 
              @template=#<ActionView::Base:0x3496c10 ...>, 
              @charset="utf-8">, 
              @_current_render=nil, 
              @assigns_added=nil, 
              @_first_render=nil, 
              @view_paths=["/example_app/app/views"]>
local_assigns => {}

As we can see, render_template is a relatively simple method that delegates most of itâs work to Renderable#render. Thatâs where we are going next:

# (line 193 action_pack/action_view/renderable.rb)
def render(view, local_assigns = {})
  compile(local_assigns)
  
  view.with_template self do
    view.send(:_evaluate_assigns_and_ivars)
    view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
    
    view.send(method_name(local_assigns), local_assigns) do |*names|
      ivar = :@_proc_for_layout
      if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar))
        view.capture(*names, &proc)
      elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}")
        view.instance_variable_get(ivar)
      end
    end
  end
end

Whew, thatâs it for this week. Next week, we get super deep into rendering and see how Rails performs all of itâs rendering duties.


Deep in Rails: ActionMailer#deliver Part I

Posted by admin 15/11/2009 at 14h59

Welcome to the first in a series of articles that will delve deep into several commonly used methods of Rails. The first method we will look at is Actionmailer#deliver. Now, I am going to assume that you know what AM is and how to use. You’ve generated mailer models before and set up views and called these methods via something like: MyMailer.deliver_some_message(insert, your, params, here). What we are going to to look at is what happens when you make the deliver call.

We will be skipping what happens around rails (the ruby core goings-ons) and all the method stacking that goes on in ruby and focus on the rails part in this series. So let’s get started. (We will be using Rails 2.3.2 as the guide for the code)

What’s happening now?

When deliver_some_message is executed, the first method that is fired is AR’s class method method_missing. Here’s the method that gets executed:

(line 391 action_mailer/base.rb)
class << self
  ...
  def method_missing(method_symbol, *parameters) #:nodoc:
    if match = matches_dynamic_method?(method_symbol)
      case match[1]
        when 'create'  then new(match[2], *parameters).mail
        when 'deliver' then new(match[2], *parameters).deliver!
        when 'new'     then nil
      else super
      end
    else
      super
    end
  end
  ...
end

So when you call deliver_some_message(params), ruby passes all that info (method name included) to the method_missing method. I have created a simple Mailer for this series to help us see what’s going on throughout this entire chain of methods and to see each of the params for these methods.

Here is the quick mailer I created:

class MessageMailer < ActionMailer::Base
  def contact_form(msg_from, message)
    subject    'Im contacting you'
    recipients "someemail@somedomain.com"
    from       msg_from
    sent_on    Time.now
    body       :message => message
  end
end

So when I call

MessageMailer.deliver_contact_form("jake@somewhere.com", "Send this message")

the params end up being distributed like so:

method_symbol => :deliver_contact_form
parameters => ["jake@somewhere.com", "Send this message"]

Now that we know what’s being passed, let’s see what happens to it. The first line of the method is

if match = matches_dynamic_method?(method_symbol)

matches_dynamic_method? is a private method in AM and is defined as:

(line 441 action_mailer/base.rb)
private
  def matches_dynamic_method?(method_name) #:nodoc:
    method_name = method_name.to_s
    /^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
  end

This is pretty simple, we are doing a scan across the method name to match it against create_ or deliver_ and then capturing the rest of the method name or we are looking strictly for new at the beginning of our method name and with nothing else attached. The reason this is done is to disable AM’s initialize capabilities for outside referencing and only offer create and deliver. On line 254 in AR base.rb we see that we have privatize the method new with:

private_class_method :new #:nodoc:

So if someone tries to be clever and call MessageMailer.new, it will hit the method_missing (since the base new method has now been made private) and end up returning nil.

The match method returns a Match object that contains the captures. With our example the match variable would look like:

match.captures => ["deliver", "contact_form"]
match[1] => deliver
match[2] => contact_form

So now we got a match and we can continue thru the case statement. Since match[1] in our example returns deliver, we execute the line

when 'deliver' then new(match[2], *parameters).deliver!

Here, we call the private new method to initialize a new AR instance and we pass it the second part of our capture and all the params that came to method_missing originally. After initializing, the method deliver! is then called (which we will talk about after we talk about what happens during initialization).

Let’s go ahead and see what new looks like (or more precisely, initialize, since you dont create a new method, you create the initialize method which new calls). AR’s initialize method looks like this:

(line 452 action_mailer/base.rb)
def initialize(method_name=nil, *parameters) #:nodoc:
  create!(method_name, *parameters) if method_name
end

This initialize is quite simple, it calls create! if method_name is present. In our case it will be since we passed “contact_form” to it. So let’s go to create! which is also right below it in the source:

   
(line 458 action_mailer/base.rb)
def create!(method_name, *parameters) #:nodoc:
  initialize_defaults(method_name)
  __send__(method_name, *parameters)

  unless String === @body
    if @parts.empty?
      Dir.glob("#{template_path}/#{@template}.*").each do |path|
        template = template_root["#{mailer_name}/#{File.basename(path)}"]
        
        next unless template && template.multipart?
        
        @parts << Part.new(
          :content_type => template.content_type,
          :disposition => "inline",
          :charset => charset,
          :body => render_message(template, @body)
        )
      end
      unless @parts.empty?
        @content_type = "multipart/alternative" if @content_type !~ /^multipart/
        @parts = sort_parts(@parts, @implicit_parts_order)
      end
    end
    
    template_exists = @parts.empty?
    template_exists ||= template_root["#{mailer_name}/#{@template}"]
    @body = render_message(@template, @body) if template_exists
    
    if !@parts.empty? && String === @body
      @parts.unshift Part.new(:charset => charset, :body => @body)
      @body = nil
    end
  end
  
  @mime_version ||= "1.0" if !@parts.empty?
  
  @mail = create_mail
end

Whoa! Don’t worry, it’s not that bad. Let’s take it line by line. Note: I have removed the comments from the above snippet because they wouldnt format in textile properly.

First, let’s figure out what we have in the method:

method_name => "contact_form"
parameters => ["jake@somewhere.com", "Send this message"]

Cool, now we know what we have going in, so let’s step through it. First we have: initialize_defaults(method_name) which is a private method in AM

which looks like:

(line 537 action_mailer/base.rb)
def initialize_defaults(method_name)
  @charset ||= @@default_charset.dup
  @content_type ||= @@default_content_type.dup
  @implicit_parts_order ||= @@default_implicit_parts_order.dup
  @template ||= method_name
  @default_template_name = @action_name = @template
  @mailer_name ||= self.class.name.underscore
  @parts ||= []
  @headers ||= {}
  @body ||= {}
  @mime_version = @@default_mime_version.dup if @@default_mime_version
end

This method initializes several of the attribute accessors, or in this case advanced attribute accessors, with defaults and some class attribute accessors. The advanced attribute accessors are just like regular attribute accessors except they add one extra ability which you have seen several times in your use of ActionMailer models. Take a look at our simple mailer again:

class MessageMailer < ActionMailer::Base
  def contact_form(msg_from, message)
    subject    'Im contacting you'
    ...
  end
end

Do you see it? Notice, how we can make the call:

subject    'Im contacting you'

This is not a normal attribute accessor call. Normally, you can only call

obj.some_attribute

or

obj.some_attribute =  "some stuff"

The advanced attribute accessor gives you a third option of setting the attribute via a regular method call like:

obj.some_attribute("some stuff")

or, more elegantly:

obj.some_attribute "some stuff"

instead of the regular ol’

obj.some_attribute = "some stuff"

Let’s see how AM accomplishes this. Take a look at action_mailer/adv_attr_accessor.rb:

  
module ActionMailer
  module AdvAttrAccessor #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods #:nodoc:
      def adv_attr_accessor(*names)
        names.each do |name|
          ivar = "@#{name}"

          define_method("#{name}=") do |value|
            instance_variable_set(ivar, value)
          end

          define_method(name) do |*parameters|
            raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
            if parameters.empty?
              if instance_variable_names.include?(ivar)
                instance_variable_get(ivar)
              end
            else
              instance_variable_set(ivar, parameters.first)
            end
          end
        end
      end
    end
  end
end

The main part of all of that is the adv_attr_accessor(*names) definition. What Rails is doing here is going over each name (or attribute) you want to create and then doing the following:

  • Dynamically creating an instance variable name.
  • Dynamically creating a method= method (or setter method) for the attribute.
  • Dynamically creating a method method (or getter method) for the attribute, but (and this is where the magic is) adding the ability to have 0 or 1 parameters (instead of the normal 0 parameters that Ruby normally provides). If the parameters to the method is 0 then it just gets the instance variable (attribute) as normal, if it is 1, then it sets the instance variable to the parameter (like the normal method= method does).

That’s how we get the sugar coated ability to write obj.some_attribute "some stuff" for our AM methods.

Ok, now back to initialize_defaults. So we see the attributes getting initialized, let’s see how our example would initialize:

method_name           => contact_form
charset               => "utf-8"
content_type          => "text/plain"
implicit_parts_order  => ["text/html", "text/enriched", "text/plain"]
template.inspect      => "contact_form"
default_template_name => "contact_form"
action_name           => contact_form
mailer_name           => "message_mailer"
parts                 => []
headers               => {}
body                  => {}
mime_version          => "1.0"

Cool, now that we got that all initialized, let’s keep going. Back to create!:

(line 458 action_mailer/base.rb)
def create!(method_name, *parameters) #:nodoc:
  initialize_defaults(method_name)
  __send__(method_name, *parameters)
  ...
end

The next line calls __send__ with the method_name and parameters that we passed in originally. The reason __send__ is called is just in case you override the normal send method. This is a nice lesson for you newcomers to Rails. DONT OVERRIDE CORE RUBY METHODS! Sorry for the yelling.

But, since we cant predict everything, more specifically, we cant predict what new rails developers will do with AM, we use the __send__ method the make sure we dynamically call method_name with the parameters and not an overridden send method in your customized AM class.

In our example, we have the following arguments to send:

method_name => "contact_form"
parameters => ["jake@somewhere.com", "Send this message"]

So Ruby sends the message “contact_form” to our instantiated AM model and since we defined contact_form in our mailer model Ruby will execute this method now. Let’s look at our mailer again:

class MessageMailer < ActionMailer::Base
  def contact_form(msg_from, message)
    subject    'Im contacting you'
    recipients "someemail@somedomain.com"
    from       msg_from
    sent_on    Time.now
    body       :message => message
  end
end

Now that we have seen how the adv_attr_accessors work we can see that all that our contact_form method (or any AM deliver method for that matter) is doing is setting all the proper attributes. Your view is just an HTML or plain text version of your email with some fancy ERB parsing to help make it dynamic.

Let’s keep going. We have just set several attributes using the dynamic __send__ method to call our contact_form method in our AM model. Now we are back to the create! method again:

   
(line 458 action_mailer/base.rb)
def create!(method_name, *parameters) #:nodoc:
  initialize_defaults(method_name)
  __send__(method_name, *parameters)

  unless String === @body
    if @parts.empty?
      Dir.glob("#{template_path}/#{@template}.*").each do |path|
        template = template_root["#{mailer_name}/#{File.basename(path)}"]
        
        next unless template && template.multipart?
        
        @parts << Part.new(
          :content_type => template.content_type,
          :disposition => "inline",
          :charset => charset,
          :body => render_message(template, @body)
        )
      end
      unless @parts.empty?
        @content_type = "multipart/alternative" if @content_type !~ /^multipart/
        @parts = sort_parts(@parts, @implicit_parts_order)
      end
    end
    ...
  end
  ...
end

The next thing we do is check to see whether our body attribute is a String. If not, we are going to do some processing. Let’s take a look and see what attributes we have set with our example:

@parts           => nil
@template        => "contact_form"

Now we know that (in our example) parts is empty and we are going to enter into that conditional statement. Now we are going to find all template views that match our template “contact_form”. template_root is a class method on AM that returns the view_path root (usually apps/views/). This method is copied from ActionView::Base and the root is set there.

Next, we will set the variable template to be the value of template_root when the key is the full path (with the mailer_name, in this case “message_mailer”) to the erb or rhtml file. What we get back, is an instance of ActionView::ReloadableTemplate wrapped around our view file. Let’s see that with our example:

template  => 
  #<ActionView::ReloadableTemplate:0x34a4a68 
    @base_path="message_mailer", 
    @template_path="message_mailer/contact_form.erb", 
    @_memoized_path=["message_mailer/contact_form.erb"], 
    @name="contact_form", 
    @filename="/example_app/app/views/message_mailer/contact_form.erb", 
    ...
    @load_path="/example_app/app/views">

Next, we see that we skip over any further processing unless we have a multipart message or have multiple views for our mailer method (thereby making it a multipart message). That takes us out of the loop (for our example) and down to

unless @parts.empty?
  @content_type = "multipart/alternative" if @content_type !~ /^multipart/
  @parts = sort_parts(@parts, @implicit_parts_order)
end

In our example, @parts is empty so we are pretty much done with this area. Let’s take a look and see what happens next:


def create!(method_name, *parameters) #:nodoc: ... unless String === @body if @parts.empty? ... end template_exists = @parts.empty? template_exists ||= template_root["#{mailer_name}/#{@template}"] @body = render_message(@template, @body) if template_exists ... end end

Basically, what we are doing here is making sure we have either an implied template or dynamically generated template. This is mainly for multipart messages. In our example, since there are still no parts, template_exist gets set to true. Since it is already true, the next line gets skipped and we go directly to rendering the message into our protected attribute @body. Let’s see what render_message looks like:

(line 553 action_mailer/base.rb)
def render_message(method_name, body)
  if method_name.respond_to?(:content_type)
    @current_template_content_type = method_name.content_type
  end
  render :file => method_name, :body => body
ensure
  @current_template_content_type = nil
end

When we call render_message in create!, we call it with @template and @body, let’s see what those get set to inside of render_message for our example:

@template   = method_name     => "contact_form"
@body       = body            => {:message=>"Send this message"}

At the beginning of render_message @body is set to the hash of instance variables that we set in our contact_form method in our mailer. Once we are finished processing, that hash will be gone and @body will be replaced with the full rendered message.

That’s it for now. Stay tuned for next week for Part II where we will pick back up and continue our trek through ActionMailer#deliver and get real deep into rendering.


Super InPlace Controls has gone through some upgrades.

We’ve Moved

First and foremost, we have moved the code from google to github.

To install run


script/plugin install git://github.com/flvorful/super_inplace_controls.git

Rails 2.3 Support

Tested with Rails 2.3 and 2.1

Better Jquery Support

We have updated the code to work better with jQuery. Now, in_place_date_selector, uses the jQuery datepicker method if available. JRails is required for jquery support.

Better Validation

Validations were a problem with SIPC, but no longer. Now, if you dont have an error div, SIPC will skip over the rendering of the error box and just add the class “fieldWithError” to the proper input tag.

Overall cleanup

Cleaned up the code in general and made it less brittle.

Check out the demos and the docs at our Open Source Site and stay tuned for some new plugins we’ve been working on.

—jake