Friday 24 August 2007

Updated Database Design

As promised.


Table Definition

users

Column Name

Data Type

Constraints

id

integer

Primary Key

handle

varchar(10)

Unique Key

fname

varchar(15)

NOT NULL

lname

varchar(15)

NOT NULL

password_hash

varchar(128)

NOT NULL

password_salt

varchar(8)

NOT NULL

email

varchar(45)

NOT NULL

handle – name of the account

fname – first name of user

lname – last name of user

password_hash – SHA512 digest of user’s plaintext password

password_salt – 8 bit randomly generated salt

email – email of user


members

Column Name

Data Type

Constraints

id

integer

Primary Key

project_id

integer

Foreign Key

user_id

integer

Foreign Key

permission

varchar(10)

NOT NULL, Default “guest”

id – primary key

project_id – project the member belongs to

user_id – user that has access to the project

permission – the permission level (admin, member, guest)

projects

Column Name

Data Type

Constraints

id

integer

Primary Key

name

varchar(30)

Primary Key

description

varchar(100)

NOT NULL, Default “”

id – primary key

name – name of the project

description – description of the project


jobs

Column Name

Data Type

Constraints

id

integer

Primary Key

project_id

integer

Foreign Key

name

varchar(30)

NOT NULL

description

varchar(100)

NOT NULL, Default “”

id – primary key

project_id – name of the project it belongs to

name – name of the job

description – description of the job


etransactions

Column Name

Data Type

Constraints

id

integer

Primary Key

date

date

NOT NULL, Default Timestamp

job_id

integer

Foreign Key

counterparty

varchar(30)

NOT NULL

category

varchar(30)

NOT NULL, Default “Other”

flow

varchar(10)

NOT NULL

created_by

integer

Foreign Key

description

varchar(100)

Default “”

amount

decimal(9,2)

NOT NULL, Unsigned

id – primary key

date – date of the transaction

job_id – the job which the transaction belongs to

counterparty – the transaction made to

category – the category of the transaction

flow – inflow or outflow

created_by – the user who created the transaction

description – description of the transaction

amount – the amount of the transaction

Thursday 23 August 2007

Bug #2: Problem with updating user object

The bug was found in the model side as I was trying to update the user object by doing:


@user = User.update(params[:id], {
:fname => params[:user][:fname],
:lname => params[:user][:lname],
:email => params[:user][:email] })

Whenever I tried to update, I'd get this error:

ArgumentError in UserController#update wrong number of arguments (2 for 4)

Could not figure out what was wrong with it from googling. So once again I decided to hop onto my trusty real-time help solution: irc. I got suggested to try this method of updating:

@user = User.find(params[:id])
@user.update_attributes({
:fname => params[:user][:fname],
:lname => params[:user][:lname],
:email => params[:user][:email]}) if @user != nil
This seemed to work perfectly. I still haven't figured out what was the problem with the other one. Perhaps its because I didn't provide the other two parameters that's in the table definition: password_hash and password_salt. I'm not sure.

Bug #1: AJAX checking in certain actions

So I came across a bug as I was testing my newly written code to check whether handles and usernames have already been registered in the database. This worked fine when I want to sign up. However, when I want to update a user's profile, it would not let me update my profile with my existing email because the email already exists in the database. Pretty obvious that there's a bug there.

So when I did my AJAX checking, I had to determine what type of action I was doing (i.e. signing up, updating...). I had a parameter called action which would be sent in the query string, telling the controller what kind of action I am doing. The problem is, whenever I did that, my code for some reason could not recognise what kind of action I'm performing. When I printed out params[:action], it gave me the controller's method name.

Out of frustration, I hopped onto irc to get some real-time help. Turns out that action was a reserved name used for routing purposes, which determines which method of the controller you're calling to. D'oh! Obviously I should've known this as my code is scattered with :action => "something".

So I changed the parameter name to actiontype and it worked perfectly!

Tuesday 21 August 2007

Form validation with AJAX

Finally managed to utilise javascript and ajax to do client-side form checking. I am using javascript instead of rjs because I feel like I have more control over what I'm doing and also I'm more familiar with the javascript syntax. Since prototype framework is included as part of the javascript defaults in RoR, I will be using that framework too.

Its got some pretty handy things like accessing DOM elements using $('element id'), so you don't have to deal with document.getElementId etc. The framework also provides a cool Ajax object which handles all the nasty readystates that you will come across if you use the conventional way of doing Ajax.

Here is a snippet of the client-side code:

new Ajax.Request("/user/check_exists",
{
parameters: params + "=" + textField.value,
onLoading: showMsg(infoField, "<img src="../../images/ajax/ajax-loader.gif" border="0" /> Checking if available..."),
onSuccess: function(request) {
if (request.responseText)
{
showMsg(infoField, request.responseText);
textField.className = "fail";
textField.focus();
}
else
{
showMsg(infoField, "");
textField.className = "success";
}
}
});
Here is a snippet of the server-side code:

def check_exists
if params[:handle]
@handle_exists = User.find(:first, :conditions => [ "handle = ?", params[:handle] ])
if @handle_exists == nil
render :text => ""
else
render :text => "Username is taken. Please choose another."
end
elsif params[:email]
@email_exists = User.find(:first, :conditions => [ "email = ?", params[:email] ])
if @email_exists == nil
render :text => ""
else
render :text => "This email is registered. Please choose another."
end
end
end
Sorry about the indentations if they don't come out correctly. For some reason this current blogger layout strips any unnecessary whitespaces.

Here are some screen shots of what I've done today. You will notice I used a little CSS to make things look nicer.



[edit date="23-08-07"]

I decided to move the business logic into the model and leave the transaction logic in the controller. More elegant and scalable I reckon.

In the model:
def self.attribute_exists?(attribute, value)
@attribute_exists = User.find(:first, :conditions => [ attribute + " = ?", value ])
if @attribute_exists != nil
return @attribute_exists
else
return nil
end
In the controller:
def check_exists
if params[:actiontype] == "new"
if params[:handle]
@user = User.attribute_exists?("handle", params[:handle])
if @user == nil
render :text => ""
else
render :text => "Username is taken. Please choose another."
end
elsif params[:email]
@user = User.attribute_exists?("email", params[:email])
if @user == nil
render :text => ""
else
render :text => "This email is registered. Please choose another."
end
end
elsif params[:actiontype] = "edit"
@cur_user = User.find(params[:id])

if params[:handle]
@user = User.attribute_exists?("handle", params[:handle])
if @user == nil || @user == @cur_user
render :text => ""
else
render :text => "Username is taken. Please choose another."
end
elsif params[:email]
@user = User.attribute_exists?("email", params[:email])
if @user == nil || @user == @cur_user
render :text => ""
else
render :text => "This email is registered. Please choose another."
end
end
end
end
A general rule of thumb is that code that requires communication with the database should be placed in the model side instead of the controller. The controller's job is to handle the user's request, retrieve data from the model and provide a response if necessary. The view's job is to handle presentation layer (i.e. HTML & CSS) for viewing purposes (thus called the View).

[/edit]

Monday 20 August 2007

Ruby on Rails and J2EE: Is there room for both?

An interesting article about the comparison of Ruby on Rails and J2EE. Both web technologies use the MVC (Model, View, Controller) framework.

You can read about it here.

Friday 17 August 2007

authentication feature

Please enable a forgot password feature - probably generating a temporary password to be emailed to user.

auto-complete textfield

We have all seen the famous Google Suggestions which was one of the driving factors to the rapid growth of AJAX applications, using real-time auto-completion without refreshing your browser.

Ruby on Rails makes this easy by using script.aculo.us javascript framework. A simple ajax auto-complete field only consists of two lines (with reference to the MVC framework):


#View
<%= text_field_with_auto_complete :model, :attribute %>

#Controller
auto_complete_for :model, :attribute

Viola! You've got yourself a working auto-complete textfield.


Saturday 11 August 2007

authentication

Currently working on FoR authentication by following this tutorial. Most likely be using SHA1 for hashing the password to be stored/compared with the password on database. Something to note that SHA1's digest is 40 characters long.

I have significantly modified the database table design with respect to the budgeting function. Will post an update on it later.

[edit date="17-08-07"]

Have got the authentication working now. The authentication checking is done in application.rb where all controllers have access to. Controllers that need protection simply need to add:
>> before_filter :authenticate

You can also define which methods within the controller you want to exclude protection by adding this parameter to before_filter:
>> before_filter: authenticate, :except => [ :method1, :method2]

The code for the authentication is:


def authenticate
unless @session[:user]
@session[:return_to] = @request.request_uri
@session[:intended_action] = action_name
@session[:intended_controller ] = controller_name
flash[:notice] = 'Login required.';
redirect_to :action => "login",
:controller => "user_admin"
return false
end
end

I have a controller called user_admin which handles all the user administrations (login, logout, sign up, etc.). The actual authentication logic is inside the User model. I also did a bit of research and found out that SHA-0 and SHA-1 can be broken through collision attack. Therefore, I decided to use SHA-512 (512 bit output) to hash the password and salted with a randomly generated 32-bit salt.

The actual password and salt creation:

def password=(pass)
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp
self.password_salt, self.password_hash = salt, Digest::SHA512.hexdigest(pass + salt)
end

So when you create a new user, to store the password_hash and password_salt, simply do:
>> @user.password = password

where password is the user's plaintext password.

[/edit]

Friday 10 August 2007

configuring environments

Just finished installing and configuring

  • Instant Rails
  • MySQL Tools
  • Notepad++
  • FileZilla
  • Smart Draw
on both machines (2A, 2B) in the EIE 417 labs.

It seems like Instant Rails works well in Windows and that will be chosen platform to work on. Runs Mongrel behind Apache because apparently Apache is hard to configure and everything is done for you in Instant Rails.

Chose Notepad++ as the ruby editor as well because Aptana doesn't have colour for ruby code (or at least I can't figure out how to do configure it).

FileZilla will be used to transfer files for backup onto our diesel account.

Smart Draw will be used to draw software diagrams.