Chapter 8: Refining Your Code
Refining Your Model
We refine the User model just to make other files, like controllers and views, simpler, easier to write and read. We will just exclude some of the commonly used stuff and put it into the model, in form of functions. Look at the following code:
app/models/user.rb
class User < ActiveRecord::Base attr_accessor :current_password validates_presence_of :screen_name, :e_mail, :password validates_uniqueness_of :screen_name validates_uniqueness_of :e_mail, :case_sensitive => false validates_length_of :screen_name, :within => 6..30 validates_length_of :e_mail, :within => 6..50 validates_length_of :password, :within => 6..30 validates_confirmation_of :password validates_format_of :e_mail, :with => /^[A-Z0-9_.%-]+@([A-Z0-9_]+\.)+[A-Z]{2,4}$/i, :message => "must be a valid e-mail address" validates_presence_of :forename, :surname def full_name [forename, surname].join(' ') end def login!(session) session[:user_id] = id end def self.logout!(session) session[:user_id] = nil end def self.logged_in?(session) session[:user_id] != nil end def self.logged_in(session) return nil if not logged_in?(session) User.find(session[:user_id]) end def update_attributes(attributes) if attributes[:password].empty? # the user doesn't want to change the password attributes[:password] = password attributes[:password_confirmation] = password elsif attributes[:current_password] != password # the user provided incorrect current_password errors.add(:current_password, "is incorrect") return false end super(attributes) end endWe have added the following functions:
- full_name
- Displaying the full name (forename + surname) is quite common, especially in the views. The full_name is not present in the database and therefore is not available as the user member variable, but thanks to the definition of this function you can use expressions like @user.full_name as it were.
- login!
- This function replaces the session[:user_id] = id that appeared in several places in the user controller. This just slightly influenced the length of the code but, by adding the meaning to the snippets like @user.login!(session) it's contributed the text clarity and understandability. Notice the exclamation mark, used to point out that this function changes the state of the application (from not logged to logged).
- logout!
- Similar to the previous one. Notice that to logout you do not need any particular instance of user, therefore this function should be used for the whole class, ie. User.logout!(session) rather than @user.logout!(session). Notice the word self just before the name of the function: this tells the Ruby interpreter that this is a class function, not an instance function, and that it should be called for the entire class, not just an individual object.
- logged_in?
- Another example of a class function. Returns a logical expression which is true if someone is logged in and false if there is no currently logged user.
- logged_in
- Yet another class function, returns the is of the currently logged user (or nil if none).
- update_attributes
- This is an overriden function. This means that it was already defined in our class, and we have even used this function from within the user controller. The new, updated (or: overwritten) version of this function additionally pre-processes the password (for the discussion of the password update check Chapter 7), and, after that, it calls it standard (also known as: super) version to update all the attributes.
Look below for some samples of the possible modifications in the code. The first file shows how a sample view template can benefit from the full_name function.
app/views/user/find_by_town.html.erb
<h1>All People in <%= @town %></h1> <ul> <% @users.each do |user| %> <li><%= link_to user.full_name, :controller => :user, :action => :profile, :id => user.screen_name %></li> <% end %> </ul>Here is the complete source code of the User Controller with modifications made possible with the new functions defined in the User Model:.
app/controllers/user_controller.rb
class UserController < ApplicationController before_filter :protect, :except => [:login, :register, :tech] def index @title = "User Index" @user = User.logged_in(session) end def register @title = "Register" if request.post? @user = User.new(params[:user]) if @user.save @user.login!(session) flash[:notice] = "User with login #{@user.screen_name} created!" redirect_to :action => :index end end end def login @title = "Log in to Fakebook" if request.post? user = User.find_by_screen_name_and_password(params[:user][:screen_name], params[:user][:password]) if user user.login!(session) flash[:notice] = "User #{user.screen_name} logged in" redirect_to :action => "index" else flash[:notice] = "Invalid email/password combination" end end end def logout flash[:notice] = "User " + User.logged_in(session).screen_name + " logged out" User.logout!(session) redirect_to :controller => :site, :action => :index end def edit @title = "Edit Your Details" @user = User.logged_in(session) if request.post? if @user.update_attributes(params[:user]) flash[:notice] = "Your details have been updated" redirect_to :action => :index end end @user.password = nil @user.password_confirmation = nil end def profile @screen_name = params[:id] @title = "User Profile for #{@screen_name}" @user = User.find_by_screen_name(@screen_name) end def find_by_town @town = params[:id] @title = "All people from #{@town}" @users = User.find(:all, :conditions => "town = '#{@town}'") end private # Protect a page against not logged users def protect unless User.logged_in?(session) flash[:notice] = "Please login first" redirect_to :action => :login return false end end endRefining User Hub & Profile
To go on smoothly, we can also slightly modify the User Hub and Profile pages. These modifications include a better use of HTML headings and lists. They are quite straightforward and do not require any comment:
app/views/user/index.html.erb
<h1>Hello, <%= @user.full_name %>!</h1> <h2>Your Details:</h2> <ul> <li>Screen name: <%= @user.screen_name %></li> <li>E-mail address: <%= @user.e_mail %></li> <li>Occupation: <%= @user.occupation %></li> <li> Town/City: <%= @user.town %> (<%= link_to "see all people in your town", :controller => :user, :action => :find_by_town, :id => @user.town %>) </li> </ul> <p><%= link_to "Edit Your Details", :action => :edit %></p>app/views/user/profile.html.erb
<% if @user %> <h1><%= @user.full_name %>: User Profile</h1> <h2>Personal Details:</h2> <ul> <li>Screen name: <%= @user.screen_name %></li> <li>Occupation: <%= @user.occupation %></li> <li>Town/City: <%= @user.town %></li> </ul> <% else %> <h1>No User Profile for: <%= @screen_name %></h1> <% end %>
Copyright (C) 2009-2011 by Jaroslaw Francik

