Aug 24 2010
1 note

Create a Generic Rails Base Project

This tutorial covers how to create a simple rails base project with user authentication using binarylogic’s authlogic gem. More advanced users might want to throw a few other gems in, but user authentication is good for a bare minimum, since practically every web app you code will have some sort of user authentication.

1. Create a new rails project

 $ rails baseproject

2. Change directory into the new project’s folder

 $ cd baseproject

3. Delete public/index.html so we can set our own landing page.

 $ rm public/index.html

4. Create Home controller with the index action and view, which will be our landing page.

 $ script/generate controller Home index

5. Install the authlogic gem if you haven’t done so yet.

 $ gem install authlogic

6. Add the authlogic gem declaration to your environment.rb around line 22, under the section where there are similar commented out config.gem declarations.

# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "sqlite3-ruby", :lib => "sqlite3"
# config.gem "aws-s3", :lib => "aws/s3"
  config.gem "authlogic"

7. Scaffold the user model.

 $ script/generate scaffold User login:string email:string crypted_password:string password_salt:string persistence_token:string

These fields are specified by authlogic. You simply create the  fields in the db, and authlogic will use them accordingly. Once you understand authlogic better, you can add more fields to add functionality to the app.

8. Migrate your db.

 $ rake db:migrate

9. Add the ‘acts_as_authentic’ declaration to app/models/user.rb so it looks like this:

class User < ActiveRecord::Base
  acts_as_authentic
end

10. Erase the code in app/views/users/edit.html.erb and paste this code:

<h1>edit profile</h1>
 
<% form_for @user do |f| %>
  <%= f.error_messages %><br />
  
  <%= f.label 'edit password', f.object.new_record? ? nil : "change password" %><br />
  <%= f.password_field :password %><br />
  <br />
  
  <%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %><br />
  <br />

  <%= f.submit "update" %>

   or <%= link_to "cancel", url_for(:controller => 'users', :action => 'show', :id => @user.id ) %>
<% end %>

11. Erase the code in app/views/users/index.html.erb and paste this code:

<h1>Listing users</h1>

<table>
  <tr>
    <th>Login</th>
    <th>Email</th>
  </tr>

<% @users.each do |user| %>
  <tr>
    <td><%=h user.login %></td>
    <td><%=h user.email %></td>
    <td><%= link_to 'Show', user %></td>
    <td><%= link_to 'Edit', edit_user_path(user) %></td>
    <td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New user', new_user_path %>

12. Erase the code in app/views/users/new.html.erb and paste this code:

<h1>edit profile</h1>
 
<% form_for @user do |f| %>
  <%= f.error_messages %><br />
  
  <%= f.label 'edit password', f.object.new_record? ? nil : "change password" %><br />
  <%= f.password_field :password %><br />
  <br />
  
  <%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %><br />
  <br />

  <%= f.submit "update" %>

   or <%= link_to "cancel", url_for(:controller => 'users', :action => 'show', :id => @user.id ) %>
<% end %>

13. Erase the code in app/views/show.html.erb and paste this code:

<p>
  <b>Login:</b>
  <%=h @user.login %>
</p>

<p>
  <b>Email:</b>
  <%=h @user.email %>
</p>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

14. Scaffold the user sessions:

 $ script/generate scaffold UserSession --skip-migration

15. Erase the code in your UserSessions controller and paste this code:

class UserSessionsController < ApplicationController
  before_filter :require_no_user, :only => [:new, :create]
  before_filter :require_user, :only => :destroy
  
  def new
    @user_session = UserSession.new
    @user = User.new
  end
  
  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      redirect_to root_url
    else
      flash[:notice]='Invalid username/password combination'
      redirect_to :back
    end
  end
  
  def destroy
    current_user_session.destroy
    redirect_to root_url
  end
end

16. Erase the code in app/models/user_session.rb and paste this code:

class UserSession < Authlogic::Session::Base
end

Notice it extends an authlogic base class, not a rails class.

17. Delete all the views in app/views/user_sessions/ other than new.html.erb, and replace the contents of new.html.erb with this:

<h1>login</h1>
<% form_for @user_session, :url => url_for(:controller=>'user_sessions', :action=>'create') do |f| %>
  <%= f.error_messages %><br />
  <%= f.label :login, 'username' %><br />
  <%= f.text_field :login %><br />
  <br />
  <%= f.label :password, 'password' %><br />
  <%= f.password_field :password %><br />
  <br />
  <%= f.check_box :remember_me %><%= f.label :remember_me, 'remember  me' %><br />

  <%= f.submit "login" %>
<% end %>

18. Delete all views in app/views/layouts except for 1, and rename it application.html.erb to make it the master file. Replace its contents with this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Base Project</title>
  <%= stylesheet_link_tag 'scaffold' %>
</head>
<body>

<p style="color: green"><%= notice %></p>
<div>
<% if current_user %> 
	<%= link_to 'logout', logout_url %>
<% else %>
	<%= link_to 'login', login_url %>&nbsp;<%= link_to 'signup', signup_url %>
	
<% end %>
</div>

<%= yield %>

</body>
</html>

19. Update your config/routes.rb to look like this:

ActionController::Routing::Routes.draw do |map|
  map.resources :user_sessions

  map.resources :users

  map.resources :users

  # The priority is based upon order of creation: first created -> highest priority.

  # Sample of regular route:
  #   map.connect 'products/:id', :controller => 'catalog', :action => 'view'
  # Keep in mind you can assign values other than :controller and :action
  map.logout 'logout', :controller => 'user_sessions', :action =>'destroy'
  map.login 'login', :controller => 'user_sessions', :action =>'new'
  map.signup 'signup', :controller => 'users', :action =>'new'
  
  # Sample of named route:
  #   map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
  # This route can be invoked with purchase_url(:id => product.id)

  # Sample resource route (maps HTTP verbs to controller actions automatically):
  #   map.resources :products

  # Sample resource route with options:
  #   map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }

  # Sample resource route with sub-resources:
  #   map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
  
  # Sample resource route with more complex sub-resources
  #   map.resources :products do |products|
  #     products.resources :comments
  #     products.resources :sales, :collection => { :recent => :get }
  #   end

  # Sample resource route within a namespace:
  #   map.namespace :admin do |admin|
  #     # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
  #     admin.resources :products
  #   end

  # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
   map.root :controller => "home"

  # See how all your routes lay out with "rake routes"

  # Install the default routes as the lowest priority.
  # Note: These default routes make all actions in every controller accessible via GET requests. You should
  # consider removing or commenting them out if you're using named routes and resources.
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

20. Update your app/controllers/applicationcontroller.rb to look like this:

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  helper_method :current_user_session, :current_user
  filter_parameter_logging :password, :password_confirmation
  
  
  private
  
      def current_user_session
        return @current_user_session if defined?(@current_user_session)
        @current_user_session = UserSession.find
      end

      def current_user
        return @current_user if defined?(@current_user)
        @current_user = current_user_session && current_user_session.record
      end

      def require_user
        unless current_user
          flash[:notice] = "You must be logged in to do that."
          respond_to do |format|
            format.html { redirect_to login_url }
          end
          return false
        end
      end
      
      def require_no_user
        if current_user
          redirect_to root_url
          return false
        end
      end

      def store_location
        session[:return_to] = request.request_uri
      end

      def redirect_back_or_default(default)
        redirect_to(session[:return_to] || default)
        session[:return_to] = nil
      end
end

Your app should now run when you start your server with:

 $ script/server

Starting your next projects with a copy of this project as a base rather than coding everything from scratch again will definitely save you some valuable time. Check out the demo app and the source code.