Real world usage of ActionMailer for account activation
back
Previous article:
ActiveRecord class association for the same object
view
Next article:
Search engine friendly link_to function in Rails
view
I have recently had to use the ActionMailer action pack in Ruby on Rails for a fairly typical requirement. I needed to implement a registration function for the logon page which sends an email to the new user with an activation link. Once they click on the link, it activates the account, giving them access the website. The other similar requirement was to remotely moderate comments and discussions sent into my site from my mailbox instead of checking the site regularly. When a visitor leaves a comment, a mail will be dispatched to me with a couple of links to either allow or disallow the comment. ActionMailer seems to be the obvious candidate to achieve this. First of all, the documentation for ActionMailer that comes with Rails is simply inadequate. It includes one simple use case, but left me scratching my head about what was possible and what was not. I experimented a bit with it, and found it confusing that even though it works in a similar way to other components of the Rails MVC framework, it is not quite the same as them. For a start, ActionMailer does not require a controller, in fact, the only way you can invoke its services is via the class methods, not instance methods. Next ActionMailer is not exactly an ActiveRecord object, even though it resides in the models directory. Thirdly, ActionMailer passes its instance variables to the templates in the views directory in a completely different manner than to the controller objects. I finally managed to get it to work by trial and error, with a lot of undelivered mails along the process. So, in order to send a mail with a URL people can click on to perform a one off action, after which the URL will self destruct and can never be re-used again, how is it done ? You need to generate something unique on the fly, a token, append that to the activation URL, and then send it along in the mail. You need to also cloak the information you are sending to ensure the URL does not give away the internal workings of your application (so something like http://www.mysite.com/activate?user=joeblog is not the best approach, as all the spam bots will easily be able to automate account registration and use your server to send ads for the blue pills to everyone at AOL). To prepare for the return trip of the token, you need to have a storage place for it in either your file system or database against the record you are awaiting validation. Then once the link is activated from the mail you sent (which incidentally is a good way to ensure the mail account belongs to some real person if it can be delivered), the record is updated and the token is destroyed so it can not be re-used. How is this done with the help of ActionMailer ? First we need to generate a token using something unique. How about the time of the day ? or maybe the MAC address of your server plus the time. Whatever it is, we need to convert it into something URL friendly. So in your controller, once the registration submission is returned, you can do: class MyController < ApplicationController def new_account @user = User.new(params[:user]) @user.active = false @user.validation_key = Digest::SHA1.hexdigest("#{Time.now}") # persist the key for the return trip @user.save MyMailer::deliver_new_registration(@user, self) end end And when the link is activated, you can do: class MyController < ApplicationController def activate_account @user = User.find_by_validation_key(params[:key]) if ! @user.nil? @user.active = true # ensure the key can not be re-used @user.validation_key = nil @user.save end end end The MyMailer class resides in the file app/models/my_mailer.rb and looks like: class MyMailer < ActionMailer::Base def new_registration(user,controller) @recipients = user.name @subject = "Please activate your account" @from = "me@mysite.com" url = controller.url_for(:action => 'activate_account', :key => user.validation_key, :only_path => false) @body = { :title => "My new super website", :real_name => user.real_name, :url => url } end end As you can see, the instance variables to be passed to the templates in the views directory will have to shredded out into string variables, as I tried to pass the whole user parameter in the @body and never got it to appear at the other end, as in the case of the controllers passing their instance variables to the template. For the above MyMailer class, a template of the name app/views/my_mailer/new_registration.text.html.rhtml needs to be created: <h2><%= @title %> Validation</h2> Hello <%= @real_name %><p> Please click on the link below to access my site:<br> <a xhref='<%= @url %>'><%= @url %></a><p> Enjoy the experience<p>. If you are a perfectionist, you can also create another file called app/views/my_mailer/new_registration.text.plain.rhtml, which contains the same information as above, but without the html tags. This is for the benefit of those of us whole are still using mail clients which do not render HTML (yes they do exist !).
back
Previous article:
ActiveRecord class association for the same object
view
Next article:
Search engine friendly link_to function in Rails
view
|