has_many and belongs_to with non-primary foreign keys

Hi,

I'm having a bit of trouble with my first Rails app.

First of all your relationship is invalid. In relational databases there are three valid types of relationships, which are one-to-one, one-to-many, and many-to-many.

In Rails a one-to-one relationship is actually represented by a validated one-to-many relationship. In other words as far as the database is concerned it is a one-to-many. On the Rails side you use has_one in place of the has_many. This simply limits the one-to-many so the many side allows only one associated object.

User:   has_one :item

Item:   belongs_to :user

At one-to-many is similar to a one-to-one except it uses has_many like this:

User:   has_many :items

Item:   belongs_to :user

The third type of relationship requires what is called a "join table" and is called a many-to-many relationship:

User: has_and_belongs_to_many :items

Item: has_and_belongs_to_many :users

This requires a third table that contains two foreign keys making up two one-to-many relationships:

create_table :items do |t|   t.column :created_at, :timestamp   t.column :text, :text end create_table :users do |t|   t.column :name, :string end create_table :items_users do |t|   t.column :item_id, :integer   t.column :user_id, :integer

You never join two table between two foreign keys. There is no way for the database to track the relationship. All relations are based on one of these three basic types.

Also note that Rails provide has_many :through for many-to-many relationships where the joining table has it's own model object. This is used to expose additional columns inside the join table that are used to track information related to both side of the many-to-many association. An example of this would be Suppliers and Products. You may want to track things like the last purchase price of a specific product that was purchased from a specific supplier. This value would be stored inside the join table. Then the join table would have it own model object for accessing that information. Call it maybe ProductSupplier. Then you could do something like my_product_supplier.last_purchase_price, where my_product_supplier is an instance of the class ProductSupplier.

Hope this helps.

I think you have an extra 'user_id' column that's causing some confusion. The User table does not need it. By default, the create_table call is going to generate a users table with a auto- incrementing 'id' column. Unless you do something explicit to override the convention (you haven't) then Rails is going to use that field as the primary key/id.

By convention, a table referring to another table has a foreign key named after the class to which it refers. That means your items table should have a user_id column to refer to the User class (as it does).

Having followed the conventions you can just use the has_many/ belongs_to macros without modification.

Robert Walker wrote:

First of all your relationship is invalid. In relational databases there are three valid types of relationships, which are one-to-one, one-to-many, and many-to-many.

Ok, thanks for the overview, there weren't nearly enough basic HowTo's for this. I'm still unsure of what type of relationship I should have then.

You never join two table between two foreign keys. There is no way for the database to track the relationship. All relations are based on one of these three basic types.

Yeah, I figured that was probably my problem. The issue is that the user_id is already specified (as something like: '11384739') and the regular "id" begins at 1 and auto_increments.

Hope this helps.

It does, thank you.

AndyV wrote:

I think you have an extra 'user_id' column that's causing some confusion. The User table does not need it. By default, the create_table call is going to generate a users table with a auto- incrementing 'id' column. Unless you do something explicit to override the convention (you haven't) then Rails is going to use that field as the primary key/id.

By convention, a table referring to another table has a foreign key named after the class to which it refers. That means your items table should have a user_id column to refer to the User class (as it does).

Having followed the conventions you can just use the has_many/ belongs_to macros without modification.

I added the extra user_id column in the users table because each user already has a specified id, as does each item (and each user has_many items). What I was attempting to do was display all of the items for a given user by matching the "user_id" columns of each table. I'm basically trying to configure the models so that this call [@items = Item.find(:all, :conditions => "user_id = '#{@user.user_id}'")] can be accessed natively like @user.items.

I hope that made sense and thanks for your help so far!