Reputation: 17072
I'm trying to figure out something regarding rails relationships. I already posted a question regarding a specific items not long ago but I do not really understand what's done in the underlying DB.
I have a Project
model and a Client
model.
A Project belongs_to :client
=> I need to manually add client_id
in projects table (with a migration).
A Client
has_many :projects
=> I do not need to do anything in the DB (no migration).
The project.client
and client.projects
methods are both available.
I have a Group
model and a User
model.
A Group
has_and_belongs_to_many :user
A User
has_and_belongs_to_many :group
I then need to create a migration to create a joint table with a user_id
and a group_id
pointers.
I do not really see where the border between rails and the relational database is.
Why do I need to add foreign key sometimes but not always ? How is the has_many
relationship handled as I did not do anything in the underlying DB for this particuliar guy ?
I am kind of lost sometimes :)
Thanks and Regards,
Luc
Upvotes: 2
Views: 322
Reputation: 98
class Customer < ActiveRecord::Base
has_many :orders, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
@order = @customer.orders.create(order_date: Time.now)
Upvotes: -1
Reputation: 107728
For a has_many
<-> belongs_to
assoication, you're defining that one project is owned (belongs_to
) by one client. Therefore, that client has many (has_many
) projects. For a project to determine what client it belongs to it needs to have an client_id
column so that it can look it up. This client_id
column is used by Rails when you call the client
method, much like this:
Client.find(project.client_id)
That's how you can find a project's client. The client_id
column is often referred to as a foreign key, because its a unique identifier ("key") in a table not of its origin ("foreign"). Boom.
When you call the other way around, finding all the projects a client has, i.e. client.projects
, Rails does the equivalent of this:
Project.find_all_by_client_id(client.id)
This then returns all Project
records which are associated with a particular client, based off the client_id
field in the projects
table.
With a has_and_belongs_to_many
association, such as your users & groups example, you're declaring that a user has_and_belongs_to_many :groups
.
Now if it were simply a has_many :groups
, the foreign key would go in the groups
table, or if it were a belongs_to
it would go in the users
table. Good thing to remember: the foreign key always goes in the table of the model that has the belongs_to
.
You're also declaring that a group has_and_belongs_to_many :users
, and so we come across the same problem. We can't declare the key in the users table because it doesn't belong there (because a user has many groups, you would need to store all the group ids the user belongs to) or the groups table for the same reasons.
This is why for a has_and_belongs_to_many
we need to create what's known as a join table. This table has two and only two fields (both of them foreign keys), one for one side of the association and another for the other. To create this table, we would put this in a migration's self.up
method:
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
A couple of things to note here:
groups_users
.:id
option here which, when given the value of false
generates a table with no primary key. A join table doesn't need a primary key because its purpose is to just join other tables together.group_id
and user_id
as integer fields, just like we would on a belongs_to
association.This table will then keep track of what groups have what users and vice versa.
There's no need to define additional columns on either the users
or groups
table because the join table has got that under control.
Upvotes: 5