Wednesday, March 25, 2009

Using Ramaze and Sequel Together

In this post, we'll begin to use Ramaze and Sequel together. This is based, as is usual, on a previous post Using a Simple Model, but here we'll actually use a database backed model with Sequel. We'll start out by creating a database using a Sequel migration which was discussed more fully here. I've put the migration in a subdirectory called dbMigration. Here's the code:


# 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 three tables.
def up
# Create the accounts table with a primary key and a title.
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


To create simply run:

sequel -m dbMigration -M 1 sqlite://accounts.db

which should create a database accounts.db in the main directory with a table accounts with columns for a login and password. Here we're going to cheat just a bit (OK, quite a bit) to keep things simpler. Normally, in an application like this one, we'd have a registration page that would allow us to create new accounts (and we'll probably add that in a future post), but here we're just going to put a new account in using some code. Here's the dbload.rb file that puts in a single account with loginid "hello" and password "world":


# dbload.rb
#
# Run this file after the running the database migration
# to create a login/password. This is a quick hack since we
# don't have a page to add them with.
require 'rubygems'
require 'sequel'
DB = Sequel.sqlite("accounts.db") # Open the accounts database
require 'models/account'
Account.create(:login => 'hello', :password => 'world')


Now let's look at start.rb which starts things off. In this version of our code, I've moved the controller into its own directory in the same way that the models and views have their own directories. Here's the code:


# 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.
#
# The database should have been set up using the database migrations in
# the dbMigration directory. Since there's currently no way to add login/password
# pairs, we're going to cheat and run the dbload.rb file which will create a
# login "hello" with a password "world". Normally, this would be handled with a
# registration page.
require 'rubygems'
require 'ramaze'
require 'sequel'

# 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'

Ramaze.start


This is all pretty straight forward. The only mildly tricky part is that we need to open the database before we bring in the model. The database we open is the one we created with the database migration and loaded with the dbload. Finally, as is normal, we start Ramaze.

Our model is very simple. It only contains the Account which is derived from the Sequel::Model. Here's the code:


# 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


The controller class in this example is where all of the real work is done. We still have our two methods index and logged_in from before. The index logs a user into the system and the logged_in happens after the user is authorized. Before either of these though is some new code that is used to protect our pages from users that are not logged in. In our earlier examples, you could actually go to the logged_in page without having been authorized. What the new code, helper aspect / before, does is prevent that. We check the session variable for a loginID not equal to nil and redirect to the index (login) page if it is nil. If it's not, we'll just continue on with normal processing. This code happens before the logged_in page is called. If we add additional pages, they can be protected by simply adding them in the before() list. There is also an equivalent after call that can be used in a similar way as well as before_all and after_all that can be used without a list of methods. Here's the controller:


# controllers/main_controller.rb
#
# This example is based on the previous "Using Models" example. It has two
# methods (index and logged_in). 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 session[:loginID]
# 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 form with a login and a password as
# well as a "Login" button in your browser.
def index
# 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(:index)
end
end
end

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


The only other interesting thing in the controller is the use of the model Account to determine if an account exists for a given login/password. We use the find method and pass in the values from the form, as we've done in previous posts, to check if this is a valid account. If it is, we set the session variable with the loginID and redirect to the logged_in page. This will trigger the before check that we discussed earlier. If we could not find this user (and in this case there will only be one), we set the flash variable and redirect back to the index page to let the user try again. We also set the session/loginID value to nil here to clear it out.

Here's the view pages starting with view/page.xhtml:

<html>
<head> <title>Using Sequel and Ramaze Together</title> </head>
<body>
#@content
<h5> Powered by Ramaze </h5>
</body>
</html>


Here's the view/index.xhtml which contains our login form:

#{flashbox}
<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>


Finally, view/logged_in.xhtml which shows a welcome message based on the loginID set in the logged_in method of the main controller:

#{flashbox}
Welcome to Our Site: #@loginID


As always, please post your questions and comments.

4 comments:

  1. I have question that is not really related to the ramaze question. What editor are you using for ruby and html? I like the way you get color for your code in this post. Can you share with me how did you do it?

    Thanks,
    Luan

    ReplyDelete
  2. Luan, I use vim for editing. For generating the color, I use maraku. I did a post on this and you can find it here: http://steamcode.blogspot.com/2009/02/blogfmtrb.html. If you email me offline, I'll send you a new version that I just created. Use my handle here at gmail dot com.

    ReplyDelete
  3. Great writeups here!

    I reckon beginners would love to know that a Sequel::Model automagically gets "connected" to the corresponding table in the DB according to its class name. Maybe you could put that up somewhere to fix that "missing link" sensation.

    ReplyDelete
  4. Arnaud, You're right, I never really discuss that. I'm doing a new post now and will make sure to include it.

    Glad you're enjoying the posts!

    ReplyDelete