Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Eager Loading and where clause

I'm eager loading a model object with its associations:

user= User.includes(:posts).find(1)

But then at certain points in the code I would like to do something like this:

user.posts.where(:topic => "x")

But that just re-runs the query again. So instead I thought I'd do this:

user.posts.select{|post| post.topic == "x" }

That doesn't re-run the query. But I have a couple of questions.

First, Is this the right way to do it?

Second, I'm a bit confused about what select does in this case. Because when I run the last line even when I haven't used the includes function, the first time it runs the query and then after that if I run it again, it doesn't .. so is there some sort of caching involved? Because when i use the where clause it runs the query every single time.

Thank you.

like image 225
user1069624 Avatar asked Mar 20 '13 12:03

user1069624


1 Answers

select is a Ruby method on Enumerable. The first time you run

user.posts.select{|post| post.topic == "x" }

all Post instances for user's :posts association will be loaded from the database into memory; specifically into an ActiveRecord collection object. select is then called on this collection, filtering out all Post instances in the collection with a :topic attribute that is anything other than "x".

Running the above line a second time won't query the database again, because you've already loaded the posts into memory.


When you do an includes like below

user= User.includes(:posts).find(1)

it is going to eager load the :posts relation for each User instance returned (in this case, a single User where :id is 1). You now have all Post instances loaded into memory as described in the previous section above.

If you then do something like

user.posts.where(:topic => "x")

you are now telling rails to run a new query against Post, this time finding all Post instances where :topic is "x" and where :user_id is the :id of the user. where doesn't work like a "filter" on an in-memory ARel collection.


  • If you want to avoid another query that bad, select is a fine way to filter the in-memory result set.
  • You could run benchmarks to find which is faster:
    1. querying the db and repopulating another ARel collection into memory
    2. iterating over the in-memory enumberable and running a filter with Ruby
  • If you never need all related :posts for user, you can easily include this in the original query

    user.includes(:posts).where("posts.topic = ?", 'x')
    
like image 145
deefour Avatar answered Sep 22 '22 20:09

deefour