Tuesday, April 21, 2009

Registering Users and More

For this post we start with the post Using Ramaze and Sequel Together code and add in Registration for users. We're also going to add in a couple of more things along the way which is not in keeping with my normal way of doing things, but in this case they're pretty simple. Quite a lot of this post, I realized while working on it, was developed using things I learned from RailsSpace and the associated book. If you're interested in learning Rails at all, you should check this out. I had much more success with it than with the Agile Web Development With Rails (YMMV). Anyway, along with the Registration page, we're going to add in a place holder for About and Help pages along with a Logout page. We're also going to be doing a bit of CSS (mostly Cargo Culting from the RailsSpace book).

Here's our first database migration from dbMigration

# dbMigration/001_LoginMigration.rb
# Run:
# sequel -m dbMigration -M 1 sqlite://accounts.db
# from the top level directory to create the :accounts table with login and
# password columns.
#
class CreateAccountsTable < Sequel::Migration

# For the up we want to create the account table.
def up
# Create the accounts table with a primary key, a login, and a
# password.
create_table(:accounts) do
primary_key :id
String :login, :unique=>true
String :password
end
end

# For the down we want to remove the accounts tables.
def down
drop_table(:accounts)
end
end



this creates the account table and put in the login and password columns. This code is directly from the post mentioned above. In order to make this a bit more realistic, I've added a second migration:

# dbMigration/002_AddEmailMigration.rb
# Run:
# sequel -m dbMigration -M 2 sqlite://accounts.db
# from the top level directory to add the email column to the accounts table.
#
class AddEmail < Sequel::Migration

# For the up we want to add the email to the accounts table.
def up
alter_table(:accounts) do
add_column(:email, :text)
end
end

# For the down we want to remove the email column.
def down
alter_table(:accounts) do
drop_column(:email)
end
end
end



which takes the existing accounts table and adds in the email column. Just run a sequel -m dbMigration -M 2 sqlite://accounts.db which should run both of the migrations in order giving us the table as well as the three columns.

OK, here's the first bit of real code, our start.rb

# start.rb
#
# This is the main program for the example. It loads the Sequel database,
# loads the controller and model, and then starts up Ramaze.
#
#
# You can add new accounts on the registration page.
require 'rubygems'
require 'ramaze'
require 'sequel'

# The database should have been set up using the database migrations (there are
# currently two of them) in the dbMigration directory. Run them with:
# sequel -m dbMigration -M 2 sqlite://accounts.db
# Open the accounts database. This must be done before we access the models
# that use it.
DB = Sequel.sqlite("accounts.db")

# Load the controllers and models (one of each in this case).
require 'controllers/main_controller'
require 'models/account'

# Start Ramaze.
Ramaze.start



This just opens the database, and once again we're using Sqllite, loads the one controller and then the one model, and finally starts ramaze.

The real work of this program is done in the controller, controllers/main_controller.rb. Here's the listing:

# controllers/main_controller.rb
#
# This example is based on the previous "Using Models" example. It has a
# number of methods . The index method will take data from a form and call the
# Account.find method. If the Account.find method returns something (we aren't
# actually going to use what's returned), we will set the session variable,
# and redirect to the logged_in method. If it returns false we will set the
# flash variable and stay on the index page.
class MainController < Ramaze::Controller
# Use page.xhtml in the view directory for layout
layout :page

# Set up a helper to check if we're logged in and only allow access
# to the :logged_in page if we are. This is probably the hard way to
# do this for only the single page but will make much more sense if
# we add more pages as we'd do in a real application.
helper :aspect
before(:logged_in) {
unless logged_in?
# Set the flash message which will only be available in the next
# screen. In this case that will be the logged_in screen.
flash[:message] = "You must log in before accessing the requested page."
redirect Rs(:index)
end
}

# You can access it now with http://localhost:7000/ This should display a
# welcome message. This is the home page.
def index
@title = "Welcome to SteamCode"
end

# Register a new user with SteamCode. We will get here from the
# views/register.xhtml page where the user will put in their (requested)
# login, password, and email address. First find if the user already
# exists, if it does, then we'll set a message to tell the user so and
# redirect them back to the register screen. If not, we'll go ahead and add
# them to the database with the appropriate login, password, and email
# address. We'll then send them to the login screen to let them log in to
# SteamCode.
def register
@title = "Register with SteamCode"
# Make sure we're getting here from a post request.
if request.post?
# Check the login and password.
# if we find the Account based on the login and password. If we find it
# we'll save the login ID in the session variable and we can use that
# to show if the Account is currently logged in or not. If we can't
# find the Account, we'll set the flash message, set the session to nil
# and just stay on this page.
if Account.find(:login => request[:loginID])

# This user already exists. Set the flash message for them to
# try again.
flash[:message] = "Login #{request[:loginID]} already used. Please select another."

# Stay on the register page.
redirect Rs(:register)
else
# This account does not exist. Grab the loginID, the password,
# and the email and create a new Account with them.
loginID = request[:loginID]
password = request[:password]
email = request[:email]

# Log the new user (a real application wouldn't probably print
# the password out though).
Ramaze::Log.debug "New User Added: loginID = #{loginID} password = #{password} email = #{email}"

# Create the account with the login, password, and email given.
Account.create(:login => loginID, :password => password, :email => email)

# Redirect to the login page.
redirect Rs(:login)
end
end
end

# You can access it now with http://localhost:7000/login
# This should display a form with a login and a password as
# well as a "Login" button in your browser.
def login
# Set the title for the page.
@title = "SteamCode Login"
# Make sure we're getting here from a post request.
if request.post?
# Check the login and password.
# if we find the Account based on the login and password. If we find it
# we'll save the login ID in the session variable and we can use that
# to show if the Account is currently logged in or not. If we can't
# find the Account, we'll set the flash message, set the session to nil
# and just stay on this page.
if Account.find(:login => request[:loginID], :password => request[:pw2])
# Use the name= portion of the input form to grab the data
# from the request variable and save it in the session
# hash table.
session[:loginID] = request[:loginID]

# Redirect to the logged_in screen.
redirect Rs(:logged_in)
else
# The login could not be authorized. Set the flash message
# and stay on this page (index/login). Set the session loginID
# to nil also. This will effectively log the user out. This would
# be reasonable if they are logged in and then try to log in with
# a new login/password.
flash[:message] = "Incorrect password, please try again!!!"
session[:loginID] = nil

# Stay on the login page.
redirect Rs(:login)
end
end
end

# Set the login ID variable so we can issue a welcome message on the
# logged_in page.
def logged_in
@title = "SteamCode Logged In"
@loginID = session[:loginID]
end

# Logout of SteamCode. Sets the session[:loginID] to nil, sets the flash
# message, and goes back to the home page.
def logout
session[:loginID] = nil
flash[:message] = "Thank you for using SteamCode."
redirect Rs(:index)
end

# The about action. Sets the about title. This also uses the view/about
# xhtml file.
def about
@title = "SteamCode About"
end

# The help action. Sets the help title. This also uses
# the view/help xhtml file.
def help
@title = "SteamCode Help"
end

private

# A private helper method that returns true if there is a
# session[:loginID] value set and false otherwise.
def logged_in?
return session[:loginID] != nil
end
end


As normal, we start with a previous post, in this case the Using Ramaze and Sequel Together post. In this case our index page just sets the title "Welcome to SteamCode". The next method is our register which allows us to create a new user (recall in our last version, we just hard coded everything to work with hello/world as a username/password combination). The code here checks the login/password that is passed in from the form and if it already exists, sets a flash message and redirects the user back to the Registration page. Otherwise it will grab the login/password/email from the request hashtable, create a new account, and redirect the user to the login page to allow them to login. One thing to note here is that a couple of people on the Ramaze mail list suggested just creating the account and catching any errors that come back rather than the two piece way of doing things shown here. I kind of like that way, but this seems more intuitive and easier for people just starting out to understand, but note that you may see it done differently in other examples from other people.

The next method is the login and that is very similar to our last example. We simply take the login/password from the request hash and check if they are in the database. If they are, we log the user in and redirect to the logged_in page. Otherwise we add a flash message and redirect back to the login page to allow them to try again. A "real" system would do things like keep count of the number of tries for a login and possibly lock the user out for some time period. Here, once again, we're keeping it simple.

The logged_in method is where the user ends up after a successful login. In most applications, this would be the user's "home" page. Here, set the title and grab the login id so we can greet the user.

Next up is the logout method. This is very simple and sets the user's session[:loginId] to ni, sets a thank you flash message, and finally redirects the user back to the home page.

The next two methods, about and help merely set the title for the two actions.

Finally we have the logged_in? private method which is a helper method which let's us know if the user is logged in to the system or not.

Next up, let's take a look at our model, in models/account.rb.

# models/account.rb
#
# This is the model for the Account and is backed by the :accounts table in the
# database. For this simple example we don't need anything but the definition.
require 'rubygems'
require 'sequel'

# Create the Account model.
class Account < Sequel::Model
end


Once again, as in our previous post, there is really nothing in here except for the Account class being derived from Sequel::Model.

Our view directory has a number of files now corresponding to our new methods from the controller. First our page.xhtml has a few new twists from the previous version.

<!-- view/page.xhtml -->

<!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">
<head>
<!-- Use the page.css in the public directory and set title based on
what's set in the associated method.
-->

<link rel="stylesheet" type="text/css" href="/page.css"/>
<title>#@title</title>
</head>
<div id="header">
<h1>SteamCode</h1>
</div>
<div id="nav">
<!-- Move this next section over to the right side of the screen. It
will contain the Login/Register if we're not logged in and the Logout
if we are. -->

<span style="float: right">
<!-- We're going to use the private method logged_in? here to test if
we want to showhte Login/Register links or the Logout link -->

<?r if !logged_in? ?>
<a href="#{R(MainController, :login)}">Login</a> |
<a href="#{R(MainController, :register)}">Register</a>
<?r else ?>
<a href="#{R(MainController, :logout)}">Logout</a>
<?r end ?>
</span>

<!-- These next three will be on the left side and always there -->
<a href="#{R(MainController, :index)}">Home</a> |
<a href="#{R(MainController, :about)}">About Us</a> |
<a href="#{R(MainController, :help)}">Help</a>
</div>
<body>
<!-- Display the flashbox message -->
#{flashbox}

<!-- Display the actual content. This will come from the method or the
associated view/*.xhtml file
-->

#@content

<!-- Set the footer in the center of the screen. -->
<div id="footer" style="text-align: center;">
<h5> Powered by Ramaze </h5>
</div>
</body>
</html>


First, the head now contains a style sheet, page.css.

/* Header CSS */
#header {
background:#9DA9EE;
color: white;
margin-bottom: 0;
padding: 0.25em;
}

#nav {
background: #ccc;
color: black;
padding: 0.5em;
}

/* Footer CSS */
#footer {
background:#9DA9EE;
color: black;
}

There's not too much in there, just the beginnings of what you would have in a "real" style sheet including styling for the header, footer, and nav.

Next the page.xhtml has the header, now styled a bit, followed by our nav section. Here we use a bit of ruby to decide what to show. If the logged_in? method from the controller is not true, we display the Login and Register links. If it is true, then we display the Logout link. I don't think we've discussed the Ruby aspect of this before. but in our views, we can use the "" and put in any valid ruby code and we have access to methods in the controller as we've used here. After that, we have the three links that are always displayed on the right hand side, index, about, and help. index is our main page and about and help are really just place holders for now. Here's the views for all of the above:

<!-- view/login.xhtml -->
<form id="login" method="post">
<div>
<!-- for= goes with id=, the name= is placed in the request variable. -->
<label for="nick">Login:</label>
<input id="nick" name="loginID" type="text" />
<br/>
<label for="pw1">Password:</label>
<input id="pw1" name="pw2" type="password" />
<br/>
<input type="submit" value="Login" />
</div>
</form>


<!-- view/register.xhtml -->
<form id="register" method="post">
<div>
<!-- for= goes with id=, the name= is placed in the request variable. -->
<label for="loginID">Login:</label>
<input id="loginID" name="loginID" type="text" />
<br/>
<label for="password">Password:</label>
<input id="password" name="password" type="password" />
<br/>
<label for="email">Email:</label>
<input id="email" name="email" type="text" />
<br/>
<input type="submit" value="Register" />
</div>
</form>


The login and register views above both contain forms that the user can fill in and pass to the appropriate methods in the controller and their associated methods were discussed above. For the register, you put in a login, (that's checked in the controller to make sure there's not already a user with that login), a password (no error checking here, so get it right), and an email address (unused through the rest of the system). Obviously, in a real system, you would do things differently. On the login page, the user puts in a login/password pair and the controller authenticates and then logs the user in. Once the user is logged in to the system, they will go to the logged_in page

<!-- view/logged_in.xhtml -->
Welcome to SteamCode: #@loginID


Here they're just shown a welcome message with their user name from the logged_in method in the controller.

Nothing really in the index (the main page), about, or help pages shown here

<!-- view/index.xhtml -->
<h2>This is the Steamcode main page</h2>


<!-- view/help.xhtml -->
<h2>This will contain a FAQ on all of the things you can do with
SteamCode.</h2>


<!-- view/about.xhtml -->
<h2>This will talk all about SteamCode and all of the wonderful things we do.</h2>


So there you have it. If not the beginnings of a web application, at least the beginnings of the beginnings of a web application. As always, let me know if you have any questions or comments and I'll do my best to answer them. Also, if you end up building any Ramaze apps, let me know as I'd love to see what can be done and more importantly how things are done.

No comments:

Post a Comment