Created
October 14, 2008 23:02
-
-
Save agibralter/16815 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Processing UsersController#create (for 127.0.0.1 at 2008-10-14 18:53:56) [POST] | |
Parameters: {"user"=>{"terms_and_conditions"=>"1", "password_confirmation"=>"password", "gender"=>"", "year"=>"", "password"=>"password", "news"=>"1", "email"=>"bob@fat.com"}, "commit"=>"join the project", "action"=>"create", "controller"=>"users"} | |
[4;35;1mSQL (0.000238)[0m [0mSELECT `email` FROM `users` WHERE (`users`.email = 'bob@fat.com') [0m | |
[4;36;1mSQL (0.000130)[0m [0;1mSELECT `email` FROM `users` WHERE (`users`.email = 'bob@fat.com') [0m | |
[4;35;1mUser Create (0.000205)[0m [0mINSERT INTO `users` (`salt`, `questions_count`, `updated_at`, `dob`, `crypted_password`, `state_password`, `gender`, `responses_count`, `multipurpose_code`, `email_verified_at`, `year`, `state_email`, `display_name`, `login`, `email`, `created_at`, `news`) VALUES('c0d1e5cfdsa159229f753baf5b72dbb865e249e2', 0, '2008-10-14 22:53:56', NULL, 'c8398e31f1d73249902dfds273abf165da1559a0', 'known', '', 0, '9fd5aded6db6b8fps0b7ea9a35035d5d36be38c2', NULL, '', 'unverified', NULL, NULL, 'bob@fat.com', '2008-10-14 22:53:56', 1)[0m | |
[4;36;1mUser Load (0.000174)[0m [0;1mSELECT * FROM `users` WHERE (`users`.`id` = 1011) [0m | |
Sent mail to bob@fat.com |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'digest/sha1' | |
class User < ActiveRecord::Base | |
########################### | |
# state | |
########################### | |
state_machine :state_email, :initial => 'fresh' do | |
before_transition :to => 'unverified', :do => :make_email_verification_code | |
before_transition :to => 'verified', :do => :set_email_verified_attributes | |
event :unverify do | |
transition :to => 'unverified', :from => %w(unverified verified fresh) | |
end | |
event :verify do | |
transition :to => 'verified', :from => %w(unverified verified fresh) | |
end | |
end | |
state_machine :state_password, :initial => 'known' do | |
before_transition :to => 'forgotten', :do => :make_forgot_password_code | |
before_transition :to => 'known', :do => :clear_forgot_password_code | |
event :forgets_password do | |
transition :to => 'forgotten', :from => 'known' | |
end | |
event :remembers_password do | |
transition :to => 'known', :from => 'forgotten' | |
end | |
end | |
def verified? | |
state_email == 'verified' | |
end | |
def unverified? | |
state_email == 'unverified' | |
end | |
def forgets_password? | |
state_password == 'forgotten' | |
end | |
def remembers_password? | |
state_password == 'known' | |
end | |
########################### | |
# attributes | |
########################### | |
# Virtual attribute for the unencrypted password | |
attr_accessor :password, :terms_and_conditions | |
attr_accessible :login, | |
:email, | |
:password, | |
:password_confirmation, | |
:terms_and_conditions, | |
:news, | |
:gender, | |
:display_name, | |
:year | |
########################### | |
# validations | |
########################### | |
validates_presence_of :email, :message => "missing email" | |
validates_length_of :login, :within => 3..40, :allow_blank => true, :message => "login must be between 3 and 40 characters" | |
validates_length_of :email, :within => 3..100, :allow_blank => true, :message => "email must be between 3 and 100 characters" | |
validates_uniqueness_of :login, :allow_blank => true, :message => "login is taken" | |
validates_uniqueness_of :email, :allow_blank => true, :message => "email is taken; please contact us if this seems like a mistake" | |
validates_format_of :login, :with => /^[^@]+$/, :allow_blank => true, :message => "invalid login (cannot be an email address)" | |
validates_format_of :email, :with => /(\S+)@(\S+)/, :allow_blank => true, :message => "invalid email" | |
validates_presence_of :password, :if => :password_required?, :message => "missing password" | |
validates_presence_of :password_confirmation, :if => :password_required?, :message => "missing password confirmation" | |
validates_length_of :password, :within => 4..40, :if => :password_required?, :missing => "password must be between 4 and 40 characters" | |
validates_confirmation_of :password, :if => :password_required?, :message => "passwords do not match" | |
validates_presence_of :terms_and_conditions, :on => :create, :message => "please read and agree to the terms and conditions" | |
validates_format_of :terms_and_conditions, :with => /^1$/, :on => :create, :message => "please read and agree to the terms and conditions" | |
validates_inclusion_of :news, :in => [true,false], :allow_blank => true | |
validates_inclusion_of :gender, :in => %w(m f), :allow_blank => true, :message => 'must select m or f' | |
validates_format_of :year, :with => /^\d{4}$/, :allow_blank => true, :message => 'must select a year' | |
########################### | |
# callbacks | |
########################### | |
before_save :encrypt_password, :nullify_blanks, :check_for_new_unverified_email | |
########################### | |
# public methods | |
########################### | |
# Updates attributes while also marking that the email address has been | |
# verified. | |
def verify_email_and_update_password_attributes(attributes) | |
self.verify(false) # the false flag tells SM not to save | |
self.remembers_password(false) # the false flag tells SM not to save | |
self.update_attributes(attributes.delete_if {|key, val| key.to_sym != :password && key.to_sym != :password_confirmation}) | |
end | |
# Authenticates a user by their login name and unencrypted password. Returns | |
# the user or nil. Also calls remembers_password in CASE 1 of | |
# forgot_password?. This is the main (non-fatal) flaw of the combined | |
# email-verification/forgotten password system. | |
def self.authenticate(login_or_email, password) | |
u = find :first, :conditions => ["#{login_or_email =~ /@/ ? 'email' : 'login'} = ?", login_or_email] | |
if u && u.authenticated?(password) | |
u.remembers_password | |
u | |
end | |
end | |
# Encrypts some data with the salt. | |
def self.encrypt(password, salt) | |
Digest::SHA1.hexdigest("--#{salt}--#{password}--") | |
end | |
# Encrypts the password with the user salt | |
def encrypt(password) | |
self.class.encrypt(password, salt) | |
end | |
def authenticated?(password) | |
crypted_password == encrypt(password) | |
end | |
# A customized attribute writer that sets whether or not the user is | |
# changing the email address. | |
def email=(new_email) | |
unless (self.email == new_email) || new_email.blank? | |
@email_updated = true | |
write_attribute(:email, new_email) | |
end | |
end | |
########################### | |
# protected methods | |
########################### | |
protected | |
# Before filter that encrypts a plain text password into a crypted_password. | |
def encrypt_password | |
return if password.blank? | |
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record? | |
self.crypted_password = encrypt(password) | |
end | |
# Before filter that ensures "NULL" values instead of blanks in DB. | |
def nullify_blanks | |
self.login = nil if login.blank? | |
write_attribute(:email, nil) if email.blank? | |
end | |
# If there's an email address and it's different than the one that existed | |
# when the record was loaded we need to validate it again. | |
def check_for_new_unverified_email | |
unverify(false) if @email_updated && !self.unverified? | |
end | |
def make_email_verification_code | |
self.multipurpose_code = new_multipurpose_code | |
self.email_verified_at = nil | |
end | |
def make_forgot_password_code | |
self.multipurpose_code = new_multipurpose_code | |
end | |
def clear_forgot_password_code | |
self.multipurpose_code = nil if self.verified? | |
end | |
# Creates a random hash code that has oh so many uses. | |
def new_multipurpose_code | |
Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join ) | |
end | |
# Called to set the attributes associated with an email verification. | |
def set_email_verified_attributes | |
self.email_verified_at = Time.now.utc | |
self.multipurpose_code = nil | |
end | |
# Some helper methods used in validation. | |
def present_email? | |
!email.blank? | |
end | |
def present_login? | |
!login.blank? | |
end | |
def blank_email? | |
email.blank? | |
end | |
def blank_login? | |
login.blank? | |
end | |
def password_required? | |
crypted_password.blank? || !password.blank? | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UserObserver < ActiveRecord::Observer | |
# def after_save(user) | |
# MailerWorker.asynch_mail_forgot_password_message(:user_id => user.id) if user.recently_forgot_password? | |
# MailerWorker.asynch_mail_email_verification_message(:user_id => user.id) if user.new_unverified_email? | |
# end | |
def after_unverify(user, from_state, to_state) | |
MailerWorker.asynch_mail_email_verification_message(:user_id => user.id) | |
end | |
def after_forgets_password(user, from_state, to_state) | |
MailerWorker.asynch_mail_forgot_password_message(:user_id => user.id) | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UsersController < ApplicationController | |
before_filter :login_required, :except => [:new, :create] | |
def new | |
@user = User.new | |
end | |
def create | |
@user = User.new(params[:user]) | |
if @user.save | |
self.current_user = @user | |
flash[:notice] = "thank you for joining; please check your email for a confirmation" | |
redirect_to home_path | |
else | |
flash[:warning] = "please fix the errors below" | |
render :action => 'new' | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment