Activation Emails with Restful Authentication and Acts_As_State_Machine

December 17, 2007

Skip to the code

Update: December 18, 2007: less code, same result

Summary

If you use the Restful Authentication plugin with the –include-activation and –stateful options (see Acts_As_State_Machine and the post by Jonathan Linowes which united the two plugins), you’ll need to make a couple modifications to the code after you run the generator in order to keep the user activation email from being sent simultaneously with the user signup notification email.

Problem

The UserObserver code sends an activation email during the after_save callback if the user’s state is “pending.” Without activation the state transition goes from pending to active and we can leave the code alone. With activation, however, we introduce another state: the “state of being notified that our account has been created.”

If we change the UserObserver to watch for an “active” state instead of “pending,” the user will receive an activation email every time we save the user’s record and that puts the user into the state of “I’m about to adios this annoying website.”

Suggested Solution

We add a temporary state “notified” between pending and active that allows us to send the activation email at the right time with little disruption to the original restful authentication code.

Just before we “activate!” the user in the UserController, we “notify!” them, putting them in the “notified” state, save the user, which calls the after_save callback, which sends the activation email, and then return control to the UserController, which calls “activate!,” putting the user in the “active” state.

The Code

Changes to User.rb

Update: I removed the :do_notify code which was redundant (state transitions automatically save the record)

  state :notified
 
  event :notify do
    transitions :from => :pending, :to => :notified
  end
 
  event :activate do
    transitions :from => :notified, :to => :active
  end
 
  event :suspend do
    transitions :from => [:passive, :pending, :notified, :active], :to => :suspended
  end
 
  event :delete do
    transitions :from => [:passive, :pending, :notified, :active, :suspended], :to => :deleted
  end

Changes to User_Observer.rb

  def after_save(user)
    UserMailer.deliver_activation(user) if user.notified?
  end

Changes to User_Controller.rb

  # In the activate method,
  # insert the notify! line right before the activate! line
  current_user.notify!
  current_user.activate!

If I’ve left something out, or if you have a better solution, please say so below. Cheers!

9 Comments

  1. Jamie spoke thusly:

    Hi Harry,

    Can one not simply change …

    def after_save(user)
    UserMailer.deliver_activation(user) if user.pending?
    end

    to …

    def after_save(user)
    UserMailer.deliver_activation(user) if user.active?
    end

    I’m having another problem, which is that my activation_code isn’t making its way into the activation e-mail nor the database, even though it’s set correctly within User#make_activation_code. I’ve just started to try to figure it out though…

    Good luck!
    Jamie

  2. Harry spoke thusly:

    Jamie,

    Unfortunately, no. If you change the code to user.active, you get a confirmation email every time you save the user, even if the user is just changing his password.

  3. Juha Syrjälä spoke thusly:

    Hi,

    I was just wrestling with this same problem. Your solution works just fine. It makes an extra update to database when user is activated, but that is not a problem for me. Thanks!

    Juha

  4. emil tin spoke thusly:

    in my view, the problem is that emails should be triggered by changes in the state, not bu saves. that’s the purpose of acts_as_state_machine. so using an observer, and checking for states is just causing confusion. my solution (which seems toworks) is to get rid of the observer, and send the emails from the state change methods in User. (does anyone know if this might have any bad side effects?)
    an improvement could be to create an new state change observer class that could be attached and would listen for state changes, instead of activerecord callbacks.
    another problem with the code was empty activation codes we. here the problem is that the if you set an initial state, the corresponding state change method is called only AFTER the initial saving of the object, (which is somewhat counterintuitive), so the activation code was generated after the save and was lost. the solution is to start the User as passive, and do an pending! right after the initial save. this way there’s no need for the temporary notified state.

  5. Harry spoke thusly:

    I like the idea of using the state instead of the callback method which polls every time but may not be used. Whereas, when you transition to the state which requires an email to be sent, you know the email should be sent at that time.

    Do you have code you can share, Emil?

  6. emil tin spoke thusly:

    Sure. Here’s what so far seems to work for me. Use at your own risk! If you see any problems, I would be interested in knowing about it. I’ve also had to do number of other changes to how the activation works.
    Everything to do with UserObserver has been removed. Remember to remove the reference in environment.rb too.

    User.rb:
    acts_as_state_machine :initial => :passive

    state :passive
    state :pending, :enter => :do_pending
    state :active, :enter => :do_activate
    state :suspended
    state :deleted, :enter => :do_delete

    [....]

    def do_pending
    make_activation_code
    UserMailer.deliver_signup_notification(self)
    end

    def do_activate
    self.activated_at = Time.now.utc
    self.deleted_at = self.activation_code = nil
    UserMailer.deliver_activation(self) #this email could be left out
    end

    user_controller.rb:
    def create
    cookies.delete :auth_token
    # protects against session fixation attacks, wreaks havoc with
    # request forgery protection.
    # uncomment at your own risk
    # reset_session
    @user = User.new(params[:user])
    @user.save! #save first
    @user.register! #then change state
    current_user = @user
    #at this point, the user is registered and logged in but not authorized,
    #because the account is not yet activated.
    #redirect to activation page
    redirect_to :controller => :account, :action => ‘activate’
    rescue ActiveRecord::RecordInvalid
    render :action => ‘new’
    end

  7. Ches spoke thusly:

    I followed emil’s suggestion to eliminate the Observer and send emails upon state change callbacks (much cleaner, thanks emil!). I did so in essentially the same way as the code he shared.

    I also added a ‘forgetful’ state for handling password resetting with emails. I’ll get around to blogging that code sometime soon :-)

    Just wanted to share though that, as emil alluded to above, the differences in the way callbacks work for new vs. existing model objects is the tricky part in working with acts_as_state_machine. The section on Callbacks at this link has been the most useful summary reference I’ve found:

    http://rails.aizatto.com/category/plugins/acts_as_state_machine/

  8. Harry Love spoke thusly:

    Thanks for the suggestions and the link, Ches. You reminded me that I still need to handle the forgetful state. Being able to eliminate the observers is a nice way to go.

  9. Saurav spoke thusly:

    Hi,
    I want to send the welcome email only after the user logs in for the first time. Can anybody give me some suggestion on how to do it. Rite now Im using
    def after_save(user)

    UserMailer.deliver_activation(user) if user.pending?

    end

    and this method sends the welcome email only when the user activates himself.

Say, say, say, what you want.