This week we continue from our look at the Ruby language with an examination of the Ruby on Rails framework.
Ruby on Rails is a framework which allows easy development of web applications using the Ruby language. The key feature of Ruby on Rails is that a lot of the common web development tasks are "wrapped up" in the framework, so you can develop code which queries or modifies a database with very little effort.
Ruby on Rails is based on a common software development architecture known as Model/View/Controller. Under Model/View/Controller, the application is broken down into three components:
In Rails, a controller represents a particular entity or "thing" such as a song, a user or a person. Controllers contain a set of methods or actions which do certain things to that "thing", and each action maps to a URL. Therefore, the controller is the entry point to the application from the web browser. For example, for a controller called hits, representing a hit (song) in the HitTastic! database:
http://hittastic.com/hits/update/1024We would post form data (the view) to that URL and use it to update the song with the ID of 1024 in the database (the model).
http://hittastic.com/hits/get_details/1024This controller method would retrieve all the details (from the model) of the song with the ID 1024 and present the details to the user as a web page (the view).
Rails is a bit different from most server side scripting techniques in that a lot of code is pre-generated for you. You can easily create a basic application to read and modify a database without writing a single line of code!
Here are some step by step instructions to generate a basic Rails application. The first example does not involve a database, but it will allow us to examine the structure of a Rails application.
rails helloThis generates a Rails application called hello. You'll also see a new folder called hello appear.
cd hello
ruby script/generate controller meThis generates a controller called me, which is designed to represent a person. Each controller typically represents an entity of some kind, such as a person or a song. In this way, Rails applications follow REST principles.
cd app/controllersYou'll notice an auto-generated file called me_controller.rb. This file will store all the controller code relating to the me entity. Edit this file and modify it so it looks like this:
class MeController < ApplicationController
def name
@n = "Barack Obama"
end
def age
@a = Time.now.year - 1961 - 1
end
end
(replace Barack Obama and 1961 by your name and year of birth respectively.
The -1 is because it's so early in the year and chances are you won't have
had your birthday yet)
cd ../app/views/meand create two files, name.rhtml and age.rhtml. The first is a view for the "name" method of the "me" controller, and the second is a view for the "age" method of the "me" controller.
<!-- name.rhtml --> <html> <body> My name is : <%= @n %> </body> </html>
<!-- age.rhtml --> <html> <body> My age is : <%= @a %> </body> </html>Note the following:
cd ../../..Now start the server from the InstantRails GUI window:
http://localhost:3000/me/name http://localhost:3000/me/ageAll being well, the first URL will bring up your name, and the second, your age.
The example above was designed to get you used to the layout of the various files which make up a Rails application, in particular the controller and views. However a real Rails application is likely to talk to a database. The InstantRails environment we are using comes with a MySQL database.
rails hittasticand move to the hittastic folder generated:
cd hittastic
mysql -u rootand create a database:
mysql> create database hittastic_development;Tell MySQL that we're using this database:
mysql> use hittastic_development;and create the hits table in the database:
mysql> CREATE TABLE hits (id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(255), artist VARCHAR(255), year VARCHAR(255), chart VARCHAR(255), quantity INT, price FLOAT)Finally exit the database client:
mysql> quit
ruby script/generate scaffold HitThis will generate a scaffold called Hit, to represent hits. Note that the scaffold name is always the singular of the table name, with the initial letter capitalised, e.g. for a table hits it would be Hit; users becomes User.
To help you understand what's going on with Rails code, we'll go through one of the controller methods, namely the show method of the hit controller.
def show
@hit=Hit.find(params[:id])
end
The show method of the hits controller will be called by a URL of the
form:
http://localhost:3000/hits/show/1much like the simple me controller above. Note however the additional "1" on the end. This is a parameter, representing the ID of the hit. Behind the scenes the "1" is converted to a query string parameter id with a value of 1. (One of the features of Rails is that it uses clean REST-style URLs).
http://localhost:3000/hits/show/1the variable @hit will contain hit number 1.
If you play around with our pre-generated HitTastic! application, you'll find you can do quite a few things: you can add new hits, you can edit hits, you can delete hits and you can list all hits. However we might want to add one or two extra items of functionality. In the HitTastic! application we might wish to:
http://localhost:3000/hits/buy/1 http://localhost:3000/hits/restock/1Note once again the descriptive, REST-style URLs.
Carry out the following steps:
link_to 'Show', { :action=>'show', :id=>hit }
This code will call the link_to method, and pass it two parameters:
def buy
@hit=Hit.find(params[:id])
@hit.buy
redirect_to :action => 'list'
end
Note how this is working:
class Hit < ActiveRecord::Base endThis means "the Hit class inherits from the ActiveRecord::Base class". ActiveRecord::Base is an inbuilt class which is part of Rails: it contains methods for interacting with a database. Note how the Hit class has no actual code in it yet. However, you can perform several actions (such as finding a hit by ID with the find method) on a Hit object because these methods are part of the inbuilt ActiveRecord::Base class.
def buy
if self.quantity > 0
self.quantity = self.quantity - 1
save
end
end
This code is saying "if the quantity of the current hit is greater than 0,
reduce its quantity by 1 and save the change in the database".
self is a reference to the current object: basically, it's
rather like this in Java.http://localhost:3000/hits/listand try buying a hit.
OK, that works, but it would be nice to display an error message if the selected hit is out of stock. How do we do that? First return to the model and modify the buy method so that it looks like the following:
def buy
if self.quantity > 0
self.quantity = self.quantity - 1
save
return true
else
return false
end
end
Note how the buy method of the Hit class is now returning true if
the hit was bought successfully, or false if it is out of stock.
We can take advantage of this in the controller, as we will do in a moment:
we can react differently depending on the value that the buy method
returns.
def buy
@hit = Hit.find(params[:id])
if @hit.buy==true
flash[:notice] = 'Hit successfully bought'
else
flash[:notice] = 'Out of stock'
end
redirect_to :action => 'list'
end
What we are doing here is calling the find method of the Hit class in the
model, and testing if it returns true or false. If it returns true, we
set up a flash message (see below) indicating that the hit was
successfully bought. This will be displayed in the view, see below
(remember that all
displaying must be done in the view, not in the controller or the model).
If not, we bring up a similar message indicating that
it's out of stock.
<h1>Update quantity of existing hit</h1> <% form_tag :action => 'do_restock', :id => @hit do %> <p><label for="quantity">Amount to increase stock by:</label><br/> <input name="quantity" id="quantity" /> <input type="submit" value="Go!" /> <% end %> <%= link_to 'Show', :action => 'show', :id => @hit %> | <%= link_to 'Back', :action => 'list' %>Much of this is just HTML, but note the form_tag command, this is a Ruby call which automatically sets up a form with an action of 'do_restock' (we'll come onto this in a moment) and passes the ID through the form.
http://localhost:3000/hits/restock/1All we need to do for the controller is to find the hit, so that it can be passed on to the view, so that the view knows which song we are re-stocking. So just add this code:
def restock
@hit = Hit.find(params[:id])
end
We've created a restock action which is associated with a view allowing the user to enter an amount to increase the stock by. However now we need a do_restock action which actually restocks the hit, i.e. increases its quantity in the database. If you go back to the view for the restock action, note how the form posts to do_restock already, so all we need to do is write the code for the do_restock action.
<% form_tag :action => 'do_restock', :id => @hit do %>In other words, the form keeps a record of the ID, which is posted along with the quantity. So our controller code should read the ID and quantity from the form using the params hash (which is rather like $_POST in PHP):
def do_restock
@hit=Hit.find(params[:id])
if @hit.restock(params[:quantity].to_i) == true
flash[:notice] = 'Success'
else
flash[:notice] = 'Invalid quantity'
end
redirect_to :action => 'list'
end
The do_restock controller is working in a similar way to the
buy controller, above. We find the hit with the given ID, then
we call a restock
method in the model (which we haven't written yet), test its
return value, and set up ana appropriate flash message.
def restock(amount)
if amount > 0
self.quantity = self.quantity + amount
save
return true
else
return false
end
end
Hopefully this should be fairly self explanatory: the restock method
takes a parameter representing the amount to increase the stock by, then
the quantity is increased by that amount and the changes
saved to the database. Finally we return true or false depending if there
was an error: if the supplied amount was 0 or less, we return false to
indicate an error.A Rails application contains: