Question

This ruby/rails construct always puzzles me:

User.where(:name => "Thiago").limit(3).using(:slave_one)

This must execute from left-to-right, so that each successive invocation has a receiver. So we start with the User class, invoke where on it which returns an instance of ActiveRecord::Relation. Then limit is invoked on that instance, returning another instance of ActiveRecord::Relation. Then using is invoked on that instance (which happens to choose a shard). The whole thing builds up an SQL query inside an ActiveRecord::Relation object, I guess. My question is, "what triggers the actual execution of the query"? It can't execute at any particular point in the chain because there might be successors in the chain that further modify the query. Even after using returns, the query still can't execute, because it can't know whether additional methods are tacked on to the chain. Obviously, it does execute the query after building it up, so how is the query actually invoked?


Thanks... I see now that the names of the methods in the chain have "semantics". Some will further modify the query that is being built up. The last and only the last may be of the type that requires data to be fetched.

Was it helpful?

Solution

The ActiveRecord::Relation doesn't bother talking to the database until you ask it for some data. For example:

User.where(:name => "Thiago").limit(3).using(:slave_one).count
# talks to the database here ----------------------------^^^^^

If you have a look at ActiveRecord::Relation, you'll see that it includes ActiveRecord::QueryMethods and most of the stuff in there looks like this:

def x(*args)
  relation = clone
  # merge args into relation
  relation
end

So the Relation just builds a query piece by piece until you do something that requires the query to be executed; then it will build the appropriate SQL, ship it off to the database, and do something useful (in theory) with what the database sends back.

Also note that each of the QueryMethods methods return a cloned and modified relation so you can do things like this:

r1 = User.where(:name => 'Pancakes')
r2 = r1.limit(3)

and then use r1 to grab all the matches or r2 to just grab three of them.

OTHER TIPS

This construct (where the query never executes as you're building it) is intentional. It's called Lazy Loading.

The query executes the first time you ask the relation for data from it. IE if you had:

@users = User.where(:name => "Thiago").limit(3).using(:slave_one)
@users.first.name #the query executes here

Normally this is going to execute when it his the first line in your view template that calls for data from the query.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top