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:
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"
enddef 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
enddef 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 LightThe 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
# BottleThe 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.drinksIt'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.drinksWrapping 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)