This post is part of a series of reviews on the book Agile Web Development With Rails. Check out the Introduction post for a full table of contents along with some initial notes.

As said in the initial post, the primary objective of this to-be-built-application is to develop a simple online store, which basically manages product listing and sales in general (more details will come as we develop the application).

The first thing we need to do is to build a way to maintain our products, which are the basic essence of an online store. Maintaining includes creating, editing, deleting, listing, and so forth. And to do that is actually easier said than done, the Rails framework supplies all the tools we need. So, assuming you have your Rails up and running smoothly with your database (I’m using MySQL) and that you understand how Rails work (MVC architecture), let’s get to work.

Rails offers a scaffold script generator that creates a basic CRUD environment, which is perfect for our initial learning scenario. With a single line of code, Rails creates all the files necessary, including the model, the controller and the view files. At your application directory in your terminal, execute this:

 ruby script/generate scaffold product title:string description:text image_url:string 

Voillá. If done correctly, a ton of output should have been flooded into your terminal and with one line of code you already have a mini-application that can be accessed by activating your web server (ruby script/server at the terminal) through http://localhost:3000/products . Play around with it a bit, its quite fun.

Walking away from the excitement for a while, let’s take note of another important concept: migrations. Migration is the way that Rails deals with the database, and each migration mean a change that we want to make to the database, which is represented by a source file in the db/migrate folder of your application. Each migration is named with a message and a timestamp that is set to the time that a migration is created. This concept has a partner called ‘rake db:migrate’, which is a command that looks for migrations (wanted changes to the databases) that have not been applied to the database yet, and applies them.

Let’s go back to the fun, shall we? So, we (by we, I mean the script generating command) created a table called products which has 3 attributes: title, description and image_url. I’m pretty sure we forgot about something which is propably what matters the most to everyone: the price. We’ll do this by using the concept of migrations we just learned. Why not just open up mysqladmin and execute an alter table add column command? Because that leaves no version-controlled history, and its nice to keep track of changes made on the database and inform these changes to fellow programmers working on the same project as you, which is where migrations come in handy. To create a migration (in this case, to add a new price column), execute this:

ruby script/generate migration add_price_to_product price:decimal 

After creating a migration its necessary to execute the rake command, but before we do that, let’s format that price. Open up the migration file on the db/migrate folder in your application directory and make it look like this:

class AddPriceToProduct < ActiveRecord::Migration
 def self.up
  add_column :products, :price, :decimal,
  :precision => 8, :scale => 2, :default => 0
 end
 def self.down
  remove_column :products, :price
 end
end

As you might have noticed, we added the fourth line of code with some formatting arguments: precision tells the database to store eight significant digits for the price column, and scale says that two of these digits will fall after the decimal point. Now we can submit this change to the database:

rake db:migrate

Although we made changes in the database, nothing has been changed on the view files, so let’s add some changes in the HTML to make the new price field appear (no changes need to be done to the controller). Index.html.erb file

<th>Price</th>
<td><%=h product.price %></td>

New.html.erb and edit.html.erb files

<p>
<%= f.label :price %><br />
<%= f.text_field :price %>
</p>

Show.html.erb

<p>
<b>Price:</b>
<%=h @product.price %>
</p>

There we go, new price column added with almost no effort, just refresh the page to immediately see the changes. As we all know, validation is an important subject when developing web applications, because we just can’t let users do whatever they want, or else they’ll just mess up everything. The good thing about this is that with Rails, validation means 1 line of code, lets see how its done. Since the model is the one responsible of dealing with data before it gets to the database, let’s put the validation code in there.

class Product < ActiveRecord::Base
 validates_presence_of :title, :description, :image_url
 validates_numericality_of :price
 validate :price_must_be_at_least_a_cent
 validates_uniqueness_of :title
 validates_format_of :image_url,
 :with => %r{\.(gif|jpg|png)$}i,
 :message => 'must be a URL for GIF, JPG ' +
 'or PNG image.(gif|jpg|png)'
protected
def price_must_be_at_least_a_cent
  errors.add(:price, 'should be at least 0.01' ) if price.nil? ||
  price < 0.01
 end
end

Explaining that code:
– validates_presence_of checks whether a field is empty or not.
– validates_numericality_of checks if what was typed is a number or not.
– validates_uniqueness_of checks if the input sent is equal to some previous saved value thas has to be unique.
– values_format_of checks if the input is within the permitted filetypes.
– validate :price_must_be_at_least_a_cent calls on a custom function that checks if the price submitted is below a cent (it also checks for nil to avoid giving an error page), adding a custom error to the errorlist.
With that code, all data that does not match the validation will not go into the database and an error message will show pointing out to the user what he did wrong, test it yourself and see the wonder of a couple of lines of code that has just made our application a lot more solid.

Let’s wrap this up in a bow and finish by styling off our application. To do this you will need to download these:
A migration file
Some images
A stylesheet

Throw them in their respective directories in your application folder (db/migrate, public/images, public/stylesheets). So what’s the migration file for? For test data. Instead of typing your own data into the browser, its easier to get a migration file and migrate it to the database, it will automatically clean up the products table and add 3 rows of test data do it, so be warned that any data you have previously injected will be deleted. But for the migrate command to recognize a new migration, you need to create one first, so here we go to the terminal again:

ruby script/generate migration add_test_data

Delete the newly created file at the db/migrate folder, put the downloaded one there and type the migrate command in the terminal:

rake db:migrate

But don’t refresh the main page yet, we need to set the new style sheet that is still not recognized by your application because of the default scaffold stylesheet its now using, so we need to change that. Open up the products.html.erb at the views/layouts folder in your application directory and change the 8th line of code to this:

<%= stylesheet_link_tag 'scaffold', 'depot' %>

Now that the stylesheet is in place, lets change the whole index layout to suit the new style: Index.html.erb file

<div id="product-list">
<h1>Listing products</h1>
<table>
<% for product in @products %>
<tr>
<td>
<%= image_tag product.image_url, :class => 'list-image' %>
</td>
<td>
<dl>
<dt><%=h product.title %></dt>
<dd><%=h truncate(product.description.gsub(/<.*?>/,''), 80) %></dd>
</dl>
</td>
<td>
<%= link_to 'Show', product %><br/>
<%= link_to 'Edit', edit_product_path(product) %><br/>
<%= link_to 'Destroy', product,
:confirm => 'Are you sure?',
:method => :delete %>
</td>
</tr>
<% end %>
</table>
</div>
<br />
<%= link_to 'New product', new_product_path %> 

Now we have a styled and solid product maintenance system going on, refresh the page and check out the results of this first task.