views:

46

answers:

2

I had a terrible morning. Lots of emails floating around about why things don't work. Upon investigating I found that there is a data mismatch which is causing errors.

Scenario Customer and Address are two tables.

Customer contains

class Customer < ActiveRecord::Base
    has_one :address, :foreign_key => "id"        
end

Address Contains

class Address < ActiveRecord::Base
    belongs_to :customer, :foreign_key => "cid"    
end

So the two tables match on id which is the default and that column is auto incremented.

Problem on the edit Page we have some code like this.

params[:line1] = @customer.first.address.line1

It fails because no matching record is found for a customer in the address table. I don't know why this is happening. It seems that over time a lot of records did not get added to Address table. Now problem is that when a new Customer is added (say with id 500) the Address will be added with some other id (say 425) ...now you don't know which address belongs to which customer.

Question Being new to Rails, I am asking whether it is always considered good to create an extra column for joining of the records, rather than depending on the column that is automatically incremented? If I had a seperate column in Address table where I would manually insert the recently added customers id then this issue would not have come up.

+1  A: 
John Topley
If I go with what you said. then how will adding records in Customer and Address tables work? `@cust = Customer.new(:someCol => "someVal")` `@cust.save` `@addy = Address.new(:customer_id => @cust.id)` Does this look ok?
Omnipresent
You'd do `@address = @customer.build_address(:line_1 => '...', :line_2 => '...')`
John Topley
+2  A: 

That has_one-belongs_to relationship should result in the "belonging" model having the key of the "having" model. Or, in other words, the :foreign_key clause should be the same in both models.

If I have these:

class Customer < ActiveRecord::Base
  has_one :address, :foreign_key => 'cid' # note foreign_key same as in Address
end
class Address < ActiveRecord::Base
  belongs_to :customer, :foreign_key => 'cid' # note foreign_key same as in Customer
end

then I can do this:

>> cust = Customer.create(:name=>'Mr Custard')
+----+------------+
| id | name       |
+----+------------+
| 1  | Mr Custard |
+----+------------+
1 row in set
>> add = cust.create_address(:line_1 => '42 Some Street', :line_2 => 'Some where')
+----+-----+----------------+------------+
| id | cid | line_1         | line_2     |
+----+-----+----------------+------------+
| 1  | 1   | 42 Some Street | Some where |
+----+-----+----------------+------------+
1 row in set

checking:

>> Customer.first.address
+----+-----+----------------+------------+
| id | cid | line_1         | line_2     |
+----+-----+----------------+------------+
| 1  | 1   | 42 Some Street | Some where |
+----+-----+----------------+------------+
1 row in set
>> Address.first.customer
+----+------------+
| id | name       |
+----+------------+
| 1  | Mr Custard |
+----+------------+

and my database looks like this:

sqlite> select * from customers;
1|Mr Custard
sqlite> select * from addresses;
1|1|42 Some Street|Some where

(the nice table output for ActiveRecord results comes from Hirb, by the way)

Mike Woodhouse
thanks for the clear explanation. Curious, say that you now go in and run the following query `update Address set id = 100 where id = 1` (doing this because I deploy on heroku and am fed up with having to reset autoincrement) so after that update query...will your association still work?
Omnipresent
The association should work regardless of the value of `id`. But there's rarely a good reason to modify a record's primary key like that. What don't you like about the default behavior of autoincrement?
Tom
@Tom - the OP didn't specify, but the most likely reason for not using the convention is that there's a legacy schema involved. Otherwise, I can't think of a good reason not to follow convention.
Mike Woodhouse
@Omnipresent - Updating Address.id shouldn't cause a problem, because ActiveRecord doesn't think Customer needs to know it - Address holds the foreign key link, which is enough to allow navigation in either direction in a one-to-one relationship. If you update Customer.cid, then you'd need to ensure Address was kept in synch.
Mike Woodhouse