Home arrow Ruby-on-Rails arrow Page 5 - Callbacks and the Active Record
RUBY-ON-RAILS

Callbacks and the Active Record


In this conclusion to a five-part series that delves into the Rails framework's Active Record, you'll finish learning about validations, and take a look at callbacks. This article is excerpted from chapter five of the book Beginning Rails: From Novice to Professional, written by Jeffrey Allan Hardy, Cloves Carneiro Jr. and Hampton Catlin (Apress; ISBN: 1590596862).

Author Info:
By: Apress Publishing
Rating: 5 stars5 stars5 stars5 stars5 stars / 1
February 25, 2010
TABLE OF CONTENTS:
  1. · Callbacks and the Active Record
  2. · Validating the Format of an Attribute
  3. · Making Callbacks
  4. · Reviewing the Updated Models
  5. · Updating the User Model

print this article
SEARCH DEVARTICLES

TOOLS YOU CAN USE

advertisement
Callbacks and the Active Record - Updating the User Model
(Page 5 of 5 )

Our Event model is nicely filled out, but we still need to do a little bit of work on our User model. We’ll be applying a lot of the techniques we described in this chapter, such as custom methods to allow us to perform user authentication, and validation methods to make sure our data stays clean.

When we created the user migration (Listing 5-2), we added a field called password. This field stored a plain-text password, which if you think about it, isn’t very secure. It’s always a good idea to encrypt any sensitive data so it can’t be easily read by would-be intruders. We’ll deal with the encryption in the User model itself, but the first thing we’ll do is rename the field in the database from password to hashed_password. This is so we can create a custom accessor called password with which we can set the password while maintaining a field to store the encrypted version in the database. The plain-text password will never be saved.

To accomplish this, we’ll create a migration. From the terminal, issue the following command to create the new migration:

$ ./script/generate migration rename_password_to_hashed_password

Next, fill in the migration as shown in Listing 5-28.

Listing 5-28. Migration to Rename password to hashed_password in db/migrate/007_ rename_ password_to_hashed_password.rb

class RenamePasswordToHashedPassword < ActiveRecord::Migration
  def self.up
    rename_column :users, :password, :hashed_password
  end
 

  def self.down
    rename_column :users, :hashed_password, :password
  end
end

Run the migration using therake db:migratecommand as follows:

$ rake db:migrate

Next, update yourUsermodel so that it looks like Listing 5-29. In Listing 5-29, we’ve programmed all the user authentication methods we’ll need for allowing users to log in. Let’s take a look at the code first, and then we’ll describe in detail what we’ve done.

Listing 5-29. Current User Model, in app/models/user.rb

require 'digest/sha1'

class User < ActiveRecord::Base
  attr_accessor :password

  has_many :events,
           :dependent => :destroy
  has_many :registrations,
           :dependent => :destroy
  has_many :activities, :through => :registrations, :source => :event

  validates_presence_of   :login
  validates_length_of     :login, :within => 3..40
  validates_uniqueness_of :login, :case_sensitive => false
  validates_format_of     :login, :with => /^[\w\.-]+$/i
 
  validates_presence_of   :email
  validates_format_of     :email, :with => /^[^@][\w.-]+@[\w.-]+[.][a-z]{2,4}$/i

  validates_presence_of   :password,     :if => :password_required?
  validates_length_of     :password, :within => 4..40, :if => :password_required?
 validates_confirmation_of :password,    :if => :password_required?

  before_save :encrypt_new_password

  def self.authenticate(login, password)
    user = find_by_login(login)
    return user if user && user.authenticated?(password)
 
end

  def authenticated?(password)
    hashed_password == encrypt(password)
  end

  protected
   
def encrypt_new_password
      return if password.blank?
      self.hashed_password = encrypt(password)
    end

    def password_required?
      hashed_password.blank? || !password.blank?
    end

    def encrypt(string)
      Digest::SHA1.hexdigest(string)
    end
end

Whenever you’re storing something sensitive, like a password, you want to encrypt it. To encrypt the password in ourUsermodel, we use a simple algorithm called a hash that will create a random-looking string from the provided input. This hashed output cannot be turned back into the original string easily, so even if someone steals your database, he will have a prohibitively difficult time discovering your users’ passwords. Ruby has a built-in library calledDigest, which includes many hashing algorithms.

Let’s go through the additions to ourUser model:

  1. require 'digest/sha1': We start by requiring theDigestlibrary we will use for encrypting the passwords. This loads the needed library and makes it available to work with in our class.
  2. attr_accessor :password: This defines an accessor attribute,password, at the top of the class body. This tells Ruby to create reader and writer methods forpassword. Since the password column doesn’t actually exist in our table anymore, apassword method won’t be created automatically by Active Record. Still, we need a way to set the password before it’s encrypted, so we make our own attribute to use. This will work just like any model attribute, except that it won’t be persisted to the database when the model is saved. 
     
  3. before_save :encrypt_new_password: Thisbefore_savecallback tells Active Record to run theencrypt_new_passwordmethod before it saves a record. That means it will apply to all operations that trigger a save, includingcreateandupdate
     
  4. encrypt_new_password: This method should perform encryption only if thepassword attribute contains a value, since we wouldn’t want it to happen unless a user is changing her password. So, if thepassword attribute is blank, we return from the method and thehash_passwordvalue is never set. If thepasswordvalue is not blank, we have some work to do. We set thehashed_passwordattribute to the encrypted version of the password by laundering it through theencryptmethod. 
     
  5. encrypt: This method is fairly simple. It leverages Ruby’sDigestlibrary that we included on the first line to create an SHA1 digest of whatever we pass it. Since methods in Ruby always return the last thing evaluated,encryptwill return the encrypted string. 
     
  6. password_required?: When we’re performing our validations, we want to make sure we’re validating the presence, length, and confirmation of the password only if validation is required. And it’s required only if this is a new record (thehashed_passwordattribute is blank) or if thepasswordaccessor we created has been used to set a new password
    (!password.blank?). To make this easy, we’ve created thepassword_required?predicate method, which returnstrueif a password is required, orfalseif it’s not. We then apply this method as an:ifcondition on all our password validators. 
     
  7. self.authenticate: You can tell this is a class method because it’s prefixed withself (it’s defined on the class itself). That means you don’t access it via an instance; you access it directly off the class, just as you would withfind,new, orcreate (User.authenticate, not
    @user = User.new; @user.authenticate). Theauthenticatemethod accepts a login and an unencrypted password. It uses a dynamic finder (find_by_login) to fetch the user with a matching login. If the user was found, the user variable will contain aUserobject; if not, it will benil. Knowing this, we can return the value ofuserif, and only if, it is notniland theauthenticated?method returnstruefor the given password (user && user.authenticated?(password)). 
     
  8. authenticated?: This is a simple predicate method that checks to make sure the storedhashed_passwordmatches the given password after it has been encrypted (viaencrypt). If it matches,trueis returned.

Let’s play with these new methods from the console so you can get a better idea of how this comes together.

>> User.authenticate("eugene", "secret")
=> #<User:0xb74197d8>
>> User.authenticate("eugene", "secret2")
=> nil

So, when we ask theUser model to authenticate someone, we pass in the login and the plain-text password. Theauthenticatemethod hashes the given password and then compares it to the stored (hashed) password in the database. If the passwords match, theUser object is returned and authentication was successful. When we try to use an incorrect password,nilis returned. In Chapter 6, we’ll write code in our controller to use these model methods and actually allow users to log in to the site. But for now, we have a properly built and secure back end for how users will authenticate.

Summary

After reading this chapter, you should have a complete understanding of Active Record models. We’ve covered associations, conditions, validations, and callbacks at breakneck speed. Now the fun part starts. In the next chapter you will get to use all the groundwork that we established in this chapter to produce the web interface for the data structures we have created here. This is when you really get to reap the benefits of your hard work.

 


DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.

blog comments powered by Disqus
RUBY-ON-RAILS ARTICLES

- Adding Style with Action Pack
- Handling HTML in Templates with Action Pack
- Filters, Controllers and Helpers in Action P...
- Action Pack and Controller Filters
- Action Pack Categories and Events
- Logging Out, Events and Templates with Actio...
- Action Pack Sessions and Architecture
- More on Action Pack Partial Templates
- Action Pack Partial Templates
- Displaying Error Messages with the Action Pa...
- Action Pack Request Parameters
- Creating an Action Pack Registration Form
- Ruby on Rails Templates and Layouts
- Action Pack Controller Creation
- Writing an Action Pack Controller

Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 



© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 5 - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials