Keeping Track: A Simple Guestbook

Most applications will need to do more with data—typically, at least, they’ll store the data and present it back as appropriate. It’s time to extend this simple application so that it keeps track of who has stopped by, as well as greeting them. This requires using models. (The complete application is available in ch04/guestbook002.)

Warning

As the next chapter on scaffolding will make clear, in most application development you will likely want to create your models by letting Rails create a scaffold, since Rails won’t let you create a scaffold after a model with the same name already exists. Nonetheless, understanding the more manual approach will make it much easier to work on your applications in the long run.

Connecting to a Database Through a Model

Keeping track of visitors will mean setting up and using a database. In Rails 2.0.2 and later, this should be easy when you’re in development mode, as Rails now defaults to SQLite, which doesn’t require explicit configuration. (Earlier versions of Rails required setting up MySQL, which does require configuration, which you'll still want to do for deployment as discussed in Chapter 18.) To test whether SQLite is installed on your system, try issuing the command sqlite3 -help from the command line. If it’s there, you’ll get a help message. If not, you’ll get an error, and you’ll need to install SQLite.

Once the database engine is functioning, it’s time to create a model. Once again, it’s easiest to use script/generate to lay a foundation, and then add details to that foundation. This time, we’ll create a simple model instead of a controller and call the model entry:

ruby script/generate model entry
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/entry.rb
      create  test/unit/entry_test.rb
      create  test/fixtures/entries.yml
      create  db/migrate
      create  db/migrate/20080718193908_create_entries.rb

For our immediate purposes, two of these files are critical. The first is app/models/entry.rb, which is where all of the Ruby logic for handling a person will go. The second, which defines the database structures and thus needs to be modified first, is in the db/migrate/ directory. It will have a name like [timestamp]_create_entries.rb, where [timestamp] is the date and time when it was created. It initially contains what’s shown in Example 4-4.

Example 4-4. The default migration for the entry model

1  class CreateEntries < ActiveRecord::Migration
2    def self.up
3      create_table :entries do |t|
4
5        t.timestamps
6      end
7    end
8
9    def self.down
10     drop_table :entries
11   end
12 end

There’s a lot to examine here before we start making changes. First, note on line 1 that the class is called CreateEntries. The model may be for a person, but the migration will create a table for more than one person. Rails names tables (and migrations) for the plural, and can handle most common English irregular pluralizations. (In cases where the singular and plural would be the same, you end up with an s added for the plural, so deer become deers and sheep become sheeps.) Many people find this natural, but other people hate it. For now, just go with it—fighting Rails won’t make life any easier.

Also on line 1, you can see that this class inherits most of its functionality from the Migration class of ActiveRecord. ActiveRecord is the Rails library that handles all the database interactions. (You can even use it separately from Rails, if you want to.)

There are two methods defined here, on lines 2 (self.up) and 9 (self.down). The self.up method will be called when you order the migration run, and will create tables and columns your application needs. The self.down method will be called if you roll back the migration—effectively it provides you with “undo” functionality.

Note

This example takes the slow route through creating a model so you can see what happens. In the future, if you'd prefer to move more quickly, you can also add the names and types of data on the command line as you will do when generating scaffolding in the next chapter.

Both of these operate on a table called Entries—self.up creates it on line 3, while self.down destroys (drops) it on line 10. Note that the migration is not concerned with what kind of database it works on. That’s all handled by the configuration information. You’ll also see that migrations, despite working pretty close to the database, don’t need to use SQL—though if you really want to use SQL, it’s available.

Storing the names people enter into this very simple application requires adding a single column:

create_table :entries do |t|
  t.string :name
  t.timestamps
end

The new line refers to the table (t) and creates a column of type string, which will be accessible as :name.

Note

In older versions of Rails, that new line would have been written:

t.column :name, string

The old version still works, and you’ll definitely see migrations written this way in older applications and documents. The new form is a lot easier to read at a glance, though.

The t.timestamps line is there for housekeeping, tracking “created at” and “updated at” information. Rails also will automatically create a primary key, :id, for the table. Once you’ve entered the new line (at line 4 of Example 4-4), you can run the migration with the Rake tool:

$ rake db:migrate
(in /Users/simonstl/rails/guestbook)
== 20080814190623 CreateEntries: migrating =====================
-- create_table(:entries)
   -> 0.0041s
== 20080814190623 CreateEntries: migrated (0.0044s) ============

Rake is Ruby’s own version of the classic command-line Unix make tool, and Rails uses it for a wide variety of tasks. (For a full list, try rake --tasks.)

In this case, the db:migrate task runs all of the previously unapplied self.up migrations in your application’s db/migrate/ folder. (db:rollback runs the self.down migrations corresponding to the previous run, giving you an undo option.)

Now that the application has a table with a column for holding names, it’s time to turn to the app/models/entry.rb file. Its initial contents are very simple:

class Entry < ActiveRecord::Base
end

The Entry class inherits from the ActiveRecord library’s Base class, but has no functionality of its own. For right now, it can stay that way—Rails provides enough capability that nothing more is needed.

Warning

Remember that the names in your models also need to stay away from the list of reserved words presented at http://wiki.rubyonrails.org/rails/pages/ReservedWords.

Connecting the Controller to the Model

As you may have guessed, the controller is going to be the key component transferring data that comes in from the form to the model, and then it will be the key component transferring that data back out to the view for presentation to the user.

Storing data using the model

To get started, the controller will just blindly save new names to the model, using the code highlighted in Example 4-5.

Example 4-5. Using ActiveRecord to save a name

class EntriesController < ApplicationController

  def sign_in
       @name = params[:visitor_name]
       @entry = Entry.create({:name => @name})

  end

end

The highlighted line combines three separate operations into a single line of code, which might look like:

       @myEntry = Entry.new
       @myEntry.name = @name
       @myEntry.save

The first step creates a new variable, @myEntry, and declares it to be a new Entry object. The next line sets the name property of @myEntry—effectively setting the future value of the column named “name” in the Entries table—to the @name value that came in through the form. The third line saves the @myEntry object to the table.

The Entry.create approach assumes you’re making a new object, takes the values to be stored as named parameters, and then saves the object to the database.

Note

Both the create and the save method return a boolean value indicating whether or not saving the value to the database was successful. For most applications, you’ll want to test this, and return an error if there was a failure.

These are the basic methods you’ll need to put information into your databases with ActiveRecord. (There are many shortcuts and more elegant syntax, as the next chapter will show.) This approach is also a bit too simple. If you visit http://localhost:3000/entries/sign_in/, you’ll see the same empty form that was shown in Figure 4-1. However, because @entry.create was called, an empty name will have been written to the table. The log data that appears in the server’s terminal window shows:

Entry Create (0.000522)   INSERT INTO "entries" ("name", "updated_at",
"created_at") VALUES(NULL, '2008-08-14 19:13:58', '2008-08-14 19:13:58')

The NULL is the problem here because it really doesn’t make sense to add a blank name every time someone loads the form without sending a value. On the bright side, we have evidence that Rails is putting information into the Entries table, and if we enter a name, say “Zaphod,” we can see the name being entered into the table:

Entry Create (0.000409)   INSERT INTO "entries" ("name", "updated_at",
"created_at") VALUES('Zaphod', '2008-08-14 19:15:06', '2008-08-14 19:15:06')

It’s easy to fix the controller so that NULLs aren’t stored—though as we’ll see in Chapter 7, this kind of validation code really belongs in the model. Two lines, highlighted in Example 4-6, will keep Rails from entering a lot of blank names.

Example 4-6. Keeping blanks from turning into permanent objects

class EntriesController < ApplicationController

  def sign_in
       @name = params[:visitor_name]
     if !@name.blank?
       @entry = Entry.create({:name => @name})
     end
  end

end

Now Rails will check the @name variable to make sure that it has a value before putting it into the database. !@name.blank? will test for both nil values and blank entries. (blank is a Rails method extending Ruby's String objects. The ! at the beginning means “not,” which ensure that only values that are not blank will be accepted.)

If you want to get rid of the NULLs you put into the database, you can run rake db:rollback and rake db:migrate (or rake db:migrate:redo to combine them) to drop and rebuild the table with a clean copy.

== 1 CreateEntries: reverting =======================================
-- drop_table(:entries)
   -> 0.0029s
== 1 CreateEntries: reverted (0.0031s) ==============================

== 1 CreateEntries: migrating =======================================
-- create_table(:entries)
   -> 0.0039s
== 1 CreateEntries: migrated (0.0041s) ==============================

If you want to enter a few names to put some data into the new table, go ahead. The next example will show how to get them out.

Retrieving data from the model and showing it

Storing data is a good thing, but only if you can get it out again. Fortunately, it’s not difficult for the controller to tell the model that it wants all the data, or for the view to render it. For a guestbook, it’s especially simple, as we just want all of the data every time.

Getting the data out of the model requires one line of additional code in the controller, highlighted in Example 4-7.

Example 4-7. A controller that also retrieves data from a model

class EntriesController < ApplicationController

  def sign_in
       @name = params[:visitor_name]
     if !@name.blank? then
       @entry = Entry.create({:name => @name})
     end

     @entries = Entry.find(:all)

  end

end

The Entry object includes a find method—like new and save, inherited from its parent ActiveRecord::Base class without any additional programming. If you run this and look in the logs, you’ll see that Rails is actually making a SQL call to populate the @entry array:

Entry Load (0.000633)   SELECT * FROM "entries"

Next, the view, still in views/entries/sign_in.html.erb, can show the contents of that array, letting visitors to the site see who’s come by before, using the added lines shown in Example 4-8.

Example 4-8. Displaying existing users with a loop

<html>
<head><title>Hello <%=h @name %></title></head>

<body>
<h1>Hello <%=h @name %></h1>

<% form_tag :action => 'sign_in' do %>
   <p>Enter your name:
   <%= text_field_tag 'visitor_name', @name %></p>

   <%= submit_tag 'Sign in' %>

<% end %>
<p>Previous visitors:</p>
<ul>
<% @entries.each do |entry| %>
  <li><%=h entry.name %></li>
<% end %>

</ul>
</body>
</html>

The loop here iterates over the @entries array, running as many times as there are entries in @entries. @entries, of course, holds the list of names previously entered, pulled from the database by the model that was called by the controller in Example 4-7. For each entry, the view adds a list item containing the name value, referenced here as entry.name. The result, depending on exactly what names you entered, will look something like Figure 4-5.

The guestbook application, now displaying the names of past visitors

Figure 4-5. The guestbook application, now displaying the names of past visitors

It’s a lot of steps, yes, but fortunately you’ll be able to skip a lot of those steps as you move deeper into Rails. Building this guestbook didn’t look very much like the “complex-application-in-five–minutes” demonstrations that Rails’ promoters like to show off, but now you should understand what’s going on underneath the magic. After the apprenticeship, the next chapter will get into some journeyman fun.

Get Learning Rails: Live Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.