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.
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.
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.
精彩评论