Development of Login Management Module and Comment Management Module

Exclusive offer: get 50% off this eBook here
Building Dynamic Web 2.0 Websites with Ruby on Rails

Building Dynamic Web 2.0 Websites with Ruby on Rails — Save 50%

Create database-driven dynamic websites with this open-source web application framework

£13.99    £7.00
by A P Rajshekhar | April 2009 | Open Source

Ruby on Rails is an active component in the world of web application framework. Ruby is the language used and Ruby on Rails is the framework built upon Ruby. In this article by A.P.Rajshekhar, we will be learning how to develop the login management module and the comment management module .We will start off by creating a login page and then move to implementing the Authenticate method, set up the session, try out applying authorization. After this, we will learn generating the Scaffold, modifying the model, review the View, customizing the controller under the development of comment management module sub title.

Lets get started right away.

Developing the Login Management Module

Even though Login and session handling are separate functionalities from User management, they depend on the same table—user. Also, the functionalities are more alike than different. Hence, instead of creating a new Controller, we will be using the UserController itself as the Controller for the Login module. Keeping this point in mind, let us look at the steps involved in developing the Login Management, which are:

  • Creating the Login page
  • Implementing the Authentication Method
  • Setting up the Session
  • Applying Authorization

Leaving aside the first step, all other steps mainly focus on the Controller. Here we go.

Creating the Login Page

We need a login page with text boxes for user name and password in which users can put their credentials and submit to the login authenticator (fancy name for the action method that will contain the logic to authenticate the user). That's what we are going to create now. The convention for any website is to show the login page when the user enters the URL without any specific page in mind. RoR also follows this convention. For example, if you enter the URL as http://localhost:3000/user, it displays the list of users. The reason is that the index action method of the UserController class calls the list method whenever the aforementioned URL is used. From this, we can understand two things—first, the default action method is index, and second, the first page to be shown is changeable if we change the index method.

What we need is to show the login page whenever a user enters the URLhttp://localhost:3000/user. So let's change the index method. Open theuser_controller.rb file from the app/views/user folder and remove all the statements from the body of the index method so that it looks like as follows:

def index
end

Next, let us create an index.rhtml file, which will be shown when the index method is called. This file will be the login page. In the app/views/user folder, create an index.rhtml file. It will be as follows

<%= form_tag :action=> 'authenticate'%>
<table >
<tr align="center" class="tablebody">
<td>User name:</td>
<td><%= text_field("user", "user_name",:size=>"15" ) %></td>
</tr>
<tr align="center" class="tablebody">
<td>Password:</td>
<td><%= password_field("user","password",:size=>"17" ) %></td>
</tr>
<tr align="center" class="tablebody">
<td></td>
<td><input type="submit" value=" LOGIN " /></td>
</tr>
</table>

It uses two new form helpers—text_field and password_field. The text_field creates a text field with the name passed as the parameter, and the password_field creates a password field again with the name passed as the parameter. We have passed the authenticate method as the action parameter so that the form is submitted to the authenticate method. That completes the login page creation. Next, we will work on the authenticate method.

Implementing the Authenticate method

Implementing the Authenticate method Authenticating a user essentially means checking whether the user name and password given by the user corresponds to the one in database or not. In our case, the user gives us the user name and password through the login page. What we will be doing is checking whether the user is in database and does the password that we got corresponds to the password stored in the database for the user? Here, we will be working on two levels:

  • Model
  • Controller

We can put the data access part in the action method that being the Controller itself. But it will create problems in the future if we want to add something extra to the user name/password checking code. That's why we are going to put (or delegate) the data access part into Model.

Model

We will be modifying the User class by adding a method that will check whether the user name and password provided by the user is correct or not. The name of the method is login. It is as follows:

def self.login(name,password)
find(:first,:conditions => ["user_name = ? and password =
?",name, password])
end

It is defined as a singleton method of the User class by using the self keyword. The singleton methods are special class-level methods. The conditions parameter of the find method takes an array of condition and the corresponding values. The find method generates an SQL statement from the passed parameters. Here, the find method finds the first record that matches the provided user_name and password. Now, let us create the method that the Controller will call to check the validity of the user. Let us name it check_login. The definition is as follows:

def check_login
User.login(self.user_name, self.password)
end

This function calls the login method. Now if you observe closely, check_login calls the login function. One more point to remember—if a method 'test' returns a value and you call 'test' from another method 'test1,' then you don't need to say 'return test' from within 'test1'.The value returned from 'test' will be returned by 'test1' implicitly. That completes the changes to be done at the Model level. Now let us see the changes at the Controller-level.

Controller

In the Controller for User—UserController—add a new method named authenticate. The method will first create a User object based on the user name and password. Then it will invoke check_login on the newly created User object. If check_login is successful, that is, it does not return nil, then the user is redirected to the list view of Tales. Otherwise, the user is redirected to the login page itself. Here is what the method will look like:

def authenticate
@user = User.new(params[:user])
valid_user = @user.check_login
if logged_in_user
flash[:note]="Welcome "+logged_in_user.name
redirect_to(:controller=>'tale',:action => "list")
else
flash[:notice] = "Invalid User/Password"
redirect_to :action=> "index"
end
end

The redirect_to method accepts two parameters—the name of the Controller and the method within the Controller. If the user is valid, then the list method of TaleController is called, or in other words, the user is redirected to the list of tales. Next, let us make it more robust by checking for the get method. If a user directly types a URL to an action, then the get method is received by the method. If any user does that, we want him/her to be redirected to the login page. To do this, we wrap up the user validation logic in an if/else block. The code will be the following:

def authenticate
if request.get?
render :action=> 'index'
else
@user = User.new(params[:user])
valid_user = @user.check_login
if valid_user
flash[:note]="Welcome "+valid_user.user_name
redirect_to(:controller=>'tale',:action => 'list')
else
flash[:notice] = "Invalid User/Password"
redirect_to :action=> 'index'
end
end
end

The get? method returns true if the URL has the GET method else it returns false. That completes the login authentication part. Next, let us set up the session.

In Ruby, any method that returns a Boolean value—true or false—is suffixed with a question mark (?). The get method of the request object returns a boolean value. So it is suffixed with a question mark (?).

Setting up the Session

Once a user is authenticated, the next step is to set up the session to track the user. Session, by definition, is the conversation between the user and the server from the moment the user logs in to the moment the user logs out. A conversation is a pair of requests by the user and the response from the server. In RoR, the session can be tracked either by using cookies or the session object. The session is an object provided by RoR. The session object can hold objects where as cookies cannot. Therefore, we will be using the session object. The session object is a hash like structure, which can hold the key and the corresponding value. Setting up a session is as easy as providing a key to the session object and assigning it a value. The following code illustrates this aspect:

def authenticate
if request.get?
render :action=> 'index'
else
@user = User.new(params[:user])
valid_user = @user.check_login
if valid_user
session[:user_id]=valid_user.id
flash[:note]="Welcome "+valid_user.user_name
redirect_to(:controller=>'tale',:action => 'list')
else
flash[:notice] = "Invalid User/Password"
redirect_to :action=> 'index'
end
end
end

That completes setting up the session part. That brings us to the last step—applying authorization.

Applying Authorization

Until now, we have authenticated the user and set up a session for him/her. However, we still haven't ensured that only the authenticated users can access the different functionalities of TaleWiki. This is where authorization comes into the picture. Authorization has two levels—coarse grained and fine grained. Coarse grained authorization looks at the whole picture whereas the fine grained authorization looks at the individual 'pixels' of the picture. Ensuring that only the authenticated users can get into TaleWiki is a part of coarse grained authorization while checking the privileges for each functionality comes under the fine grained authorization. In this article, we will be working with the coarse grained authorization.

The best place to apply the coarse grained authorization is the Controller as it is the central point of data exchange. Just like other aspects, RoR provides a functionality to easily apply any kind of logic on the Controller as a whole in the form of filters. To jog your memory, a filter contains a set of statements that need to be executed before, after (or before and after) the methods within the Controllers are executed.

Our problem is to check whether the user is authenticated or not, before any method in a Controller is executed. The solution to our problem is using a 'before filter'. But we have to apply authorization to all the Controllers. Hence, the filter should be callable from any of the Controller. If you look at the definition of a Controller, you can find such a place. Each Controller is inherited from the ApplicationController. Anything placed in ApplicationController will be callable from other Controllers. In other words, any method placed in ApplicationController becomes global to all the Controllers within your application. So, we will place the method containing the filter logic in ApplicationController.

To check whether a user is authentic or not, the simplest way is to check whether a session exists for that person or not. If it exists, then we can continue with the normal execution. Let us name it check_authentic_user. The implementation will be as follows:

def check_authentic_user
unless session[:user_id]
flash[:notice] = "Please log in"
redirect_to(:controller => "user", :action =>
"index")
end
end

It checks for the user_id key in a session. If it is not present, the user is redirected to the login page. Place the code in the application.rb file as a method of ApplicationController. Next, let us use it as a filter. First, we will tell UserController to apply the filter for all the action methods except index and authenticate methods. Add the following statement to the UserController. It should be the first statement after the starting of the Controller class.

class UserController < ApplicationController
before_filter :check_authentic_user, :except =>[ :index, :authenticate ]
:
:
end

Similarly, we will place the filter in other Controllers as well. However, in their case, there are no exceptions. So TaleController will have:

class TaleController < ApplicationController
before_filter :check_authentic_user
:
:
end

GenreController and RoleController will be the same as TaleController. Thus, we have completed the 'applying authorization' part for the time being. Now, let's tie up one loose end—the problem of adding a new tale.

Tying Up the Loose Ends

When we developed the User management, the Tale management was affected as the tales table has a many-to-one relationship with the users table. Now we can solve the problem created by the foreign key reference. First, open the user.rb file and add the following statement indicating that it is at the 'one' end of the relationship:

has_many :tale

After addition of the statement, the class will look like the following:

class User < ActiveRecord::Base
validates_presence_of :user_name, :password, :first_name,
:last_name, :age, :email, :country
validates_uniqueness_of :user_name
validates_numericality_of :age
validates_format_of :email, :with => /A([^@s]+)@((?:[-a-
z0-9]+.)+[a-z]{2,})Z/i
belongs_to :role
has_many :tale
def check_login
User.login(self.name, self.password)
end
def self.login(name,password)
find(:first,:conditions => ["user_name = ? and password
=?",name, password])
end
end

Next, add the following statement to the tale.rb file:

belongs_to :user

The file will look like as follows:

class Tale < ActiveRecord::Base
validates_presence_of :title, :body_text, :source
belongs_to:genre
belongs_to :user
end

Next, open the tale_controller.rb file. In the create method, we need to add the user's id to the tale's user id reference so that the referential integrity can be satisfied. For that, we will get the current user's id from the session and set it as the value of the user_id attribute of the tale object. The create method will look like as follows, after doing the changes:

def create
@tale = Tale.new(params[:tale])
@tale.genre_id=params[:genre]
@tale.user_id=session[:user_id]
@tale.status="new"
if @tale.save
flash[:notice] = 'Tale was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

That's it. The 'loose ends' related to the User management are tied up. Now let us move onto the Comment Management module.

Building Dynamic Web 2.0 Websites with Ruby on Rails Create database-driven dynamic websites with this open-source web application framework
Published: March 2008
eBook Price: £13.99
Book Price: £21.99
See more
Select your format and quantity:

Developing the Comment Management Module

From the description of functionalities, we know that the module needs to support only three operations—add, view, and delete. The steps for developing the module are almost the same:

  • Generating the Scaffold
  • Modifying the Model
  • Refining the View
  • Customizing the Controller

We have changed the order of refining the view and customizing the Controller steps. That's what I meant by 'almost the same'. Let's get into the development.

Generating the Scaffold

Building Dynamic Web 2.0 Websites with Ruby on Rails

Open the RoR prompt using use_ruby command, and enter the following command:

C:InstantRailsrails_appstalewiki>ruby script/generate scaffold 
Comment comment list show new create destroy

You will get the following screen:

 

Building Dynamic Web 2.0 Websites with Ruby on Rails

 

If the scaffold command is reused, then it will not rewrite the existing files unless you specify the -force parameter. We need only new, list, and delete functionalities. So, we have specified the actions that we need—list, show for listing of comments, new and create for adding, and delete for deleting. However, it will still create the stubs and links that need to be tackled at the View level. First, let us do the required modifications at the Model level.

Modifying the Model

First, we have to tell RoR which fields should not be empty. For that, add the validates_presence_of method with :comment_body as the argument in the comment.rb file. After addition, the code shall be as follows:

class Comment < ActiveRecord::Base
validates_presence_of :comment_body
end

Next, we have to tell that the comments table is at the 'many' end of the relationship with both tales and users table. For that, add a belongs_to declaration to the comment.rb file.

class Comment < ActiveRecord::Base
validates_presence_of :comment_body
belongs_to :tale
belongs_to :user
end

The next step is to tell both the users and the tales table that they are at the 'one' end of the relationship. For that, open the user.rb and tale.rb files, and add the has_many declaration. After the additions, the code will be as follows for user.rb:

class User < ActiveRecord::Base
validates_presence_of :user_name, :password, :first_name,
:last_name, :age, :email, :country
validates_uniqueness_of :user_name
validates_numericality_of :age
validates_format_of :email, :with => /A([^@s]+)@((?:[-a-
z0-9]+.)+[a-z]{2,})Z/i
belongs_to :role
has_many :tale
has_many :comment
def check_login
User.login(self.name, self.password)
end
def self.login(name,password)
find(:first,:conditions => ["user_name = ? and password
=?",name, password])
end
end

For tale.rb, here is the code:

class Tale < ActiveRecord::Base
validates_presence_of :title, :body_text, :source
belongs_to:genre
belongs_to :user
has_many :comment
end

That completes the changes to be done at the Model level. Next, let us refine the View.

Refining the View

Comments will be given for a story. That means the page displaying a tale will have a link to add comments. This also means that the Comment management module is not a 'standalone' module like others, as it will not have its own menu when we decide upon the template. Now coming back to links to the comments in the tale display page, for what functionalities do we need the links? The answer is two—adding a comment and listing the comment. The add comment link will lead to the 'New Comment' page, and the view comments link will lead to the list view of the comments. Now let us see what are the problems—each comment needs a user id and the id of the tale for which the comment is being added. The listing of comments needs only the id of the tale. As user id is available from the session, we have to add only the tale id as a part of the link. That is what we are going to do.

Open the show.rhtml file from the app/views/tale directory. It contents are as follows

<% for column in Tale.content_columns %>
<p>
<b><%= column.human_name %>:</b> <%=h @tale.send(column.name) %>
</p>
<% end %>
<%= link_to 'Edit', :action => 'edit', :id => @tale %>
<%= link_to 'Back', :action => 'list' %>

Now let us add two more links—one for adding a comment and another for listing the comments:

<% for column in Tale.content_columns %>
<p>
<b><%= column.human_name %>:</b> <%=h @tale.send(column.name) %>
</p>
<% end %>
<%= link_to 'Edit', :action => 'edit', :id => @tale %>
<%= link_to 'Back', :action => 'list' %>
<%= link_to 'Add Comment',:controller=>'comment', :action => 'new', :id => @tale.id %>
<%= link_to 'View Comments',:controller=>'comment', :action => 'list', :id => @tale.id %>

The next change we have to do is remove the edit option from the viewing part of the comments. So open the list.rhtml file from the app/views/comments. The code will be as follows:

<h1>Listing comments</h1>
<table>
<tr>
<% for column in Comment.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>
<% for comment in @comments %>
<tr>
<% for column in Comment.content_columns %>
<td><%=h comment.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => comment %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => comment %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => comment
}, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</table>
<%= link_to 'Previous page', { :page => @comment_pages.current.previous }
if @comment_pages.current.previous %>
<%= link_to 'Next page', { :page => @comment_pages.current.next }
if @comment_pages.current.next %>
<br />
<%= link_to 'New comment', :action => 'new' %>

Delete the tags that link to the Edit and New Comment functionalities. We do not need anyone adding a comment without reading the story. After deletions, the code will be as follows:

<h1>Listing comments</h1>
<table>
<tr>
<% for column in Comment.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>
<% for comment in @comments %>
<tr>
<% for column in Comment.content_columns %>
<td><%=h comment.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => comment %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => comment
 }, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</table>
<%= link_to 'Previous page', { :page => @comment_pages.current.previous } if @
comment_pages.current.previous %>
<%= link_to 'Next page', { :page => @comment_pages.current.next } if @
comment_pages.current.next %>
<br />

That completes the refinement to be done to the VIEW. Now let's modify the Controller.

Customizing the Controller

Open the comment_controller.rb file and in the new method add the tale_id to the session object so that the method looks like the following:

def new
@comment = Comment.new
session[:tale_id]=params[:id]
end

Now in the create method, let us get the tale_id and the user_id from the session,and pass it to the comment object. We have used the session object because the tale_id is coming as a part of the get request, which will be available only to the new method and not the create method. After the changes, the create method will be as follows:

def create
@comment = Comment.new(params[:comment])
@comment.tale_id=session[:tale_id]
@comment.user_id=session[:user_id]
if @comment.save
flash[:notice] = 'Comment was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

We do not want to show the list of comments, once a comment has been added. Therefore, we will redirect the user to the tale's list once a comment has been added successfully.

def create
@comment = Comment.new(params[:comment])
@comment.user_id=session[:user_id]
@comment.tale_id=session[:tale_id]
if @comment.save
flash[:notice] = 'Comment was successfully created.'
redirect_to :controller=>'tale', :action => 'list'
else
render :action => 'new'
end
end

Apart from this, we have to change the list method so that it finds that only those comments are selected for which the tale_id has been passed through the link. So let us modify the paginate method in the list method to add a condition. After modification, the list method will be as follows:

def list
@comment_pages, @comments = paginate :comments, :conditions=>['tale_id = ?',
params[:id]] :per_page => 10
end

As you can see, the paginate method takes the table to paginate, the condition which is optional and the number of items to be shown per page as arguments.

And that completes our current work on the Comment management module.

Summary

We have seen how to develop the login management module and comment management module in detail.

If you have read this article you may be interested to view :

Building Dynamic Web 2.0 Websites with Ruby on Rails Create database-driven dynamic websites with this open-source web application framework
Published: March 2008
eBook Price: £13.99
Book Price: £21.99
See more
Select your format and quantity:

About the Author :


A P Rajshekhar

 

A. P. Rajshekhar, Senior Developer with Vectorform, has worked on enterprise-level web applications and game development. His endeavors include development of a Learning Management System, a Supply Management Solution and Xbox-based games. He holds a Masters Degree in Computer Applications. He is a regular contributor to Devshed Portal on topics ranging from server-side development (JEE/.Net/RoR) to mobile (Symbian-based) development and game development (SDL and OpenGL) with a total readership of more than 1.4 million.

Contact A P Rajshekhar

Books From Packt

 

Drools JBoss Rules 5.0 Developer's Guide
Drools JBoss Rules 5.0 Developer's Guide

Plone 3 Theming
Plone 3 Theming

Moodle 1.9 Multimedia
Moodle 1.9 Multimedia

Apache Struts 2 Web Application Development
Apache Struts 2 Web Application Development

Drupal 5 Views Recipes
Drupal 5 Views Recipes

JBoss Tools 3 Developers Guide
    JBoss Tools 3 Developers Guide

Pentaho Reporting 1.0 for Java Developers
Pentaho Reporting 1.0 for Java Developers

Grails 1.1 Web Application Development
Grails 1.1 Web Application Development

 

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software