Skip to content

Instantly share code, notes, and snippets.

@bryanl
Created December 28, 2008 19:49
Show Gist options
  • Save bryanl/41030 to your computer and use it in GitHub Desktop.
Save bryanl/41030 to your computer and use it in GitHub Desktop.
# in terminal
In this lesson, we will have a look at two more activerecord
relationships: has and belongs to many and has many through. Both of
these relationships use a join table, and can be quite tricky to
implement at first. We'll start with has and belongs to many and then
cover has many through.
In our journal we want to be able to classify our entries by how we
felt when we wrote them. Maybe we were feeling happy when we wrote
the entry, or maybe we were feeling sad. We will want to be able to
attach multiple feelings to each entry. One way that we can implement
this is by creating a Feeling class and associate it with our Entry
class using a join table. Why don't we implement this now?
The first thing we'll want to do is generate a Feeling class. Our
feelings will only have a name and it'll be a string. We'll use
rail's generator for this:
$ script/generate model feeling name:string
Now before we run this migration, we also want to create a join table
as well. We'll create a separate migration for the join table:
$ script/generate migration add_join_for_feelings_and_entries
We'll now configure our migration to create the join table.
# switch to textmate
> and_entries.rb
def self.up
create_table :entries_feelings do |t|
t.integer :entry_id
t.integer :feeling_id
end
end
def self.down
drop_table :entries_feelings
end
Nowe we we can run our migration
# switch to terminate
$ rake db:migrate db:test:prepare
# switch to textmate
We'll now inform entries and feelings that they can have and belong to
many of each other. To do this, we'll start with entries. In
entries, we will add the relation at the top.
> models/entry.rb
has_and_belongs_to_many :feelings
We'll also need to do the same thing to our feeling class.
> models/feeling.rb
has_and_belongs_to_many :entries
We can test this out in our console in sandbox mode.
# switch to the terminal
$ ruby script/console -s
The first thing we'll do is create a few feelings.
>> feelings = {}
>> %w[happy sad indifferent full hungry hot cold].each {|f| feelings[f] = Feeling.create :name => f}
Now that we have some feelings, we can associate them with entries.
>> first_entry = Entry.find(:first)
At first our entry has no feelings. We were happy and full when we
wrote it, so let's update our entry's feelings.
>> first_entry.feelings = [feelings["happy"], feelings["full"]]
When we wrote our second entry, we were cold, so we'll update that one as well.
>> second_entry = Entry.find(:all)[1]
>> second_entry.feelings = [feelings["cold"], feelings["happy"]]
Now we can take a look at our feelings to see what entries are
related. First, we'll look at our happy feeling.
>> feeling["happy"].entries.inspect
And we can also look at our cold feeling.
>> feeling["cold"].entries.inspect
As your application progresses, you might find that you want to add
additional attributes to your join table. You might want to add
timestamps, or you may want to add additional logic. Rails has an
answer this and it is called has many through. What we want to do now
is update our relationship between feelings and entries to be a has
many through. To do this, we'll need to introduce a new model, and
we'll call this model atmosphere. To start off, we'll need to
generate our new model. In this instance, we'll create our own migration.
# exit the console
$ script/generate model --skip-migration atmosphere
Now we'll need generate a new migration to add timestamps to our
existing table, then we will need to update our relations to use our
new model.
$ script/generate migration move_entries_feelings_to_atmospheres
# switch to textmate
> in the migration
up:
rename_table :entries_feelings, :atmospheres
add_column :atmospheres, :updated_at, :datetime
add_column :atmospheres, :created_at, :datetime
down:
remove_columns :atmospheres, :updated_at, :created_at
rename_table :atmospheres, :entries_feelings
Since we are converting this join to a class, we'll have to make our
atmosphere belong to entry and feeling.
> models/atmosphere.rb
belongs_to :entry
belongs_to :feeling
Now we'll have to make feelings and entries have many of each other through atmosphere.
> models/entry.rb
has_many :atmospheres
has_many :feelings, :through => :atmospheres
> models/feeling.rb
has_many :atmospheres
has_many :entries, :through => :atmospheres
Now we can try this out in our console sandbox.
# switch to the terminal
$ ruby script/console -s
We'll have to create our feelings again
>> feelings = {}
>> %w[happy sad indifferent full hungry hot cold].each {|f| feelings[f] = Feeling.create :name => f}
Let's add a feeling to our first entry.
>> entry = Entry.find(:first)
>> entry.feelings = feelings["happy"]
No we'll have an atmosphere. We can load it.
>> atmosphere = Atmosphere.find(:first)
Our atmosphere is now associated to our entry and feeling hot.
In this lesson we created has and belong to many and has many through
relationships. In our next lesson, we'll focus on a feature new to
rails 2, named scopes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment