Skip to content

Change Email Address

gundestrup edited this page Sep 13, 2010 · 1 revision

Add the ability for users to change their email address to something other that what they registered with. Users must be logged in to perform this operation and they need to verfiy the action via email before the change takes place.
Migration

script/generate migration New Email Address

class NewEmailAddress < ActiveRecord::Migration
def self.up
add_column :users, :new_email, :string
add_column :users, :email_activation_code, :string, :limit => 40

end def self.down remove_column :users, :email_activation_code remove_column :users, :new_email end

end

Model

user.rb

def change_email_address(new_email_address) @change_email = true self.new_email = new_email_address self.make_email_activation_code end def activate_new_email @activated_email = true update_attributes(:email=> self.new_email, :new_email => nil, :email_activation_code => nil) end def recently_changed_email? @change_email end protected def make_email_activation_code self.email_activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join ) end

user_observer.rb

def after_save(user)

user.recently_activated?
UserNotifier.deliver_change_email(user) if user.recently_changed_email?


end

user_notifier.rb

def change_email(user) setup_email(user) @recipients = “#{user.new_email}” @subject += ‘Request to change your email’ @body[:url] = “http://#{SITE_URL}/account/activate_new_email/#{user.email_activation_code}” end

Controller

account_controller.rb

def change_email return unless request.post? unless params[:email].blank? self.current_user.change_email_address(params[:email]) self.current_user.save @changed = true else flash[:notice] = “Please enter an email address” end end def activate_new_email flash.clear return unless params[:id].nil? or params[:email_activation_code].nil? activator = params[:id] || params[:email_activation_code] @user = User.find_by_email_activation_code(activator) if @user and @user.activate_new_email redirect_back_or_default(:controller => ‘/account’, :action => ‘index’) flash[:notice] = “The email address for your account has been updated.” else flash[:notice] = “Unable to update the email address.” end end

Views

account/change_email.rhtml

<% unless @changed >

Change account email address – Step 1 of 3


Enter the new email address that you wish to use for this account



<= start_form_tag >

Email Address
<

= text_field_tag ‘email’ ><= submit_tag ‘Update my email’ %>

<% else %>

Change account email address – Step 2 of 3

Thanks. An email has been sent to the address provided (<%= self.current_user.new_email %>).

Please follow the instructions contained within the email to complete this process.

<% end %>

user_notifier/change_email.rhtml

Please visit this url to update your account to use the new email address:

<%= @url %>

account/activate_new_email.rhtml

  1. empty

Suggestion from Barry Hess:

I wanted this new email address field to undergo the same validation as the email address entered when the user signs up for my site. To make this happen, I added some new validation steps and slightly modified controller action.
Model

I have the following email validations in user.rb:

validates_presence_of :email validates_length_of :email, :within => 6..100 validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/

For the new email address, I don’t want any validation to occur unless I’m specifying a new email address through my “update email” screens. Generally speaking, if there is no value given for user.new_email, then I don’t wish to validate. However, if user.new_email has something in it, validation needs to occur. The following code makes this happen:

validates_length_of :new_email, :within => 6..100, :if => :new_email_entered? validates_format_of :new_email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/, :if => :new_email_entered?
  1. Assures that updated email addresses do not conflict with
  2. existing email addresses.
    def validate
    if User.find_by_email(new_email)
    errors.add(new_email, “is already being used”)
    end
    end
protected def new_email_entered? !self.new_email.blank? end

(I’m sure that validates_format_of code can be DRY’d up.)
Controller

I also updated the account_controller.rb’s change_email action a bit. Since I’m now serving up validation messages, I want to avoid modifying @changed unless validation was successful. The check for a blank parameter is still appropriate as there is no inherent validation in the model to check for this.

I will also be using the form_for helper in the view to relate the form with the user model.

def change_email @user = self.current_user return unless request.post? unless params[:user][:email].blank? @user.change_email_address(params[:user][:email]) if @user.save @changed = true end else flash[:notice] = “Please enter an email address” end end

View

account/change_email.rhtml

<%= error_messages_for :user %>

<% unless @changed -%>


Step 1 of 3

<% form_for :user do |f| -%>

Email <%= f.text_field :email %> <%= submit_tag “Update email!” %> <% end -%>

<% else -%>


Step 2 of 3

Thanks. An email has been sent to the address provided (<%= self.current_user.new_email %>).

Please follow the instructions contained within the email to complete this process.

<% end -%>