Change Email Address
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
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 ) enduser_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}” endController
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 endViews
account/change_email.rhtml
<% unless @changed >
Enter the new email address that you wish to use for this account
<= start_form_tag >
Email Address
<
<% else %>
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
- 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?
- Assures that updated email addresses do not conflict with
- existing email addresses.
def validate
if User.find_by_email(new_email)
errors.add(new_email, “is already being used”)
end
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 endView
account/change_email.rhtml
<%= error_messages_for :user %>
<% unless @changed -%>
Email <%= f.text_field :email %> <%= submit_tag “Update email!” %> <% end -%>
<% else -%>
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 -%>