Rails Forum
Rails Work - the best place to post and find great Ruby on Rails jobs.
Username
Password

You are not logged in.

New Posts in this thread

#1 2006-08-01 16:42:53

Reedy
Coach Class
From: So. Ill, USA
Registered: 2006-06-19
Posts: 55
Website

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

Code :   - fold - unfold
  1. $ Rails bar
  2.  : All the Rails creation stuff happens here
  3.  :
  4. $ cd bar
  5. $ ./script/generate model Drink
  6. $ ./script/generate model Brewery
  7. $ ./script/generate model Container
  8. $ ./script/generate model Patron
  9. $ ./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

Code :   - fold - unfold
  1. $ ./script/generate migration create_containers_drinks
  2. $ ./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.


Code :  ruby - fold - unfold
  1. class CreateContainers < ActiveRecord::Migration
  2.   def self.up
  3.     create_table :containers do |t|
  4.       t.column "name", :string
  5.     end
  6.     
  7.     # Populate with Default Values
  8.     Container.create :name => "Bottle"
  9.     Container.create :name => "Can"
  10.     Container.create :name => "On Tap"
  11.   end
  12.  
  13.   def self.down
  14.     drop_table :containers
  15.   end
  16. end 
The second file is db/migrate/006_create_containers_drinks.rb which demonstrates creating your many-to-many table.

Code :  ruby - fold - unfold
  1. class CreateContainersDrinks < ActiveRecord::Migration
  2.   def self.up
  3.     create_table "containers_drinks", :id => false do |t|
  4.         t.column "container_id", :integer
  5.         t.column "drink_id", :integer
  6.     end
  7.   end
  8.  
  9.   def self.down
  10.     drop_table "containers_drinks"
  11.   end
  12. 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.

Code :   - fold - unfold
  1. $ 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.

Code :  ruby - fold - unfold
  1. class Brewery < ActiveRecord::Base
  2.   has_many :drinks
  3. end 
Simple enough. Let's look at the code for app/models/drink.rb

Code :  ruby - fold - unfold
  1. class Drink < ActiveRecord::Base
  2.   belongs_to :brewery
  3. 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.


Code :  ruby - fold - unfold
  1. @brewery = Brewery.find_by_name("Heineken")
  2. @drinks = @brewery.drinks
  3. for drink in @drinks
  4.   logger.info drink.name
  5. end
  6. # Output to log
  7. # Heineken Premium
  8. # Heineken Premium Light
  9. # 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

Code :  ruby - fold - unfold
  1. class Drink < ActiveRecord::Base
  2.   belongs_to :brewery
  3.   has_and_belongs_to_many :containers
  4. end 
Now for app/models/container.rb

Code :  ruby - fold - unfold
  1. class Container < ActiveRecord::Base
  2.   has_and_belongs_to_many :drinks
  3. end 
This sets up everything you need to have the many-to-many relationship. Again, a quick demonstration

Code :  ruby - fold - unfold
  1. @drink = Drink.find_by_name("Guinness")
  2. for container in @drink.containers
  3.   logger.info container.name
  4. end
  5. # Output to log
  6. # Bottle
  7. # Can
  8. # On Tap
  9. @drink = Drink.find_by_name("Heineken Premium")
  10. for container in @drink.containers
  11.   logger.info container.name
  12. end
  13. # Output to log
  14. # 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

Code :  ruby - fold - unfold
  1. class Drink < ActiveRecord::Base
  2.   belongs_to :brewery
  3.   has_and_belongs_to_many :containers
  4.   has_and_belongs_to_many :tabs
  5. end 
app/models/tab.rb

Code :  ruby - fold - unfold
  1. class Tab < ActiveRecord::Base
  2.   has_and_belongs_to_many :drinks
  3. 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

Code :  ruby - fold - unfold
  1. class Patron < ActiveRecord::Base
  2.   has_one :tab
  3. end 
app/models/tab.rb

Code :  ruby - fold - unfold
  1. class Tab < ActiveRecord::Base
  2.   has_and_belongs_to_many :drinks
  3.   belongs_to :patron
  4. 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.

Code :  ruby - fold - unfold
  1. @patron = Patron.find_by_name("Reedy")
  2. @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

Code :  ruby - fold - unfold
  1. class Patron < ActiveRecord::Base
  2.   has_one :tab
  3.   has_many :drinks, :through => :tab
  4. end 
Now, we can get the drinks without needing to reference tab.

Code :  ruby - fold - unfold
  1. @patron = Patron.find_by_name("Reedy")
  2. @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 12:36:30)


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

Offline

 

#2 2006-08-02 06:05:02

MattW
Ticketholder
Registered: 2006-07-31
Posts: 6

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!

Offline

 

#3 2006-08-02 09:06:27

Baltar
Coach Class
Registered: 2006-06-19
Posts: 54

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

Offline

 

#4 2006-08-02 12:37:35

Reedy
Coach Class
From: So. Ill, USA
Registered: 2006-06-19
Posts: 55
Website

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!

Offline

 

#5 2006-08-02 18:17:13

danger
Moderator
From: Seattle
Registered: 2006-06-15
Posts: 922
Website

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.

Offline

 

#6 2006-08-25 03:54:30

xia
Ticketholder
From: Melbourne, Australia
Registered: 2006-08-25
Posts: 24

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 05:33:37)

Offline

 

#7 2006-08-25 05:38:46

Josh
Forum Ghost
From: RI-USA
Registered: 2006-05-11
Posts: 734
Website

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
Forum - Plugins - Jobs
I write for SitePoint

Offline

 

#8 2006-08-25 22:14:51

blackflicker
Ticketholder
Registered: 2006-08-25
Posts: 2

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 22:17:33)


java c# unix

Offline

 

#9 2006-08-29 11:41:10

tonymartin
Ticketholder
Registered: 2006-08-29
Posts: 2

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)

Offline

 

#10 2006-08-29 12:13:14

tonymartin
Ticketholder
Registered: 2006-08-29
Posts: 2

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.

Offline

 

#11 2006-10-07 22:08:32

andrewroth
Ticketholder
Registered: 2006-10-07
Posts: 1

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

Offline

 

#12 2006-10-17 16:28:30

tripdragon
Mechanic
Registered: 2006-07-07
Posts: 250

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

Offline

 

#13 2006-10-17 16:31:21

Reedy
Coach Class
From: So. Ill, USA
Registered: 2006-06-19
Posts: 55
Website

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!

Offline

 

#14 2006-10-17 16:37:10

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

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

Offline

 

#15 2006-10-17 18:15:58

Josh
Forum Ghost
From: RI-USA
Registered: 2006-05-11
Posts: 734
Website

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
Forum - Plugins - Jobs
I write for SitePoint

Offline

 

#16 2006-12-10 20:35:20

dagger007
Ticketholder
From: Copenhagen, Denmark
Registered: 2006-12-10
Posts: 24

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 21:25:21)


radical vision fuels disruptive technologies

Offline

 

#17 2006-12-24 07:55:34

schmii
Ticketholder
Registered: 2006-12-24
Posts: 4

Re: Beginning Relationships

What’s with you guys can’t release a tutorial that isn’t fraught with errors and omissions?
It’s a total disservice to anyone trying to learn rails.
Why not publish working code you’ll cut the number of posts in half.

Offline

 

#18 2006-12-24 08:19:46

danger
Moderator
From: Seattle
Registered: 2006-06-15
Posts: 922
Website

Re: Beginning Relationships

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

Offline

 

#19 2006-12-24 09:21:48

Josh
Forum Ghost
From: RI-USA
Registered: 2006-05-11
Posts: 734
Website

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
Forum - Plugins - Jobs
I write for SitePoint

Offline

 

#20 2006-12-24 13:39:46

dagger007
Ticketholder
From: Copenhagen, Denmark
Registered: 2006-12-10
Posts: 24

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

Offline

 

Board footer

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson