Dmitri
Dmitri

Reputation: 2457

Rails class method scope makes an extra query

I have a relation between two objects. Let's say it like this: Model1 has_many Model2 (That doesn't really matter)

And say, I want to filter-out some of the results:

a = Model1.find(123) 
b = a.model2

And now, for example, I want to select only EVEN records (by ID)

If I do following: b.select {|x| x.id % 2 == 0} then it returns all even records as expected. And NO additional database queries created.

But if I define a class method in the Model2:

def self.even_records
   select {|x| x.id % 2 == 0}
end

Then, for some magic reason it makes an additional query to database, that looks like it re-instantiated the "b" variable (re-loads the relation):

Model2 Load (0.4ms)  SELECT `model2`.* FROM `model2` WHERE `model2`.`model1_id` = 123

Why it behaves so ? Is there any way I can fix it ?

P.S I have no fishy callbacks, like after_find or whatsoever defined in any of models.

Upvotes: 1

Views: 353

Answers (2)

Rails Guy
Rails Guy

Reputation: 3866

The Basic difference between these two is that when you call select method on b which is an array, than it calls the enumerable method select.

b.select {|x| x.id % 2 == 0} 

and when you write in a method, it calls the select method of activerecord query interface.

def self.even_records
   select {|x| x.id % 2 == 0}
end

BTW Ruby have methods like even? and odd?, so you can directly call them :

even_records = b.select{|x| x.id.even?}
odd_records = b.select{|x| x.id.odd? }

Edit: I found a simple solution for you, you can define a scope in your model Model2 like below,

scope :even_records, -> { where ('id % 2 == 0') }

and now if you will call :

Model2.even_records

you will have your even_records. Thanks

Upvotes: 1

Marek Lipka
Marek Lipka

Reputation: 51161

ActiveRecord scopes are evaluated lazily, i.e. scope is evaluated when its result is necessary. When you try this code in console, inspect method are called implicitly on every evaluated object, including ActiveRecord::Relation instance returned from

b = a.model2

call. After calling inspect on ActiveRecord::Relation, scope is evaluated and DB query is created since it's necessary to show inspect return value properly.

On the contrary, when you run your code outside rails console,

b = a.model2

won't produce DB query, thus there will probably be only one database query.

Upvotes: 2

Related Questions