has_one weirdness

I am not quite prepared to call this a rails bug. But has_one definitely behaves weirdly in rails 2.1.

Lets say you have the following two models:

class Email < ActiveRecord::Base
  belongs_to :user
end

class User < ActiveRecord::Base
  has_one :email
end

A very straight forward has_one association. Now give the following from a script/console session:

>> user = User.first
=> #<User id: 1, created_at: "2008-07-09 01:19:11", updated_at: "2008-07-09 01:19:11">
>> user.email = Email.find(user.email.id)
=> #<Email id: 1, user_id: 1, created_at: "2008-07-09 01:19:26", updated_at: "2008-07-09 01:19:26">
>> user.reload
=> #<User id: 1, created_at: "2008-07-09 01:19:11", updated_at: "2008-07-09 01:19:11">
>> user.email
=> nil

That is weird. There is no reason you would do this in production, but if you were doing this you wouldn’t expect it to update the email row to set user_id to nil. Unfortunately, that is exactly what is happening.

Personally, I wouldn’t expect it to write anything to the database until I called save on user.

Anyone have a reasonable explanation, or should I submit it to Lighthouse.

Rails 2.1 Eager Loading

As long as I can remember you could always eager load your associations in rails. If you wanted to find all your users companies, you could tell rails to do it in an efficient way.

There is no change in the code you write

User.all(:include => :company)

The SQL it used to generate was:

SELECT
  `users`.`id`      AS t0_r0
  `users`.`email   AS t0_r1
  . . .
FROM
  `users`
  LEFT OUTER JOIN `companies` ON `companies`.id = `users`.company_id

Fine, that isn’t really news, but now active_record splits that sql statement up into two statements.

SELECT * FROM `users`;
SELECT * FROM `companies`
  WHERE `companies`.id IN ('1','2', . . . );

Apparently this will be faster in most cases. I don’t have anything to say about that but, there are two interesting things I want to show you. They both involve :conditions.

If you put conditions on the users table it still generates two queries.

User.all(:include => :company,
  :conditions => "`users`.`email` LIKE '%gmail%'")
SELECT * FROM `users`
  WHERE `users`.`email` LIKE '%gmail%';
SELECT * FROM `companies`
  WHERE `companies`.id IN ('1','2', . . . );

But if you put conditions on the companies table it fails over to the old style and generates one statement

User.all(:include => :company,
  :conditions => "`companies`.`name` LIKE '%google%'")
SELECT
  `users`.`id`      AS t0_r0
  `users`.`email   AS t0_r1
  . . .
FROM
  `users`
  LEFT OUTER JOIN `companies` ON `companies`.id = `users`.company_id
WHERE
  `companies`.`name` LIKE '%google%'

I don’t know if they have to do it that way to get the correct results or what. But I thought it was interesting.

Feeds/Syndication