Rails Relationships

May 4, 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

  • Gil Wright
    I am hoping that I have not done something silly....

    Following the instructions to the letter, everything worked until I started fooling around with the html.erb files.

    When I look in the database, I can see the author_id field in the books table (it is empty for all records). When I restart the server, the changes made in the erb files do not appear.

    My original erb files looked like (just one part):


    <%= f.label :description %>

    <%= f.text_area :description %>



    I added a new section that looked like:


    <%= f.label :author %>

    <%= collection_select(:book, :author_id, Author.find(:all, :order => "name") , :id, :name) %>



    Nothing happened = I still see the old form (with two entry boxes, no author pick list). Flushed cache, restarted browser, restarted server. Nothing changes.

    I changed the entire file to match the one in the tutorial. Still nothing changes.

    Going out on a limb I tried changing the page title to:

    New Improved Book Entry Page



    Still nothing. Any hints apart from suggesting a brick wall to bang my head against?

    Thanks.
  • Yas
    A great tutorial! I was trying to understand what a foreign_key does. Thanks for the explanation.
  • Wednesday I was searching for blogs related to Web Promotion and specifically seo friendly cms and I found your related blog.
  • Hi there!
    I've been through all of Your tutorials so far and i must say - You're the best. I'm a classic LAMP developer who once decided to try switching to rails. I began with learning the Ruby language itself and it's syntax, but it never brought me to any clue on Rails - which isn't perfectly documented... Your tutorials are making the beginners life easier! Thanks!

    Btw, i tried to introduce the very basics of (M)VC structure in PHP to some wider range of HTML developers by writing my own tutorial about it. Not sure if it's a good place to share, but hope some1 would find it interesting as well http://blog.thepixers.com/2008/07/tutorial-micro-cms-for-htmlcss-developers/

    Cheers, keep up the good work! :)
  • jefe
    Jonathan,

    Your tutorials are excellent. I'd like to make a request regarding this one, Rails Relationships. I will be working with an existing database where all tables, constraints, and relationships are defined in the DBMS as opposed to using Rails Migrations. I (and I'm sure others) would appreciate any additional info you may have regarding best practices in building Ruby/Rails 2.x applications using legacy databases. The only thing I've found so far is this wiki,

    http://wiki.rubyonrails.com/rails/pages/HowToUseLegacySchemas

    which isn't all that helpful in practice.

    Thanks for your consideration

    jefe
  • aaron
    Uh, where is the article? I was looking forward to it but only the comments show up?
  • Hi Jonathan,

    Great series of tutorials!

    I found this slight error in your section called "Rails Associations."

    You have:
    Open up your model/book.rb and model/author.rb and add the following lines of code:

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

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

    But I think the book class' comment should read:
    # in model/book.rb

    Hope this makes sense, if it doesn't drop me an email, and I'll try to explain it better ;).

    David
  • Amrita
    Thanks, Jonathan, for posting such a wonderful tutorial. I implemented the application and got everything to work. Now I am trying to get the authors' page to provide a link to all books authored by each author. How do I go about doing this? In particular, I would like to understand better how the view interacts with the controller and how controllers of different models can interact.

    I apologize if I sound technically incorrect. I have been doing RoR for all of 3 days now and had a long break from OOP.

    Thanks a lot!
  • Andy
    Ignore my last comments. After going over it like 10 times, and resetting the local server, and raking it, resetting it, and nothing working, I went to lunch, and when I came back it started to work for no reason....... O well great Guide!!! Thanks a million.
  • Andy
    *I put (%=h book.author.name %> into the *
  • Andy
    11: (tr)
    12: (td)(%=h book.title %>(/td)
    13: (td)(%=h book.description %>(/td)
    14: (td)(%=h book.author.name %>(/td)
    15: (td)(%= link_to 'Show', book %>(/td)
    16: (td)(%= link_to 'Edit', edit_book_path(book) %)(/td)
    17: (td) 'Are you sure?', :method => :delete %)(/td)

    *
  • Andy
    11: !!
    12: !!
    13: !!
    14: !!
    15: !!
    16: !!
    17: ! 'Are you sure?', :method => :delete %>!

    *I put into the*
  • Andy
    I keep getting
    ----------
    NoMethodError in Books#index

    Showing books/index.html.erb where line #14 raised:

    You have a nil object when you didn't expect it!
    The error occurred while evaluating nil.name

    Extracted source (around line #14):

    11:
    12:
    13:
    14:
    15:
    16:
    17: 'Are you sure?', :method => :delete %>
    ------------------

    It seems to be that it doesn't like that I put into the view for the index and the show. It is exactly what you have in the example so I am not sure what I am exactly doing wrong.
  • Ben Cruz
    Dave:

    "How do you tell sqlite which database to use?"

    From the SQLite manual: To start the sqlite3 program, just type "sqlite3" followed by the name the file that holds the SQLite database.

    So I guess typing 'sqlite3 bookstore' will do the trick. Not sure though since i use mysql, but give it a shot :)
  • Ramu
    Great turorails.
    I have one doubt.
    let thing I projects, resources, tables and join table allocations.

    when I want to allocate resources to project... from allocations new interface, the interface hold selection list of projects and selection list of resources.

    here I will select one project and one resource.
    It's working...but how to make resources selection multiple.....please help meee
  • Dave E
    How do you tell sqlite which database to use? At the sqlite> prompt in my Terminal window I type "SELECT * FROM authors;" and get "SQL Error: no such table: authors"

    In other db products I have to issue a "USE DATABASE ;"

    Thank you for writing these articles - they are great!
  • For that matter, I'd like to know how to link a third table. And a fourth, and a fifth.

    I get ... too tactical sometimes, focusing in on a narrow solution, ignoring the big picture.
  • Ben Cruz
    Hey Brian, thanks for the answer. I see how it could work by just adding all the authors in one go if that's what your suggesting.

    Wouldn't that defeat the purpose of the second normalization rule (or was that the first...) though? Say you add a record for Mark Twain, and then later you add a record for Mark Twain Robert Heinlein. If you want to keep track of other information about the authors, like a short biography etc, it wouldn't make sense to have two biography fields for the record with two authors.

    If however that is totally not what you meant then pay no attention to anything i just said :P

    I wonder if by creating a third table called author_book, there would be an easy way for rails to understand it. Would it then be possible to add a belongs_to :author and belongs_to: book in the same model, or would it need no model at all?

    Ahh so many questions, thank you all for your input. I have a whole database setup and can't wait to get my rails app going but this is really holding me back.
  • Is there a clever way to do this in Rails?

    Preface: I have absolutely no idea what I'm talking about.

    Use the existing authors database, but shove all of the authors into the 'author' records.

    There isn't a first-name last-name field - one might assume that the logic behind 'bookstore' is going to be smart enough to be able to sort 'Mark Twain' and 'Twain, Mark'.

    If it can do this - why not let it sort out 'Mark Twain Robert Heinlein T.R. Fehrenbach' into the three authors responsible for 'War and Peace as I Knew It' ?

    At the very least - I've noticed that many ruby on rails apps on the web don't require a FirstName LastName data entry field so .. they're doing something smart behind the scenes.
  • Ben Cruz
    Hi Jonathan, I second the words of the other posters, outstanding tutorials!

    I'm a beginner in Rails and have a question related to the database. If you were to say that a book could belong to many authors (you know... if it was written by two people) that would make it a many-to-many relationship. Normally you'd get rid of this by turning it into two one-to-many relationships with a third table in the middle. Is there a clever way to do this in Rails?

    Thanks in advance and looking forward to the next tutorial.
    Have a nice day everyone :)
  • After creating the AddAuthorIdToBooks class I to got an error with rake db:migrate.

    I did get past this problem by removing all existing gems, uninstalling rails (from macports) and gem (from source).

    Rails has this much in common with other dev toys - sometimes (it seems) you just gotta clean house and start over.
  • great tutorial Jonathan! thanks
  • noel
    I tried to challenge myself by not using books and authors and instead have used contacts and addresses. I have been able to follow along substituting as needed, but for the life of me I cant figure out why I can't add addresses.

    first I ran:
    script/generate scaffold Address street_1:string street_2:string city:string state:string zip:string

    All the views were created, etc. but when I submit http://localhost:3000/addresses/new
    I am redirected to
    http://localhost:3000/addresses
    and no address is added.

    I have looked at the controller and it seems like it matches the contact controller, the new view is also very similar. Any pointers on where else to look? I have looked in the database and the addresses table is there.
  • xenfreak
    While following this tutorial i made some mistakes and had no clue how to fix them. i was trying to just copy all of your code by hand but i guess i made some mistakes so it didn't work. i copied all of your code and pasted it over the code that i had written and i got it to work haha. thanks again.
  • mattis
    Big thanks for this tutorial!

    As a rails beginner, I got some books, downloaded the latest version of rails and then spent much time pulling my hair out trying to understand why many of the examples didn't work as expected.

    Eventually, I realized it was because I was using rails 2 and almost all information online and in print is based on rails 1. And then to add to my frustration, most rails 2 specific docs focus on the differences with rails 1 - i.e. assuming that you already know how that works.

    So, again, BIG thanks for this tutorial. Enjoy the beer. ;-)
  • Great tutorials, please keep them coming :)
  • leo
    I guess here is a mistake

    # in model/author.rb **should be in models/book.rb
    class Book < ActiveRecord::Base
    belongs_to :author
    end
  • After creating the AddAuthorIdToBooks class I to got an error with rake db:migrate. I followed the steps above to update sqlite3 and the gem but I still get the error. Yet I can manually create author_id manually.

    natasha-2:~/rails/bookstore briandunbar$ rake db:migrate
    (in /Users/briandunbar/rails/bookstore)
    == 3 AddAuthorIdToBooks: migrating ============================================
    -- add_column(:books, :author_id, :int)
    rake aborted!
    SQLite3::SQLException: near "ADD": syntax error: ALTER TABLE books ADD "author_id" int

    (See full trace by running task with --trace)
    natasha-2:~/rails/bookstore briandunbar$ cd db
    natasha-2:~/rails/bookstore/db briandunbar$ ls
    development.sqlite3 migrate schema.rb
    natasha-2:~/rails/bookstore/db briandunbar$ sqlite3 development.sqlite3
    SQLite version 3.5.9
    Enter ".help" for instructions
    sqlite> ALTER TABLE books ADD "author_id" int;
    sqlite> .exit
    natasha-2:~/rails/bookstore/db briandunbar$


    Where am I going wrong?
  • yahya
    Great set of tutorials! I'm a complete newbie and was trying to find a tutorial to learn Ruby on Rails on heroku.com I tried your tutorials and now have a much clearer concept of how ruby works.
    Thanks
    Yahya.
  • after throwing away another $40 on a worthless book with a crappy errata site on the web, your site is a welcome relief.

    thank you.

    can i paypal you some money for your time and effort?
  • SteveT
    Hi Jonathan,

    One small suggestion regarding the last section where you suggest looking "under the hood" at the console. In my case (on the Mac, using TextMate IDE and command line to launch the ./script/server) "looking at the console" involved simply doing the following:

    tail -f log/development.log

    Maybe it would be helpful to others to add this in your comments?

    Thanks again for such a great tutorial - you're the best!

    SteveT
  • SteveT
    Hello Jonathan,

    After creating the AddAuthorIdToBooks class on my
    PPC Mac Mini with Tiger v.10.4.11, rake db:migrate
    gave me this error:

    == 3 AddAuthorIdToBooks: migrating ============================================
    -- add_column(:books, :author_id, :int)
    rake aborted!
    SQLite3::SQLException: near "ADD": syntax error: ALTER TABLE books ADD "author_id" int

    In the interests of saving time for others
    in the same boat, here's how I resolved
    this problem. With some help from the
    http://forums.pragprog.com/forums/66/topics/408
    I succeeded in upgrading sqlite3 from v3.1.3 to
    v3.5.9 and the sqlite3-ruby gem accordingly.
    (For those that don't have wget which I installed
    using fink, just grab the tarball using your
    web browser).

    which sqlite3
    [ returned "/usr/bin/sqlite3" ]
    sqlite3 -version
    [ returned "3.1.3" ]
    cd /usr/bin
    sudo mv sqlite3 sqlite3.1.3
    mkdir -p $HOME/tmp
    cd $HOME/tmp
    wget http://www.sqlite.org/sqlite-3.5.9.tar.gz
    tar -xvzf sqlite-3.5.9.tar.gz
    cd $HOME/tmp/sqlite-3.5.9
    ./configure
    make
    sudo make install
    sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3
    sudo gem install sqlite3-ruby -- with-sqlite3-dir=/usr/local/bin
    cd $HOME
    which sqlite3
    [ returned "/usr/bin/sqlite3" ]
    sqlite3 -version
    [ returned "3.5.9" ]

    Now "rake db:migrate" works fine! :)

    Hope this helps,

    Steve
  • SteveT
    Hello Jonathan,

    Two thumbs up on your tutorial series! Immediately after adding the author table I jumped to the web interface and added a couple of authors... well wouldn't you know when I carried on in the tutorial I saw we both listed Barack Obama as an author, independently! Maybe this is a good omen for November? :)

    Best,

    SteveT
  • Steve
    I agree. Best beginners tutorials I have found. I love programming in ruby but was struggling with understanding rails. Have a ways to go to be proficient but this definitely got me back on "track". ;^)

    Thanks!
    -Steve
  • Christoph
    Hi!
    Thank you for you very good tutorial, this was best part as yet. I hope you'll publish the next step soon!

    Greetings from Austria,
    Christoph
  • hi Jonathan! Thanks for the tutorials! These are the best on the web. waiting for the next series of tutorials. especially SEO rails.
  • The best intro to rails that i've read so far. Very accessible to the newbies, thanks!!!
  • MellifluidicPulse
    Wonderful! All of your tutorials have been great so far. I like how in depth you are taking everything - so many of the Rails 2.0 tutorials end after a couple of updates. Your definitely does the most digging. I read the first three of these, dug them, and then lost track of where I was with it (closed browser) and started working with some other tutorials. I found some good ones, but they hadn't posted on the subject I wanted to know about next. I searched for it and this page came up - next in the series, and exactly what I needed to know about! So now I'm back on the track. Keep up the good work
  • Martin Borgman
    A small improvement for "Adding a Forein Key in Rails":

    script/generate migration AddAuthorIdToBooks author_id:int

    This will create a complete db/migrate/003_add_author_id_to_books.rb.

    By the way, nice tutorial Janathan.
  • Jonathan
    You can run sqlite and mongrel at the same time, just do it in different windows.

    I run sqlite as follows:

    ng:bookstore$ sqlite3 db/development.sqlite3
    SQLite version 3.4.0
    Enter ".help" for instructions
    sqlite>

  • L
    Thank you for the great tutorial!

    How do you switch from: bookstore> to sqlite> (and later back to bookstore>), to view the tables for author and book?
  • Jonathan
    Peter and Pawel:

    haha, sometimes I get ahead of myself, I'll go back and fix that typo...I think in future posts, I'll just insert random bugs and see if you guys really are paying attention ;)

    I'm going to do some writing on nested routes, which is what you described (/books1/author) in my next iteration .
  • Hi Jonathan,

    real great. I was searching for a rails 2 tutorial since the new version was published, but found just a few hints how you can do stuff better in the new version compared to the older one.
    Then the "first rails 2.0 screencast tutorial" [1] appeared, which was quite nice, but follow that one, when you are new to rails ;-)

    So many thanks. I hope you will continue your tutorial. Probably some more comlicated stuff with relations. Or the new mapping (I saw something in this new screencast [1], that it goes beyond /books/1, like /books/1/author, or the like. I did not catch it realy.

    btw: There are some mistakes, of course, in your tutorial. I would make more ;-)
    But this one is a typical copy and paste mistake, which confused me for 1 second or so:

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

    The comment is wrong, see? ;-)

    Thanks
    Pawel


    [1] http://www.akitaonrails.com/2007/12/10/the-first-rails-2-0-screencast-english
  • Peter Marx
    Jonathan,
    I was close to dropping Ruby because none of the books and tutorials worked, as they assume the old version.

    Thanks to your effort Ruby will have a lot of new friends!

    Thanks! And hold on!

    Peter

    P.S. I think I found a little bug:

    >Open up your model/book.rb and model/author.rb and add >the following lines of code:
    >
    ># in model/author.rb MUST READ "in model/book.rb"
    >class Book belongs_to :author
    >end
  • Saloni
    Thank you Jonathan for making things easy to understand with your tutorials..!!1

    Thank you once again .!!!
  • Siva Gu
    Great Tutorial.
    I followed step by step and every thing worked like it supposed to. I am able to successfully add new book but when i view it for some reason the value for the Author is same as the Description. At the table level the author_id column as the right value.
    I would appreciate if you could point me where I am going wrong.

    Thankyou
  • Oh ok, I thought it was supposed to be smart enough to know how to modify the tables to make that migration possible. Wishful thinking I guess. Thanks for the timely response, keep up the good work!
  • Jonathan
    Isaac:

    Thanks for the kind words. I'm assuming you're talking about this
    migration?

    script/generate migration add_author_id_to_books


    If that is the case, it should be empty. When you generate a migration, it will create an empty file. You then proceed to fill in the blanks, the areas between self.up and self.down. It should look like this...


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

    def self.down
    remove_column :books, :author_id
    end
    end
  • Jonathan
    Adam: Thanks for that clarification, I didn't want to confuse any new users, but you are correct :) I thought about writing in some comments for those who have used the LAMP stack prior to Rails, but decided against it.
  • This series of yours really does deserve the praise its receiving. I did have a problem following this post however, and I haven't been able to find the root of the problem. After I added the relationship lines to my models, I ran the generate migration script and migration it created has empty up and down methods. So of course the migrate command accomplished nothing. I'm using the latest version of rails on Windows XP. Has anyone else had this problem and perhaps know a solution?

    Thanks again for these great tutorials
  • Jonathan,

    Thanks for these tutorials, they're very helpful.

    A note to sticklers with previous SQL experience: the procedure titled "Adding a Foreign Key in Rails" above may be confusing to those who think of "Foreign Key" as a referential constraint. Your procedure does, of course, add the required column, but it does not add the foreign key constraint to the table. Rails apparently does not provide a way of doing this based on relations alone. You have to go into the database manually or add an SQL execute line to your ruby code to introduce the constraint. The best solution I've found is the Foreign Key Migrations plugin from Red Hill Consulting at http://www.redhillonrails.org.

    Many thanks,

    Adam
  • This is just great!!! And I was here thinking that it would take a lot of time before I could really start enjoying Rails! I mean, I heard it was easy to understand but It's a little bit hard to find proper material to begginers...

    I was kinda lost when reading/watching some tutorial but now I know where I'm at... Now I'll go back to the others tutorial and get what they mean =)

    Thanks for your tutorials, they're incredibly simple, easy to understand and right to the point!!
  • Jonathan
    Your best bet would be the Rails API, the mailing list, or any of the forums.
  • eric
    Absolutely love the tutorials, thank you!

    I have a question though: Do you know of any resources that explain how to relate more complex relationship structures? What I'm looking for is a real world example of how all of the relationship types are used to handle more complex model relationships. I've just got some complex models in an app I'm trying to plan out, and I'm having trouble wrapping my head around how best to set it up.
blog comments powered by Disqus