Search code examples
ruby-on-railseager-loading

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.


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