Rails Eager Loading and where clause

eager-loading, ruby-on-rails

Solution

`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:

- querying the db and repopulating another ARel collection into memory

- 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')

Problem

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.

Original source