No Gravatar

Hello again! I’ve been quite busy in between jobs and haven’t had much free time to work on my postings…well, let’s make up for lost time! Last time we spoke, I left you with a Rails app that made use of a basic relational database that contained books and associated authors. Let’s move the chains and introduce a few new concepts that you’ll be able to use with your own applications.

In this installment we’re going to create a landing page so visitors don’t get dumped to your book controller, use a partial, learn about the before_filter, and add some basic HTTP authentication.

if/for blocks and Rails’ partials

The first thing we’re going to do is create a new landing page so visitors don’t get dumped onto the scaffolding, but first reset the database. I had some random entries there I wanted to wipe out, we can do this with:

ng:bookstore ng$ rake db:reset

Next, we’ll be generating a new controller to handle the requests for our landing page; following convetion this controller will be plural…call it “homes”.

ng:bookstore ng$ script/generate controller homes
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/homes
      exists  test/functional/
      create  app/controllers/homes_controller.rb
      create  test/functional/homes_controller_test.rb
      create  app/helpers/homes_helper.rb
ng:bookstore ng$

If you try to access http://localhost:3000/homes you’ll get a routing error; know what to do? Bingo! Let’s add a resource to our route and change the default map.root controller. I’ve removed the comments, but your routes.rb file should look similar to this:

ActionController::Routing::Routes.draw do |map|
  map.resources :authors
  map.resources :books
  map.resources :homes 
 
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
  map.root    :controller => 'homes'
end

Now that our routes are set up, we need to create an index.html.erb file. We’ll place this in the apps/views/homes directory. You will be able to access the homes controller by either of these urls:

  • http://localhost:3000/
  • http://localhost:3000/homes

Our index file is quite barren right now, let’s add some code to so we can list our current book inventory. First, we need data to play with! Open your apps/controllers/homes_controller.rb and add an index method. Let’s pull all records from our database and make it available to our view.

class HomesController < ApplicationController
 
  def index
    @books = Book.find(:all)
  end
 
end

Now that we’ve defined our data, we can loop through it using a basic for loop. I’ve set up the table structure with a header row, the individual rows are generated by our loop.

<table>
	<tr>
		<th>Title</th>
		<th>Description</th>
	</tr>
	<% for book in @books %>
	<tr>
		<td><%=h book.title %></td>
		<td><%=h book.description %></td>
	</tr>
	<% end %>
</table>

It essentially reads, “For each book in our @books collection, output a row with two columns. In in first column output an html escaped book title, in the second column output a html escaped book description. Once we’ve cycled through all the data in our @books collection, end the loop.”

I’m going to plug the above code into our index file.

<h1>Our Bookstore</h1>
 
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus sed nisi. Morbi aliquam ornare metus. Aliquam sed pede quis nulla porta rhoncus. Ut adipiscing aliquam enim. Sed laoreet justo in turpis. Aenean metus lorem, mollis elementum, pulvinar non, pharetra at, metus. Phasellus interdum tortor non tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer facilisis, erat sed aliquam tristique, mi augue tristique mauris, in mollis turpis tellus vel sem. Vivamus ac nibh ac lectus venenatis blandit. Morbi eget sem ac turpis sodales tincidunt. In in nunc commodo ante venenatis ornare. Aliquam sit amet ligula.	
</p>
 
<h2>Current Inventory</h2>
<table>
	<tr>
		<th>Title</th>
		<th>Description</th>
	</tr>
	<% for book in @books %>
	<tr>
		<td><%=h book.title %></td>
		<td><%=h book.description %></td>
	</tr>
	<% end %>
</table>
 
 
<p>Aliquam vestibulum ultricies velit. Vivamus pulvinar urna. Mauris tincidunt blandit massa. </p>

Visit your bookstore. It looks kind of weird with no books eh? Let’s notify our visitors that we don’t have any books in stock; we’ll do this with a simple if statement.

<h1>Our Bookstore</h1>
 
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus sed nisi. Morbi aliquam ornare metus. Aliquam sed pede quis nulla porta rhoncus. Ut adipiscing aliquam enim. Sed laoreet justo in turpis. Aenean metus lorem, mollis elementum, pulvinar non, pharetra at, metus. Phasellus interdum tortor non tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer facilisis, erat sed aliquam tristique, mi augue tristique mauris, in mollis turpis tellus vel sem. Vivamus ac nibh ac lectus venenatis blandit. Morbi eget sem ac turpis sodales tincidunt. In in nunc commodo ante venenatis ornare. Aliquam sit amet ligula.	
</p>
 
<% if @books.empty? %>
<h2>We don't have any books in stock, but they're coming!</h2>
<% else %>
<h2>Current Inventory</h2>
<table>
	<tr>
		<th>Title</th>
		<th>Description</th>
	</tr>
		<% for book in @books %>
		<tr>
			<td><%=h book.title %></td>
			<td><%=h book.description %></td>
		</tr>
		<% end %>
</table>
<% end %>
 
<p>Aliquam vestibulum ultricies velit. Vivamus pulvinar urna. Mauris tincidunt blandit massa. </p>

Pretty cool eh? It’s these little methods that make me smile! Let’s take another look at our index file…take a peek and ask yourself if anything looks out of place? Does that for loop bother you? It bothers me! I don’t like to see lots of code in my views…let’s get rid of it!

Rails has a handy feature called partials; partials are little fragments of code that can be included, and reused in your views. All partials take follow the same naming convention: “_filename.mimetype.erb”. Don’t worry about mimetypes right now, for the most part you’ll be something like _filename.html.erb or _filename.xml.erb. Create a new partial named named “book listings”, then place our if/for block in the partial.

<!-- _book_listings.html.erb -->
<% if @books.empty? %>
<h2>We don't have any books in stock, but they're coming!</h2>
<% else %>
<h2>Current Inventory</h2>
<table>
	<tr>
		<th>Title</th>
		<th>Description</th>
	</tr>
		<% for book in @books %>
		<tr>
			<td><%=h book.title %></td>
			<td><%=h book.description %></td>
		</tr>
		<% end %>
</table>
<% end %>

Now that we have a partial, we need to call it. render :partial => ‘partial_name’. Render the partial in our index view where our old if/for block was.

<!-- index.html.erb -->
<h1>Our Bookstore</h1>
 
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus sed nisi. Morbi aliquam ornare metus. Aliquam sed pede quis nulla porta rhoncus. Ut adipiscing aliquam enim. Sed laoreet justo in turpis. Aenean metus lorem, mollis elementum, pulvinar non, pharetra at, metus. Phasellus interdum tortor non tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer facilisis, erat sed aliquam tristique, mi augue tristique mauris, in mollis turpis tellus vel sem. Vivamus ac nibh ac lectus venenatis blandit. Morbi eget sem ac turpis sodales tincidunt. In in nunc commodo ante venenatis ornare. Aliquam sit amet ligula.	
</p>
 
<%= render :partial => 'book_listings' %>
 
<p>Aliquam vestibulum ultricies velit. Vivamus pulvinar urna. Mauris tincidunt blandit massa. </p>

Rails’ before filter and basic HTTP authentication

Originally, I wanted to have us create a separate administrator section for administering the Books and Authors, but I think you’ll be able to do that on your own with the concepts we’ve worked on. Instead of a separate administrators section, let’s add some basic HTTP authentication to our Books and Authors controllers. By adding this authentication, we will be password protecting all calls to this controller. Here’s a snippet of code I use for basic access control:

def authenticate
  authenticate_or_request_with_http_basic do |user_name, password|
    user_name == "foo" && password == "bar"
  end
end

If we placed this method in our Books controller with the rest of our methods, anybody could call it by visiting books/authenticate; instead we’re going to place in a walled garden. Using Rails you have two options to protect your methods: private and protected. If you’re curious what they both do, here’s an an excerpt from RubyLearning:

  • Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family. However, usage of protected is limited.
  • Private methods cannot be called with an explicit receiver - the receiver is always self. This means that private methods can be called only in the context of the current object; you cannot invoke another object’s private methods.

We’ll be using the protected method, we can do this by wrapping it like so:

class BooksController < ApplicationController  
  # note: I've truncated the methods after index
 
  before_filter :authenticate
 
  # GET /books
  # GET /books.xml
  def index
    @books = Book.find(:all)
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @books }
    end
  end
 
protected
 
  def authenticate
    authenticate_or_request_with_http_basic do |user_name, password|
      user_name == "foo" && password == "bar"
    end
  end

You may have noticed before_filter :authenticate before all of our methods. This is part of the master plan! By adding a “before_filter” we’re asking Rails to call authenticate (which we’ve defined). As is, authenticate is called before any method; often you’ll have a situation where you only want to apply the before_filter to select methods. Check this out:

# apply authenticate to all methods
before_filter :authenticate
 
# apply authenticate to all methods EXCEPT index, and show
before_filter :authenticate, :except => [:index, :show]
 
# apply authenticate to ONLY the index and show
before_filter :authenticate, :only => [:index, :show]

So let’s review…in the previous section: we created a new landing page for our visitors, played with some if and for blocks, added basic HTTP authentication, and had a crash course on the before_filter. I’m sure you’ve been playing with the app in your browser, but if you haven’t…load up http://localhost:3000/books and you should be greeted with a familiar authentication dialog box!

Leave a comment, or send me an email with ideas for future articles! I wanted to cover SEO friendly URLs, validations, and a whole lot more this time but I wanted to get you guys an update sooner than later! Useful? Donate to my beer fund or digg this!

More Rails reading:

Understanding Rails partials

Rails Relationships

May 4th, 2008

No Gravatar

The Bookstore: A Rails 2.0 Tutorial…continued…who wants to play with relational databases? Here’s the section I’m sure you’ve all by dying for; sure, the scaffold was pretty bad ass, but now we’re going to play with something you ALL will enjoy. This segment is going to be a little bit longer than the others, we’ll be covering a lot of database information! Before you continue, I will assume that you have an understanding of the previous articles:

  1. Ruby on Rails Tutorial, now with more 2.0.2!
  2. Basic Rail’s routing and a journey into Views, and Controllers
  3. RESTful design and the HTTP request

If you’ve been tinkering with the old bookstore application, which I hope all of you have, there is a chance that it looks nothing how we left it. Do yourself and grab a fresh copy, which I will be using as the foundation for this segment.

With our last iteration of the bookstore we left with a simple, functional application that allowed us to build and maintain a database of books using the Rails’ scaffolding. Currently, our bookstore stores the title and description of a book; that’s not enough…I demand more! Don’t books have authors? Indeed they do. There are a few options, and it’s time for you to choose your own adventure…you want to add an author to your books, but there’s a fork in the road…which path do you take?

  1. Add a column to the existing Books database (create a database migration to add an “author” column to your existing database)
  2. Create a new Authors database (create a relational database)

Both are viable options, and it is likely you may not have any idea what the hell I am talking about. Let’s choose option two, by the time we work through this method, you will be able to use either method. Before we start coding, as with any project, it’s good to understand what we are trying to accomplish. Here’s the goal: create another database that contains authors, then relate this database with the existing book database.

Two Rails Databases

The first thing we’re going to do is create a separate database; which really, is just a table in the existing database. Using your friend the scaffold generator with one column for the authors “name”. Do you remember the syntax? Remember: models are singular…author, not authors!

ng:bookstore admin$ script/generate scaffold Author name:string
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/authors
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  app/views/authors/index.html.erb
      create  app/views/authors/show.html.erb
      create  app/views/authors/new.html.erb
      create  app/views/authors/edit.html.erb
      create  app/views/layouts/authors.html.erb
   identical  public/stylesheets/scaffold.css
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/author.rb
      create    test/unit/author_test.rb
      create    test/fixtures/authors.yml
      exists    db/migrate
      create    db/migrate/002_create_authors.rb
      create  app/controllers/authors_controller.rb
      create  test/functional/authors_controller_test.rb
      create  app/helpers/authors_helper.rb
       route  map.resources :authors

Short and sweet. If you jumped the gun and tried to access: “http://localhost:3000/authors” you likely got an error. Did you run “rake db:migrate”? If so, do it now.

ng:bookstore admin$ rake db:migrate
(in /Users/admin/Desktop/bookstore)
== 2 CreateAuthors: migrating =================================================
-- create_table(:authors)
   -> 0.0040s
== 2 CreateAuthors: migrated (0.0043s) ========================================

Visit your new Authors section at “http://localhost:3000/authors” and add two authors. So, now we have a database of books and a database of authors, how do we tie these together? Allow me to introduce the “foreign key”, while this sounds super complex, I’ll simplify it for our purposes. Every record in our database has a unique ID, you may notice in our URLs we access our books and authors by “http://localhost:3000/books/1″ and “http://localhost:3000/authors/1″; these ID numbers correspond with our databases. Remember, these are unique! Take a look at the data from our the authors and books tables, take note that I ran out of room on my blog post to show the created_at and updated_at columns for the books table, trust me that it’s in the database.

By looking at the tables, you should be able to see how the relationship between the ID in authors table and the respective entry. To beat a dead horse, ID 1 from authors contains the following:

Barack Obama’s Record from Authors

  • ID = 1
  • name = Barack Obama
  • created_at = 2008-05-01 19:59:17
  • updated_at = 2008-05-01 19:59:17

Just as ID 2 from authors contains this set of data:

Bill Clinton’s Record from Authors

  • ID = 2
  • name = Bill Clinton
  • created_at = 2008-05-01 19:59:32
  • updated_at = 2008-05-01 19:59:32

Freakonomics Record from Books

If we look at the books table (keep in mind I didn’t show created_at or updated_at above), and look at the book with an ID of 1, we see:

  • ID = 1
  • title = freakonomics
  • description = Which is more dang…(truncated)
  • created_at = 2008-05-01 19:59:32
  • updated_at = 2008-05-01 19:59:32

If this looks repetitive and trivial, GREAT, that means it makes sense to you. I am showing three, simple examples because you MUST understand how these records are accessed. Still look foreign? Look over the examples a few more times, once it sinks in, we’ll take this a step further.

If you remember the task at hand, you’ll recall that we want to associate an author with a book. We’ll do this with a foreign key. What is that, well the reason I disassembled the database table structure for this reason exactly! Simply put, a foreign key is the ID of your record, in a table isn’t its own. If you place ID 1 from authors in the books table, you now have a foreign key. Not that cool….yet.

We don’t arbitrarily place the foreign key in the books table; foreign keys, by default are placed in their own column. Since we want to place the authors ID in the books table, we need a new column for this foreign key. It is convention to name these columns based on the origin of the foreign key; in this particular case, we would create a column named “author_id” to the table books. Okay…a little confusing still? Hang with me. I didn’t ask you to create any columns, so don’t worry…but let’s pretend we created author_id in books and the author_id column for our Freakonomics entry was assigned a value of “2″…it would looks like this:

  • ID = 1
  • title = freakonomics
  • description = Which is more dang…(truncated)
  • author_id = 2
  • created_at = 2008-05-01 19:59:32
  • updated_at = 2008-05-01 19:59:32

What does this author_id with a value of “2″ actually mean? Well, this foreign key tells us that if we look at the author table, and find record with an ID of “2″, all that data applies to our Freakonomics book! Currently, the only data in our authors table is a name, but what if it contained a lot more? Perhaps the author’s homepage, biography, age, etc!

By using a foreign key, we can keep data separate. This may not seem like a big deal right now, but imagine this scenario:

We have a million books all authored by the same guy, and instead of setting of a separate database for the book’s authors, we decided to put the author’s name, age, hair color, and publisher in out book table. Say the author changed their hair color, name, and found a new publisher. This sucks…not only are we repeating all this data in our books table, but we need to go through EACH record and change all of those fields.

Now image this…instead of just adding columns to our books table, we create a separate table called authors and in this table we add the columns: name, hair color, and publisher. In our books table, instead of having name, age, hair color, and publisher we have one foreign key column called author_id. Now that we use a foreign key to reference the secondary, related table we can easily update the required data fields!

Why did I take the time and teach you this? Even though Rails will handle most associations, you will require this basic understanding in order to create the foreign keys so Rails can perform its magic.

Rails Migrations

You may recall we run “rake db:migrate” after we generate a scaffold; we do this to create, or “migrate” or database to the most current version. You haven’t had to manually create any “migrations”, because Rails has automagically done it for you. Before we play with migrations, you should know what they are.

Back in the day, when developers worked on databases they would manually add tables (books, authors) and columns (name, description, title). No qualms here. The problem was, somewhere down the line if you wanted to remove a column, modify a table, change a field type, etc. You would again, manually modify the database. The end result is a database with the structure that you want, but let’s say you were trying to figure out the differences between your first and third version of your database. Not really possible…what migrations allow you to do is version control your database.

Here’s a bigger picture: As your development skills progress, you will likely move to a version control system for your code (git, subversion). Version control will allow you to save your code at different stages, and instantly revert to any of those saved stages (think saved game on the xbox). With version control in place, one version of your code might be looking for the title of your book; but down the road, you decided that the book title should really be called the this_is_the_book_title. You make the change to the database, renaming your title field to this_is_the_book_title. Let’s say you’ve done some more code, and realize that you made a mistake, and want to revert back to your saved version where the book title was actually called title.

Using your version control system, you quickly revert back to your saved version where your code references your Book.title (verses Book.this_is_the_book_title). The problem here is your database has a field called this_is_the_book_title. Of course, you can manually change this back; and with one or two fields this would be fine. Imagine if you changed hundreds of fields over hundreds of thousands of tables? Not so simple anymore!

Migrations, as you will see, allow for you make sure your code and your databases are in sync. Examples are golden, so open up /db/migrate/001_create_books.rb!

class CreateBooks < ActiveRecord::Migration
  def self.up
    create_table :books do |t|
      t.string :title
      t.text :description
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :books
  end
end

Off the bat, take note of the file name, “001_create_book.rb”. We know that this is the migration with number “001″ and the name “create_book”, we can see that this migration is the first, and it will create something called “book”. If you look in the db/migrate directory you will see the sequential list of files 002, 003, etc. Looking at the migration file I listed above, you’ll see a self.up and self.down. The code defined in self.up is what Rails runs when we migrate forward, the code in self.down is what is run when we migrate backwards.

When we run rake db:migrate, we ask Rails to bring our database up to the most current version, from 001 onwards; though we haven’t used it yet, we can just as easily bring the database back to the first day we created the application, we’d do this with:

rake db:migrate VERSION=1

Now, open up “/db/migrate/002_create_authors.rb” you should see this:

class CreateAuthors < ActiveRecord::Migration
  def self.up
    create_table :authors do |t|
      t.string :name

      t.timestamps
    end
  end

  def self.down
    drop_table :authors
  end
end

If we were to run the command “rake db:migrate VERSION=1” the first thing Rails would do, is figure out which version of the database it is currently at (Rails does this through a table called schema“. Once it finds the version, in this case, we are at version 2, it will proceed to run all the self.down code until it reaches the version we specified, in this case version 1. In this particular instance, Rails will first drop the table “authors”.

This may all be confusing to you, it was to me. As long as you understand the basic foundation, through repeated use, it will all make more sense. Alright, enough of that…time to play with Rails!

Rails Associations

Our original goal was to add author data to a particular book. Now that we’ve created a separate author database, let’s tie them together. You’re going to see why I spent so much time explaining foreign keys and migrations.

Under the hood, Rails is more than willing to simplify relational databases. The important thing about associations is to have a high level understanding of what belongs to what. Let me preface this by saying, I am aware this example can go multiple ways, so don’t nitpick ;) Anyways, with our Book example, let’s look at our books and authors. A book belongs to an author, and an author has many books? It just happens Rails has a has_many and belongs_to method. These are basic Rails associations, and we declare them in the model. Open up your model/book.rb and model/author.rb and add the following lines of code:

# in model/book.rb
class Book < ActiveRecord::Base
  belongs_to :author
end

# in model/author.rb
class Author < ActiveRecord::Base
  has_many :books
end

What we’ve done is set up the associations between the two models, now Rails knows to look for an additional piece of data, the foreign key, and will handle all the database queries for you! One thing of particular importance: you will notice that author is singular with belongs_to declaration, while the has_many is plural. I point this out because many of you will overlook this and wonder why your application is not working.

Adding a Foreign Key in Rails

Now that our associations are in place, let’s create a migration for adding the foreign_key. This migration will be called, “add author ID to books”, and to do this we’ll run this command:

script/generate migration add_author_id_to_books

ng:bookstore admin$ script/generate migration add_author_id_to_books
      exists  db/migrate
      create  db/migrate/003_add_author_id_to_books.rb

Open the file that was just generated. Remember everything I told you about foreign keys? We’re going to create a column called “author_id” in our Book table. Rails will see the relationship we declared in our model, and by using our foreign key, will be able to determine Author record matches up with our Book record.

class AddAuthorIdToBooks < ActiveRecord::Migration
  def self.up
    add_column :books, :author_id, :int
  end

  def self.down
    remove_column :books, :author_id
  end
end

Save the file, and run “rake db:migrate”. Another important tidbit: the rule of thumb is the foreign key should be created in the database whose model contains the “belongs_to” declaration, in this case, the Book table.

ng:bookstore admin$ rake db:migrate
(in /Users/admin/Desktop/bookstore)
== 3 AddAuthorIdToBooks: migrating ============================================
-- add_column(:books, :author_id, :int)
   -> 0.0200s
== 3 AddAuthorIdToBooks: migrated (0.0202s) ===================================

ng:bookstore admin$

Modifying the View

Before you get took excited, there are a few things we need to do. Our backend is complete, now we need to modify some views so we can pair the page our users see to our backend database. Let’s start by adding few lines of code to the Books view. Open: app/views/books/new.html.erb:

<h1>New book</h1>
 
<%= error_messages_for :book %>
 
<% form_for(@book) do |f| %>
 
 
    <b>Title</b>
    <%= f.text_field :title %>
 
 
 
    <b>Description</b>
    <%= f.text_area :description %>
 
 
 
    <b>Author:</b>
    <%= collection_select(:book, :author_id,  Author.find(:all, :order => "name") , :id, :name) %>
 
 
 
    <%= f.submit "Create" %>
 
 
<% end %>
 
<%= link_to 'Back', books_path %>

We just modified the view when the “new” method is called in our Books controller. We called on method from the Rails FormHelper library, here’s quick breakdown of what the collection select does:

<%= collection_select(:book, :author_id,  Author.find(:all, :order => "name") , :id, :name) %>
 
# This returns the following HTML 
<select id="book_author_id" name="book[author_id]">
	<option value="THE AUTHOR ID">THE AUTHOR TITLE</option>
</select>

Accessing Associated Data Fields

Up to this point, I feel like I’ve been doing everything for you ;) I’m going to give you a small piece of information, then ask you to complete a task. When we set up the associations we specified the following:

  1. An author has_many :books
  2. A book belongs_to :author

Visualizing and setting up associations is really the most difficult part, Rails really takes on the rest of relational tasks. Recall, the author table contains the name field, since we told Rails that an author has many books, and a book belongs to an author; we can access the associated field using the following:

# returns the book title
<%= @book.title %>
 
# returns the authors name
<%= @author.name %>
 
# returns the associated author's name
<%= @book.author.name %>

Now spend a few minutes, and alter the rest of the views in app/views/books; leave the index view alone for now. If you get stuck, this is what they should look like:

# app/views/books/edit.html.erb
<h1>Editing book</h1>
 
<%= error_messages_for :book %>
 
<% form_for(@book) do |f| %>
 
 
    <b>Title</b>
    <%= f.text_field :title %>
 
 
 
    <b>Description</b>
    <%= f.text_area :description %>
 
 
 
    <b>Author:</b>
    <%= collection_select(:book, :author_id,  Author.find(:all, :order => "name") , :id, :name) %>
 
 
 
    <%= f.submit "Update" %>
 
 
<% end %>
 
<%= link_to 'Show', @book %> |
<%= link_to 'Back', books_path %>
# app/views/books/show.html.erb
 
 
  <b>Title:</b>
  <%=h @book.title %>
 
 
 
  <b>Description:</b>
  <%=h @book.description %>
 
 
 
  <b>Author:</b>
  <%=h @book.author.name %>
 
 
<%= link_to 'Edit', edit_book_path(@book) %> |
<%= link_to 'Back', books_path %>

Alright! Since we added a foreign key column, our old data will not have a value for its foreign key. We could simply delete the old record or modify it so it is up to date, but I want to introduce a handy little feature: resetting the database! With the “rake db:reset” command, Rails drops and recreates the current database from db/schema.rb for the current environment. Run it!

ng:bookstore admin$ rake db:reset
(in /Users/admin/Desktop/bookstore)
"db/development.sqlite3 already exists"
-- create_table("authors", {:force=>true})
   -> 0.0043s
-- create_table("books", {:force=>true})
   -> 0.0042s
-- initialize_schema_information()
   -> 0.0439s
-- columns("schema_info")
   -> 0.0007s
ng:bookstore admin$

All Systems Go

FIRE UP YOUR SERVER! Proceed to add a few authors via /authors…

…then add a few books via /books; notice the drop down menu? Success!

Finally, click on a book and get a more detailed picture…see the associated author? Ahhh! The fruits of your labor!

Under The Hood

If you’re curious what is happening when you’re creating or viewing records, take a look at your console. Behold, the power of Rails!

Accessing /books/new:

Processing BooksController#new (for 127.0.0.1 at 2008-05-04 23:26:02) [GET]
  Session ID: BAh7ByIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7ADoMY3NyZl9pZCIlZGEyMjY4MzgzZDQ1MGU4NjE1%0ANDEwMDlmY2VlYjJhZGI%3D--fb8c96ae999682b51105e633e7e6da0c3d008cf1
  Parameters: {"action"=>"new", "controller"=>"books"}
Rendering template within layouts/books
Rendering books/new
  Author Load (0.000754)   SELECT * FROM authors ORDER BY name
Completed in 0.01405 (71 reqs/sec) | Rendering: 0.00755 (53%) | DB: 0.00075 (5%) | 200 OK [http://localhost/books/new]

On the creation of a book:

Processing BooksController#create (for 127.0.0.1 at 2008-05-04 23:26:48) [POST]
  Session ID: BAh7ByIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7ADoMY3NyZl9pZCIlZGEyMjY4MzgzZDQ1MGU4NjE1%0ANDEwMDlmY2VlYjJhZGI%3D--fb8c96ae999682b51105e633e7e6da0c3d008cf1
  Parameters: {"commit"=>"Create", "authenticity_token"=>"82b54355dede97ccb50e1bfa90ab3c5ffcf081ae", "action"=>"create", "controller"=>"books", "book"=>{"title"=>"Don't Make Me Think", "description"=>"Usability design is one of the most important--yet often least attractive--tasks for a Web developer. In Don't Make Me Think, author Steve Krug lightens up the subject with good humor and excellent, to-the-point examples.", "author_id"=>"2"}}
  Book Create (0.000378)   INSERT INTO books ("updated_at", "title", "description", "author_id", "created_at") VALUES('2008-05-04 23:26:48', 'Don''t Make Me Think', 'Usability design is one of the most important--yet often least attractive--tasks for a Web developer. In Don''t Make Me Think, author Steve Krug lightens up the subject with good humor and excellent, to-the-point examples.', 2, '2008-05-04 23:26:48')
Redirected to http://localhost:3000/books/3
Completed in 0.01351 (74 reqs/sec) | DB: 0.00038 (2%) | 302 Found [http://localhost/books]

Viewing a single book’s details:

Processing BooksController#show (for 127.0.0.1 at 2008-05-04 23:27:37) [GET]
  Session ID: BAh7ByIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7ADoMY3NyZl9pZCIlZGEyMjY4MzgzZDQ1MGU4NjE1%0ANDEwMDlmY2VlYjJhZGI%3D--fb8c96ae999682b51105e633e7e6da0c3d008cf1
  Parameters: {"action"=>"show", "id"=>"2", "controller"=>"books"}
  Book Load (0.000247)   SELECT * FROM books WHERE (books."id" = 2)
Rendering template within layouts/books
Rendering books/show
  Author Load (0.000246)   SELECT * FROM authors WHERE (authors."id" = 3)
Completed in 0.01162 (86 reqs/sec) | Rendering: 0.00550 (47%) | DB: 0.00049 (4%) | 200 OK [http://localhost/books/2]

Until next time

Four tutorials in, you guys should know the drill by now :) Next time I’ll be covering validations, routing, and maybe even some Rails SEO. Useful? Donate to my beer fund!

The next post has been posted! It covers partials, the before filter, and basic HTTP authentication.

For further reading, take a look at the Rails API, it is an invaluable reference:
Module: ActiveRecord::Associations::ClassMethods

No Gravatar

Basic Rail’s routing and a journey into the controller

Thanks for tuning in! Last time we spoke, we set up a basic Rails application called the bookstore. Using the new 2.0 scaffolding, we were able to quickly, and effortlessly move from a high-level thought process to a living, breathing model. Now that you’ve toyed around with the application source, I’m going to explain our previous code and I’ll even throw a little wrench into your gears…basic Rail’s routing.

Open up your bookstore application, if you’ve lost it or found this post randomly…be sure to visit the original post “Ruby on Rails Tutorial, now with more 2.0.2!“. Then, fire up your server, and navigate to your application’s root, likely, http:/localhost:3000. If your code is untouched from the last time we met, you should encounter a “routing error” like so:

02_routing.jpg

That’s kind of annoying, and just a little bit ugy; it’s time we fix this! You may remember play with .htaccess and doing a mod_rewrite here and there, when you use the Rails framework you no longer place the burden of URL rewriting on the webserver, but instead, on Rails. Routes can get really complicated, really fast; so I am going to show you the very basics. Even after I show you how to play around with Rails’ routing, I will leave the application’s default routes alone. I will do this because in teaching, it is much easier explaining to you that:

http://localhost:3000/books/1

is calling on the “books” controller, with the “show” method, and an “id” of 1 verues trying to explain to you that,

http://localhost:3000/library/freakonomics

can do the exact same thing! All in time though, all in time. Anyways, open up theconfig/routes.rb file and you should see something like this:

Routes are listed in order of priority, the higher on the list they are, the higher priority they are. You’ll notice Rails’ default routes on the bottom:

 map.connect ':controller/:action/:id'
 map.connect ':controller/:action/:id.:format'

Briefly, when a visitor types in the URL:

http://localhost:3000/books/1

Rails takes the HTTP request, looks through the routes listing, and systematically tries to find a match. With the URL above, the HTTP request will be matched to the first request and processed as follows:

http://localhost:3000/:controller/:action/:id

Hopefully this makes sense, I’ll touch more on this down the road. If you’re not entirely lost, recall that the reason we’re poking around the routes file is because we are getting a routing error when we attempt to access “http://localhost:3000″. At the end of the routes.rb file, add the following:

map.root :controller => "books", :action => "index"

“map.root” is the shorthand to name the root path “”; now whenever we navigate to the site’s root path we should see a call to the “books” controller and “index” action. Take note, you do not have to specificy the “index” action, as it is called by default, but I included it for sake of illustration. Navigate to your site’s root, and watch the magic.

Pretty neat, huh? Add a few more books, and then take some time to examine the URL structure of the database items you’ve entered. When you’ve done that, open up the “apps/models/book.rb” file. The file you just opened is our “Book model”, it acts as the gateway between any database queries. Right now it is empty, but it is worth pointing out. Now open the, “apps/controllers/books_controller.rb” file, you will see first entry is the index method.

  # GET /books
  # GET /books.xml
  def index
    @books = Book.find(:all)
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @books }
    end
  end

Since this code was generated by Rails, we have a few lines of commenting. What this means is, any GET request to /books will actaully call the “index” method (we’ll talk about the different request types later). When this method is called in, there are two things that happen.

  1. We are talking to our “Book model” and asking it to find all records, then we are saving those results as a Ruby hash in “@books”
  2. Next, depending on the requested format, responding with either an HTML template or XML template (more on this later)

For those readers who have some database expereince, check this out, if this doesn’t make sense, don’t worry.

    # ask the Book model to find :all records
    @books = Book.find(:all)
 
    # the resulting MySQL query
    SELECT * FROM books

Now, scroll down to the next method…it should be named “show”.

  # GET /books/1
  # GET /books/1.xml
  def show
    @book = Book.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @book }
    end
  end

This time, instead asking the Book model to find :all records, we’re asking it to find by the “params[:id]“. What does “params[:id]” mean? The “id” parameter from the URL is accessed by “params[:id]” and in this case, the value is “1″. If we were to type the URL, “http://localhost:3000/books/5″, params[:id] would be set to “5″. Let’s look at this line of code:

@book = Book.find(params[:id])

What we’re doing is assigning the results of our database query, in this case, by asking the Book model to find the record with ID matching params[:id]. You’ll notice that all models are titleized and singular: in this case Book, but it could easily be Author or Category.

Now that you’ve seen how we are retrieving our data from the database, let me show you how to format that data for your visitors. Open your “app/views/books/index.html.erb” file. And you should see this:

If you’ve developed websites in the past, the show view should be a little more familar to you. You will notice a few things that look out of place, with PHP we enclosed our code with the <? code ?> tags, with Ruby, anything enclosed between <% code %> , <%= code %>, is called embedded ruby. Here are the differences:

  • <% code %> is evaluted, but not printed
  • <%= code %> is evaulated, and printed

Check this:

1
2
3
4
5
# this will display NOTHING in your HTML
<% "You will not see this" %>
 
# this will print the string
<%= "But you will see this!" %>

You can test the code by pasting the embedded ruby code, just below the “<h1>Listing Books</h1>” tag, and accessing your site’s root, like so:

See how that works? Now, the final piece of the puzzle!

<% for book in @books %>
<tr>
<td><%=h book.title %></td>
<td><%=h book.description %></td>
<td><%= link_to 'Show', book %></td>
<td><%= link_to 'Edit', edit_book_path(book) %></td>
<td><%= link_to 'Destroy', book, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
 
<% end %>

What we’re doing here, is looping through each item in the @books hash, and for each item we are printing out the book title, “book.title” and book description, “book.description”. The “h” you see preceeding the book title and description is the Rails “HTML escape”. If you type <%= book.title %> instead of <%=h book.title %>, the book title will still dispaly, BUT it is good habit to ALWAYS HTML escape database fields with data that may contain something harmful…read: malicious users entering something other than a book description.

Take a look at the code again, if I were to read it out loud to you it would read as follows:

“For each book in the @books variable, do the following: Print the title; description; and the show, edit, and destroy commands”. To expand on the whole “for” block, here’s another example…if you were feeling rather rebelious, you could do this too:

1
2
3
4
5
6
7
8
9
10
<% for item in @books %>
<tr>
<td><%=h item.title %></td>
<td><%=h item.description %></td>
<td><%= link_to 'Show', item %></td>
<td><%= link_to 'Edit', edit_book_path(item) %></td>
<td><%= link_to 'Destroy', item, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
 
<% end %>

Hopefully that makes some more sense :) As always, if this was this helpful, if you have questions, or if you just want to bullshit…leave me a comment…help me, help you! Feel like donating to my beer fund?

The next part of our journey through Rails covers RESTful design, an integral part of a properly designed and correctly executed Rails web application.

No Gravatar

My first Rails application was the ONLamp: Rolling with Ruby on Rails tutorial, since then Rails has come a long way but I find the beginner resources lacking still. The one thing I’ve learned along the way has been that sample code is invaluable, so today, in celebration of my company’s launch of our completely free online dating (and completely Rails) site Meeta.com, I offer present to you my version of the Rolling with Ruby on Rails tutorial on OSX.

As always, I am not a Rails genius, so my code may not be perfect; if I’ve done something wrong or there is a better, more Railsesque method please let me know. If you find this useful, leave me a comment, I like to know who is dropping in.

Enough of that. Let’s go!

Let’s start by creating our Rails application, if you’re looking for a tutorial on installing Rails check out the Rails wiki on installation.

From your command prompt, type the command “rails bookstore”. By default, Rails will use SQLite as the default database, if you want to use MySQL you will need to run “rails -d mysql bookstore”. This will create a “bookstore” subdirectory contanting a complete tree of folders and files for your Rails application. This is what you should see:

ng:Sites admin$ rails bookstore
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  config/initializers
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  script/process
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/mocks/development
      create  test/mocks/test
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  Rakefile
      create  README
      create  app/controllers/application.rb
      create  app/helpers/application_helper.rb
      create  test/test_helper.rb
      create  config/database.yml
      create  config/routes.rb
      create  public/.htaccess
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/boot.rb
      create  config/environment.rb
      create  config/environments/production.rb
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  script/about
      create  script/console
      create  script/destroy
      create  script/generate
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  script/performance/request
      create  script/process/reaper
      create  script/process/spawner
      create  script/process/inspector
      create  script/runner
      create  script/server
      create  script/plugin
      create  public/dispatch.rb
      create  public/dispatch.cgi
      create  public/dispatch.fcgi
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/index.html
      create  public/favicon.ico
      create  public/robots.txt
      create  public/images/rails.png
      create  public/javascripts/prototype.js
      create  public/javascripts/effects.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  doc/README_FOR_APP
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
ng:Sites admin$

That’s a whole lot of files! I’m not going to get into details about the directory structure because I’m going to write another blog post about this next time. Very briefly, the bulk of the structure is as follows:

  • app contains your application’s models, views, and controllers…the backbone of your app
  • app/controllers contains all your controllers, these guys handle web requests
  • app/helpers contains helper classes, these will help you clean up code allowing you to keep MVC (model, view, controller) code simple and neat
  • app/models contains all your models, models are the gatekeepers to your database
  • app/views contains all your views, these are display templates we will use to plug in data, which in turn is converted to the final HTML pages your users will see
  • public holds all your publicily accessible files, think public_html

I bet you’re excited already…I was! Let’s fire up the server and see what’s going on. Run, “script/server”

ng:bookstore admin$ script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Ruby version is up-to-date; cgi_multipart_eof_fix was not loaded
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel available at 0.0.0.0:3000
** Use CTRL-C to stop.

Now, depending how your hosts are set up, visit your application in your browser:

  • http://0.0.0.0:3000
  • http://localhost:3000
  • http:/127.0.0.1:3000

You should see the default Rails placeholder that looks like this:

01_welcome.jpg

You’ll notice this corresponds with the “public/index.html” file. Well, that’s not too exciting…let’s move on to creating your application. Delete the “index.html” file in your “public” directory, then shut down the web-server, and let’s really play ball.

Let’s think about a bookstore at a high level, and how they really work. A bookstore contains books with titles andthese books each belong to categories or genres. This is the foundation of our application. I’m going to show you how to create a working model, then I’ll explain it. By running the following command, you will create a scaffold for the books in your bookstore with a title and description. Run: “script/generate scaffold Book title:string description:text”

ng:bookstore admin$ script/generate scaffold Book title:string description:text
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/books
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  app/views/books/index.html.erb
      create  app/views/books/show.html.erb
      create  app/views/books/new.html.erb
      create  app/views/books/edit.html.erb
      create  app/views/layouts/books.html.erb
      create  public/stylesheets/scaffold.css
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/book.rb
      create    test/unit/book_test.rb
      create    test/fixtures/books.yml
      create    db/migrate
      create    db/migrate/001_create_books.rb
      create  app/controllers/books_controller.rb
      create  test/functional/books_controller_test.rb
      create  app/helpers/books_helper.rb
       route  map.resources :books
ng:bookstore admin$

Okay, cool, but what is a scaffold? Think of buidings, when builders are working on a new building they put up scaffolds. This allows them to quickly build their building. As their project nears completion, they tear down the scaffolds. When you ran the “script/generate scaffold Book title:string description:text” command you asked Rails to create a scaffold for you. It provides you with a model, the necessary views, and a controller. These scaffolds allow you to quicky and rapidly develop an application, but should not be relied on in a production environment.

Let’s break down the command: “script/generate scaffold Book title:string description:text”

What you’ve done here is created a scaffold for your books, notice though the command says “Book”. ALL models in Rails are singular while views and controllers are plural. This was one of the points that confused me when I started using Rails, so pay attention! You’ll notice “title:string” and “description:text”, these are the corresponding field names and column types, even cooler is that Rails will automagically determine that your “string” is a VARCHAR or your text is TEXT. I used the examples title and description, because they are simple. These could have easily been “quantity:integer” or “isbn:string”.

Now that we’ve created a scaffold with a working model, views, and controller we need to physically create the database. Let’s do this now by running “rake db:migrate”

ng:bookstore admin$ rake db:migrate
(in /Users/admin/Sites/bookstore)
== 1 CreateBooks: migrating ===================================================
-- create_table(:books)
   -> 0.0035s
== 1 CreateBooks: migrated (0.0038s) ==========================================

ng:bookstore admin$

By running this command, you’ve asked rails to “migrate” your database using the “migrations” located in “db/migrate”. Pop in and take a look, but don’t freak out. I’ll explain those at another time. Boot up your server and navigate to your homepage, if you’ve forgotten how to do this just run, “script/server” and visit “http://localhost:3000″. You will likely encounter a “Routing error”, that’s okay though! Later, we will discuss routes, which allow you to control the URL structure.

02_routing.jpg

Remember we created a scaffold for our model “Book”, well, the corresponding controller is located in “app/controllers/books_controller.rb”. Earlier I mentioned that the controller handles all the webrequests. By default, you can access a controller by it’s name, in this case, “books”. Change your browser’s URL to: “http://localhost:3000/books” and you should see this:

03_books-index.jpg

Well, what’s this…no books? Go ahead and add one! Before you get too excited, I want you to pay attention to the URL, notice that when you click “create” you are taken to “http://localhost:3000/books/new” this URL will correspond very closely with your application’s internal workings. .After you add a book, you should see a quick summary of the book you’ve inputted into the database; here’s one that I’ve added:

04_new-book.jpg05_book_created.jpg

Click the back button, and return to the index.

06_books-index.jpg

Wasn’t that simple? You have a basic bookstore where you can create, edit, update, and destroy books from a working database! Take some time to reread this tutorial and play with the application’s internal workings, get familiar with everything here because I will use it as a foundation from this point forward.

If you need the source code, you can download it here.

Was this helpful, do you have questions, or do you just want to bullshit? Leave