Vighnesh
Vighnesh

Reputation: 728

When does passing an instance in place of an id work in Ruby on Rails ActiveRecord?

Example scenario: The model Worker belongs_to the model Bucket.

See the following queries:

1.9.3p194 :045 > Worker.where(bucket_id: Bucket.first).count
   (0.7ms)  SELECT COUNT(*) FROM "workers" WHERE "workers"."bucket_id" = 1
 => 38
1.9.3p194 :046 > Worker.where(bucket_id: Bucket.first.id).count
   (0.7ms)  SELECT COUNT(*) FROM "workers" WHERE "workers"."bucket_id" = 1
 => 38

1.9.3p194 :047 > Worker.new bucket_id: Bucket.first
 => #<Worker id: nil, email: nil, created_at: nil, updated_at: nil, bucket_id: nil>
1.9.3p194 :048 > Worker.new bucket_id: Bucket.first.id
 => #<Worker id: nil, email: nil, created_at: nil, updated_at: nil, bucket_id: 2>

As you can see, in case of the where function, passing an instance such as Bucket.first works in lieu of the exact id. So one would think it'd work for the new function too. Instead it fails silently!

Why does it work this way?

Upvotes: 3

Views: 1836

Answers (3)

ryanb
ryanb

Reputation: 16287

I believe this is happening in the ActiveRecord::PredicateBuilder. You can see there if the value is an ActiveRecord::Base object it will call id on it. The new method does not trigger this code so it will behave differently.

I prefer to be explicit and pass in the id directly. However upcoming in Rails 4 you will be able to do this:

Worker.where(bucket: Bucket.first).count

That nicely parallels with initialization:

Worker.new(bucket: Bucket.first)

In general, I recommend passing an id if the attribute you are setting/matching ends in _id.

Update: I also wanted to point out, the initialization will inherit the where conditions. So this will work in Rails 3.2:

Worker.where(bucket_id: Bucket.first).new

I'm assuming that ends up going through the PredicateBuilder, but not certain about that. Even though this works I don't recommend it.

Upvotes: 3

Nich
Nich

Reputation: 1102

From what I understand, Bucket.first will return an instant object, but Bucket.first.id will only return id of the instant object.

So, when you creating Worker.new and pass bucket_id: Bucket.first, it will actually do not work, on the other hand,

Worker.new bucket_id: Bucket.first.id 

it will pass the correct param in order to create new Worker.

Upvotes: 0

Michael Durrant
Michael Durrant

Reputation: 96614

When you use .new you are creating a blank record but it has no ID yet. With Active Record this is frequently used for forms.

This is different from say .create which, if given the required valid parameters will create a record with an ID. Similarly, selecting a record with Bucket.first gets an existing record that has an ID.

If you use .new, set the required parameters and then do a .save you will then get an ID.

Upvotes: 0

Related Questions