Topic: Beginning Relationships

HOWTO: Beginning Relationships
Part 1: "We met at a bar..."

Target Audience
Rails Beginner ~ You've got some knowledge of web applications, but are just starting your journey on Rails.

Introduction
The foundation of a great web application is proper relationships. The Rails framework takes most of the headache away from developing these relationships and almost entirely eliminates the need to write low-level queries to your database. If you've spent time writing those queries, Rails' ActiveRecord will make sense, but for those of you who are just getting started this can be confusing.

Demo Application Overview
In this table we'll be looking at designing our databases and defining relationships in the code. Our demonstration application will be an inventory system for a local pub. Here is an overview of our relationships:
http://www.dan-reedy.com/tutorials/relationships_part_1/application_overview.png

Hopefully that is clear enough for us to get started.

Step 1: Creating our Rails app and Creating the Models

$ Rails bar
: All the Rails creation stuff happens here
:
$ cd bar
$ ./script/generate model Drink
$ ./script/generate model Brewery
$ ./script/generate model Container
$ ./script/generate model Patron
$ ./script/generate model Tab

You'll notice in the ERD diagram that the relationship between drink and container is a straight many to many without a weak entity between them. This is essentially saying that a drink can come in a variety of different containers. I typically try to avoid relationships like this, but in some instances it makes sense to have it, so I'm including it for demonstration sake.

Since there will not be an extra model for this relationship we need to create the migration specifically. We will also need to do the same with the Drink model and the Tab model

$ ./script/generate migration create_containers_drinks
$ ./script/generate migration create_drinks_tabs

From here we'll need to modify each of the migration scripts with our attributes. The attributes are listed above in the ERD diagram, so I will not be reproducing each migration file here. I do want to include two, just for some examples. Here is the db/migrate/003_create_containers.rb migration file. I've included this file so you can see how you would add default values to a table during the migration phase.

class CreateContainers < ActiveRecord::Migration
  def self.up
    create_table :containers do |t|
      t.column "name", :string
    end
   
    # Populate with Default Values
    Container.create :name => "Bottle"
    Container.create :name => "Can"
    Container.create :name => "On Tap"
  end

  def self.down
    drop_table :containers
  end
end


The second file is db/migrate/006_create_containers_drinks.rb which demonstrates creating your many-to-many table.
class CreateContainersDrinks < ActiveRecord::Migration
  def self.up
    create_table "containers_drinks", :id => false do |t|
        t.column "container_id", :integer
        t.column "drink_id", :integer
    end
  end

  def self.down
    drop_table "containers_drinks"
  end
end


Once you've created all the migration files you'll need to update the database.yml file for your server and run the following command to create the tables.
$ rake migrate

Step 2: Defining Our Relationships in Rails
If you look at your database now you'll see all the tables have been created and are ready to be filled with information. Before we can add that info we need to specify our relationships in rails. We'll look at each relationship individually.

The Drink and Brewery Relationship
The business rule is: A brewery makes many drinks and a drink is made by one brewery (one-to-many). In rails that translates to has_many and belongs_to. Here is the code for

app/models/brewery.rb.

class Brewery < ActiveRecord::Base
  has_many :drinks
end

Simple enough. Let's look at the code for app/models/drink.rb
class Drink < ActiveRecord::Base
  belongs_to :brewery
end

You might be scratching your head and saying "Really? That's all there is to it?" Yes, that is all there is to it. Rather than waste time staring in amazement, let's look at how we would use this relationship.

Imagine, if you will, we have already populated information into the database and we want to retrieve it. In the first example we are going to get all the drinks from a brewery.

@brewery = Brewery.find_by_name("Heineken")
@drinks = @brewery.drinks
for drink in @drinks
  logger.info drink.name
end
# Output to log
# Heineken Premium
# Heineken Premium Light
# Amstel Light

The Drink and Container Relationship
The business rule is: A drink can come in many different containers and a container can hold many different drinks. In Rails that translates to has_and_belongs_to_many for both the Drink model and the Container model. Again, the code for app/models/drink.rb
class Drink < ActiveRecord::Base
  belongs_to :brewery
  has_and_belongs_to_many :containers
end

Now for app/models/container.rb
class Container < ActiveRecord::Base
  has_and_belongs_to_many :drinks
end

This sets up everything you need to have the many-to-many relationship. Again, a quick demonstration
@drink = Drink.find_by_name("Guinness")
for container in @drink.containers
  logger.info container.name
end
# Output to log
# Bottle
# Can
# On Tap
@drink = Drink.find_by_name("Heineken Premium")
for container in @drink.containers
  logger.info container.name
end
# Output to log
# Bottle

The Drink and Tab Relationship
The business rule is: A drink can appear on many tabs and a tab can contain many drinks. This is identical to the Drink and Container relationship so I will not spend much time at all here, in fact, I'm just going to put the code up.
app/models/drink.rb
class Drink < ActiveRecord::Base
  belongs_to :brewery
  has_and_belongs_to_many :containers
  has_and_belongs_to_many :tabs
end

app/models/tab.rb
class Tab < ActiveRecord::Base
  has_and_belongs_to_many :drinks
end

The Patron to Tab Relationship
The business rule is: A patron can have one tab and a tab can only belong to one patron. This translates to has_one and belongs_to in Rails. This is very similar to the brewery and drink relationship, so I will again only post the code.

app/models/patron.rb

class Patron < ActiveRecord::Base
  has_one :tab
end

app/models/tab.rb
class Tab < ActiveRecord::Base
  has_and_belongs_to_many :drinks
  belongs_to :patron
end

So there you go, we've setup all the relationships as shown in the ERD diagram above. So where do we go from here? Well, just to show you another type of relationship, let's imagine we wanted to know all the drinks a patron ordered. Here is how we would do it with our current relationship setup.
@patron = Patron.find_by_name("Reedy")
@drinks = @patron.tab.drinks

It's not difficult, but rails is all about simplicity, so there is another way to do it.
The Patron and Drink Relationship
Knowing that we want to just get the drinks the patron has on his tab we can bypass the tab all together using the has_many, :through relationship.
app/models/patron.rb
class Patron < ActiveRecord::Base
  has_one :tab
  has_many :drinks, :through => :tab
end

Now, we can get the drinks without needing to reference tab.
@patron = Patron.find_by_name("Reedy")
@drinks = @patron.drinks

Wrapping it Up
This was a very basic tutorial simply laying out the ground work of relationships. I plan on building on this basic application to demonstrate what you can do with Rails in a simple environment. Since all of this was extremely basic, I will not be including any source code downloads, but with Part II I plan on including the application for download.

Please leave any comments, point out any errors or give me grief for writing a tutorial that probably didn't need to be written!

Last edited by Reedy (2006-08-02 08:36:30)

Most code examples are usually pulled out of the air and not tested. Use at your own risk!

Re: Beginning Relationships

Great post!  Thanks smile  I don't know if you want to move in to this, but something that I may try on my own is to use these relationships and test out the simply_restful additions to edge.  I haven't seen many tutorials on this subject.  It may be a path that you take with this tutorial if you want to go that way.  Great job though!

Re: Beginning Relationships

Great tutorial, will be very useful for first-timers and even though I knew this stuff already your clear & concise writing style helped crystalise the information in my head. Good example data, something that people can relate to easily.

One error to correct, the 'The Drink and Container Relationship' part of your article seems to be wrapped in the code block where you output the brewery's drinks.

Awesome dude, look forward to part II

Re: Beginning Relationships

Thanks for pointing out the formatting error. It's been fixed. I appreciate the praises and hope to get started on part 2 soon.

Most code examples are usually pulled out of the air and not tested. Use at your own risk!

Re: Beginning Relationships

This is solid Reedy.  Totally useful.  This may be a better introduction to relationships than the AWDWR book - and a heck of a lot more value per dollar.

Re: Beginning Relationships

Extremely helpful, thanks, but I would like to suggest that you let the code display fully as the print version still chomps some code.

I hope the follow up integrates acts_as_list with habtm!

And it would also be helpful to see some migration files pushing habtm join table data.

Last edited by xia (2006-08-25 01:33:37)

Re: Beginning Relationships

xia wrote:

I would like to suggest that you let the code display fully as the print version still chomps some code.

I think that's the fault of the current implementation of our code tag.  I'll look into what we could do about that.

Josh Catone helps run this place
Rails Forum - Rails Jobs

Re: Beginning Relationships

Great, thanks...

Is it possible to print the individual posts (without copy/paste of course !) ?

Last edited by blackflicker (2006-08-25 18:17:33)

Re: Beginning Relationships

A very helpful and well presented articly, many thanks - I was just thinking I would be using find_by_sql, since I wasnt making much sense of joins in rails  eg What is the meaning of Firm#clients in the api documentation?

I am however struggling with using some of the methods such as minimum on joined tables.  eg, I have a bookingrefs and bookings table with a one to many on id/bokingref_id.   bookings has a column datebooked.   and I would like to find minimum databooked for a specific id.  Maybe these methods might feature in your part II (assuming they cab be used across joins)

Re: Beginning Relationships

OK so it didnt work before, but my post, I tried again and it worked. 

In case anyone is intereste I did this:

@bookingref=Bookingref.find_by_id(26)
earliest=@bookingref.bookings.minimum('hiredate)

or without the intermediate variable:

Bookingref.find_by_id(26).bookings.minimum('hiredate)

Now that is neat, even though possibly a bit unreadable.

Re: Beginning Relationships

Reedy,

Useful guide.  Thanks.  Two points:

1.  You assume the reader knows how to make the migration tables.  However, I didn't know that tables that belong_to require an id for what they belong to.  For example, drinks requires a 'brewery_id'.  A newbie might not know that.

2. The :through part doesn't work.  I get "Invalid source reflection macro :has_and_belongs_to_many for has_many :drinks, :through => :tab.  Use :source to specify the source reflection."

Would be intersted in seeing the source.
-Andrew Roth

Re: Beginning Relationships

and is this out of date now ?
Code syntax changes and :through seems to be the next big thing but very little on it is here

Re: Beginning Relationships

Things move fast in Rails world. I have not updated this code to make use of :through as it is not necessary for the tutorial project. In future versions I will include an example of :through.

I'm working on the second part currently, so be sure to check back. I'll be updating the existing code base and providing a downloadable copy as well.

Most code examples are usually pulled out of the air and not tested. Use at your own risk!

Re: Beginning Relationships

Although :through is becoming a common alternative to has_and_belongs_to_many, I think they both still have their place. It all depends upon what you need.

Railscasts - Free Ruby on Rails Screencasts

Re: Beginning Relationships

Reedy wrote:

I'm working on the second part currently, so be sure to check back.

If you get it in by Oct. 31st it'd be eligible for the tutorial contest. smile

Josh Catone helps run this place
Rails Forum - Rails Jobs

Re: Beginning Relationships

Very nice and clear.

I am currently facing a challenge related to rendering partials - I have a simple data structure.

Users, Personalinfos, and Pictures.

User {
has_many:picture
has_one:personalinfo
}

Personalinfo {
belongs_to:user
validates_associated:user
}

Picture {
belongs_to:user
validates_associated:user
}

I have a file - index.rhtml - I want to display login form and a list of existing users - So I rendered a partial _list.rhtml - which contains some html tags (formatting info) - now from within _list.rhtml i have this statement -

<%=render_collaction_of_partials "list_profiles", @userprofiles%>

in view _list_profile.rhtml - when i try to access personalinfo (e.g. list_profiles['personalinfo].firstname) nil object is returned - I have checked the db the data is there and the controller does populate @userprofiles (e.g. @userprofiles[1].personalinfo.nil? = false) - in the controller i have the following statement
@userprofiles = User.find(:all)

so now my question is why the statement <%= list_profiles['personalinfo'].firstname %> returns nil.firstname error?

thanking in anticipation

Last edited by dagger007 (2006-12-10 17:25:21)

radical vision fuels disruptive technologies

Re: Beginning Relationships

What

Re: Beginning Relationships

Schmii,
you're totally free to write one of these perfect tutorials.  Go right ahead.

Re: Beginning Relationships

You should also keep in mind that this tutorial was published on August 1st and some things in Rails have changed since then.

Josh Catone helps run this place
Rails Forum - Rails Jobs

Re: Beginning Relationships

besides the fact that nothing if perfect - tutorials should always contain errors for the people who want to use it - so that they could THINK their way out of it wink

radical vision fuels disruptive technologies