Created
August 20, 2012 19:45
-
-
Save phillipkoebbe/3407135 to your computer and use it in GitHub Desktop.
Small Rails code sample
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 User < ActiveRecord::Base | |
UNKNOWN_EMAIL = "That email address isn't registered." | |
WRONG_PASSWORD = 'Password is incorrect.' | |
LOGIN_SUCCESSFUL = "You're in!" | |
attr_accessor :password | |
attr_accessible :email, :password, :country_id | |
belongs_to :country | |
has_many :budgets, :dependent => :destroy | |
validates :email, :presence => true, :uniqueness => true, :email => true | |
validates :password, :presence => true, :on => :create | |
before_save :ensure_password_digest, :ensure_uuid | |
def set_country!(ip) | |
return if self.country_id | |
geo_result = GeoIp.geolocation(ip, :precision => :country) | |
return unless geo_result | |
country = Country.find_by_code geo_result[:country_code] || '' | |
return unless country | |
self.update_attribute :country_id, country.id | |
end | |
def User.authenticate(email, password) | |
user = find_by_email(email) | |
return nil, User::UNKNOWN_EMAIL unless user | |
# have a user, does the password match? | |
return (BCrypt::Password.new(user.password_digest) == password) ? [user, User::LOGIN_SUCCESSFUL] : [nil, User::WRONG_PASSWORD] | |
end | |
protected | |
def ensure_password_digest | |
# since password is implemented using attr_accessor, password.present? should | |
# be true only when it has been changed through user interaction. | |
self.password_digest = BCrypt::Password.create(password) if password.present? | |
end | |
def ensure_uuid | |
# since password is implemented using attr_accessor, password.present? should | |
# be true only when it has been changed through user interaction, and we want | |
# to change the uuid whenever the password changes | |
self.uuid = UUIDTools::UUID.random_create.to_s if self.uuid.nil? || password.present? | |
# make sure uuid is not already used | |
while User.exists?(:uuid => self.uuid) | |
self.uuid = UUIDTools::UUID.random_create.to_s | |
end | |
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
require 'model_helper' | |
describe User do | |
it 'should belong to country' do | |
User.new.should respond_to(:country) | |
end | |
it 'should have many budgets' do | |
User.new.should respond_to(:budgets) | |
end | |
it 'should destroy budgets when self is destroyed' do | |
budget = Factory.create :budget | |
budget.user.destroy | |
Budget.count.should == 0 | |
end | |
describe 'email' do | |
it 'should be required' do | |
user = Factory.build :user, :email => nil | |
user.should have_at_least(1).error_on(:email) | |
end | |
it 'should be unique' do | |
user_1 = Factory.create :user | |
user_2 = Factory.build :user, :email => user_1.email | |
user_2.should have_at_least(1).error_on(:email) | |
end | |
it 'should accept valid emails' do | |
valid_emails = [ | |
'[email protected]', | |
'[email protected]', | |
'[email protected]', | |
'[email protected]', | |
'[email protected]', | |
'[email protected]', | |
'[email protected]' | |
] | |
user = Factory.build :user | |
valid_emails.each do |email| | |
user.email = email | |
user.should have(:no).errors_on(:email) | |
end | |
end | |
it 'should not accept invalid emails' do | |
invalid_emails = [ | |
'user', | |
'example.com', | |
'@example.com', | |
'[email protected]', | |
'[email protected]', | |
'user@something@[email protected]' | |
] | |
user = Factory.build :user | |
invalid_emails.each do |email| | |
user.email = email | |
user.should have_at_least(1).error_on(:email) | |
end | |
end | |
end # describe 'email' | |
describe '#password' do | |
it 'should be required on create' do | |
user = Factory.build :user, :password => nil | |
user.should have_at_least(1).error_on(:password) | |
end | |
it 'should not be required on update' do | |
# password is implemented as an attr_accessor, so when a user object | |
# is loaded from the database, user.password should be nil. if something | |
# else is changed, user.password should still be nil and should not cause | |
# a validation error | |
email = '[email protected]' | |
Factory.create :user, :email => email | |
user = User.find_by_email email | |
# sanity checks | |
user.password.should be_nil | |
user.password_digest.should_not be_nil | |
user.email = '[email protected]' | |
user.should have(:no).errors_on(:password) | |
end | |
end # describe '#password' | |
describe 'password_digest' do | |
it 'should be generated automatically' do | |
email = '[email protected]' | |
Factory.create :user, :email => email | |
user = User.find_by_email email | |
user.password_digest.should_not be_nil | |
end | |
it 'should be changed when password is changed' do | |
user = Factory.create :user | |
old_password_digest = user.password_digest | |
user.update_attributes :password => user.password.reverse | |
user.password_digest.to_s.should_not == old_password_digest.to_s | |
end | |
it 'should not be changed if new password is blank' do | |
user = Factory.create :user | |
old_password_digest = user.password_digest | |
user.update_attributes :password => nil | |
user.password_digest.to_s.should == old_password_digest.to_s | |
end | |
it 'should not be changed if new password is empty' do | |
user = Factory.create :user | |
old_password_digest = user.password_digest | |
user.update_attributes :password => ' ' | |
user.password_digest.to_s.should == old_password_digest.to_s | |
end | |
end # describe 'password_digest' | |
describe 'uuid' do | |
it 'should be generated automatically' do | |
email = '[email protected]' | |
Factory.create :user, :email => email | |
user = User.find_by_email email | |
user.uuid.should_not be_nil | |
end | |
it 'should be changed when password is changed' do | |
user = Factory.create :user | |
old_uuid = user.uuid | |
user.update_attributes :password => user.password.reverse | |
user.uuid.should_not == old_uuid | |
end | |
it 'should be unique' do | |
# how to test that a randomly generated value is unique? since the user is not supplying the value, | |
# we cannot do something like: | |
# | |
# it 'should be unique' do | |
# user_1 = Factory.create :user | |
# user_2 = Factory.build :user, :uuid => user_1.uuid | |
# user_2.should have_at_least(1).error_on(:uuid) | |
# end | |
user_1 = Factory.create :user | |
Factory.create :user | |
# look up user so attributes will not be in memory from object creation | |
user_2 = User.last | |
user_2.uuid = user_1.uuid | |
user_2.save | |
user_2.uuid.should_not == user_1.uuid | |
end | |
end # describe 'uuid' | |
describe 'authentication' do | |
context 'with valid credentials' do | |
before :each do | |
email = '[email protected]' | |
password = 'user.at.example.com' | |
@saved_user = Factory.create :user, :email => email, :password => password | |
@authenticated_user, @message = User.authenticate(email, password) | |
end | |
it 'should return the correct user' do | |
@authenticated_user.id.should == @saved_user.id | |
end | |
it 'should return the LOGIN_SUCCESSFUL message' do | |
@message.should == User::LOGIN_SUCCESSFUL | |
end | |
end # context 'with valid credentials' | |
context 'with unknown email' do | |
before :each do | |
email = '[email protected]' | |
password = 'right.password' | |
Factory.create :user, :email => email, :password => password | |
@authenticated_user, @message = User.authenticate('[email protected]', password) | |
end | |
it 'should return a nil user' do | |
@authenticated_user.should be_nil | |
end | |
it 'should return the UNKNOWN_EMAIL message' do | |
@message.should == User::UNKNOWN_EMAIL | |
end | |
end # context 'unknown email' | |
context 'with wrong password' do | |
before :each do | |
email = '[email protected]' | |
password = 'right.password' | |
Factory.create :user, :email => email, :password => password | |
@authenticated_user, @message = User.authenticate(email, 'wrong.password') | |
end | |
it 'should return a nil user' do | |
@authenticated_user.should be_nil | |
end | |
it 'should return the WRONG_PASSWORD message' do | |
@message.should == User::WRONG_PASSWORD | |
end | |
end # context 'with wrong password' | |
end # describe 'authentication' | |
describe '#set_country!' do | |
context 'when country is already set' do | |
before(:each) { @user = Factory.create :user } | |
it 'should do nothing' do | |
GeoIp.should_not_receive(:geolocation) | |
@user.set_country! '127.0.0.1' | |
end | |
end | |
context 'when the country is not set' do | |
before(:each) { @user = Factory.create :user, :country => nil } | |
context 'and the IP is resolved' do | |
before(:each) do | |
GeoIp.stub(:geolocation).and_return({:country_code => 'US'}) | |
country = mock_model Country | |
Country.stub(:find_by_code).and_return(country) | |
end | |
it 'should set the country' do | |
Country.should_receive :find_by_code | |
@user.set_country! '127.0.0.1' | |
@user.country_id.should_not be_nil | |
end | |
end # context 'when the IP is resolved' | |
context 'and the IP is not resolved' do | |
before(:each) do | |
GeoIp.stub(:geolocation).and_return({:country_code => '-'}) | |
Country.stub(:find_by_code).and_return(nil) | |
end | |
it 'should not set the country' do | |
Country.should_receive :find_by_code | |
@user.set_country! '192.168.1.1' | |
@user.country_id.should be_nil | |
end | |
end # context 'when the IP is not resolved' | |
context 'and the resolution attempt times out' do | |
before(:each) do | |
GeoIp.stub(:geolocation).and_return(nil) | |
end | |
it 'should not set the country' do | |
Country.should_not_receive :find_by_code | |
@user.set_country! '127.0.0.1' | |
@user.country_id.should be_nil | |
end | |
end # context 'when the resolution attempt times out' | |
end # context 'when the country is not set' | |
end # describe '#set_country!' | |
end # describe User |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment