What's New in Edge Rails: Build URLs in Your ActionMailers (and other non-controllers)

Posted by ryan
at 5:03 PM on Wednesday, August 23, 2006

For a long time there was no good way to generate a URL within an ActionMailer. If you wanted to send a registration email when a user signs up for your application you would have had to pass in the url to be included in the email:


class MyController < ApplicationController
  def register
    # .. registration logic ...
    UserMailer.deliver_registration(user, home_url)
  end
end

class UserMailer < ActionMailer::Base
  def registration(user, url)
    @body = { 'user' => user, 'home' => url }
  end
end

This is hardly a hack, but it’s not as nice as it could be. It would be nice if the mailer itself could generate the url – or perhaps if the email template itself could generate the url? That day has come with the new ActionController::UrlWriter module.

Just include ActionController::UrlWriter in your mailer and you get access to all the familiar url_for and even the named url methods (home_url in this example):


class UserMailer < ActionMailer::Base
  include ActionController::UrlWriter
  def registration(user)
    @body = { 'user' => user, 'home' => home_url }
  end
end

tags: , ,

Comments

Leave a response

  1. BrittainAugust 23, 2006 @ 07:32 PM
    Will this module mix-in a model?
  2. Jon MaddoxAugust 23, 2006 @ 08:30 PM
    Maybe I'm being ignorant, but why do we have to explicitly include this? Why not just make it available in our mailers automatically like our controllers and views?
  3. Chu YeowAugust 23, 2006 @ 09:14 PM
    Ooh nice find... I'd wanted to do this recently (i.e. use url_for in an Action Mailer template) and ended up passing the URL in from the controller like you mentioned. It isn't a biggie or a hack at all like you said, but this module does play nicer. Brittain: From what I can see from the code, yup, it should mixin to a AR model as well.
  4. JoostAugust 23, 2006 @ 11:31 PM
    Excellent addition! I've always wondered why this couldn't be done. For me it would be even better if it were mixed in directly, but this way is fine too. Thanks!
  5. Ryan DaigleAugust 24, 2006 @ 03:47 AM
    Jon, you can always include it yourself in your @environment.rb@:
    
    class ActionMailer::Base
      include ActionController::UrlWriter
    end
    
    
    Brittain, you can include this in your models *but*, doing so would be a violation of the MVC pattern. You don't want your models to know anything about URLs - you only want them to represent your domain. The only layers that should know that there's even a web application at play are the views and controllers. So, yes, you can include this in your models - but, no, don't do it!
  6. BrittainAugust 24, 2006 @ 06:22 AM
    Ryan, I appreciate the counsel, but we're comfortable with the design rqmts of exposing our model to the URL. Read here: http://lists.rubyonrails.org/pipermail/rails/2006-July/057239.html On the other hand, if you've other input on that discussion, I welcome hearing it. Thanks.
  7. UlyssesAugust 24, 2006 @ 02:20 PM
    Jon, The real reason it's not included automatically is that I coded that up in 10 minutes and didn't (and don't) have time to fully hook it in. I was expecting someone else in core to do that, but now might be your chance to beat them to it.
  8. DHHAugust 24, 2006 @ 02:34 PM
    I just did that. It's automatically included now.
  9. Ryan DaigleAugust 24, 2006 @ 04:12 PM
    Brittain, Just read "the post":http://lists.rubyonrails.org/pipermail/rails/2006-July/057374.html where you nicely summarize the issue in question. In that circumstance I would say that I don't feel like it would be in violation of the MVC pattern to have the _observer_ of the model class include the @UrlWriter@ module. That seems like a nice compromise, providing a seperate layer from the model to contain web-specific properties?
  10. DGMAugust 24, 2006 @ 06:33 PM
    Nice. I hadn't tried to use it before, but I kinda assumed it was there all along. It's kind of an obvious thing to do...
  11. Peter CooperAugust 25, 2006 @ 11:06 AM
    All that said, it's been possible to do this for a long time now with "helper ActionView::Helpers::UrlHelper" in your ActionMailer classes then passing self through to the view. This is marginally cleaner, and certainly a good idea, but "For a long time there was no way to generate a URL within an ActionMailer" isn't entirely true.
  12. Ryan DaigleAugust 25, 2006 @ 01:06 PM
    “For a long time there was no _*good*_ way to generate a URL within an ActionMailer” Thanks, Peter.
  13. SamAugust 31, 2006 @ 10:32 AM
    I don't see what's wrong with putting knowledge of urls into the mailer. After all, the fact that a mailer even exists as a model exposes the application as a web-app. Urls are to email what what sql is to a database. How effective would a model be without the ability to generate sql? Models are not an entity unto themselves. They are a pass through layer and the backend can be email, sql, filesystem, telephone voice, or robot and the model needs to be an expert in its communication with the backend so the programmer doesn't have too.
  14. Ryan DaigleAugust 31, 2006 @ 11:41 AM
    Sam, I don't know that people are debating the merits of putting URL logic into Mailers - I think the question is having your domain models know about URLs...
  15. floydSeptember 20, 2006 @ 07:53 AM
    The valid reason for mailers to know about urls is the fact that they live in blurry territory, pseudo-models with views. However, because they use views, they should have access to all the things views do, urls being a critical subset. Models are nowhere near blurry territory. As the M in MVC, they-by definition-do not need access to the view logic helpers. If there is a fringe situation that requires a url in a model, hard code it, pass it in as a string or use some other workaround meited by the fringe case.
  16. YanMarch 08, 2007 @ 10:36 PM

    I think it’s ridiculous that Mailers are treated liek model objects. They are controllers plain and simple. They control the logic of generating the mailer view. As such they need power to call all things a normal controller does..there need to be built in view helpers, url generation, named routes, etc. Why we have to go through convoluted maneuvers to get this to work doesn’t seem right. Mailers are underdeveloped in rails. There is a nice mailer layout plugin out there now finally that lets you do the most common sense thing for emails…have a common layout. Please rails gods, spend some more time on the mailers and make them into full citizens of railsville :-)

  17. JohnMarch 23, 2007 @ 03:53 PM

    “They are controllers plain and simple.”

    Well, it is really no more of a controller than ActiveRecord is. AR creates a SQL “view” and if you want to include a URL in that SQL statement you have to jump through the exact same hoops as ActionMailer to get it there.