Pluck data from query results without making another query - ruby-on-rails

Ok, I got some records from a query, something like this.
works = Work.do_a_long_query_with_scope(....)
In my controller, I want to find something else base on this works' ids, then return this works in response
def index
works = Work.do_a_long_query_with_scope(....)
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
end
This works fine except that it created 2 query , 1 with SELECT works.id FROM works ... for that task function, 1 with SELECT works.* from works ... for serializer response.
So can I use works.ids without making another request to DB ?

You could just use #load to tell the relation to load everything from the database:
works = Work.do_a_long_query_with_scope(....)
works.load
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
After works.load, the works.ids call will pull the ids out of the already loaded Works instances rather than going back to the database.
Also, if your Task.find_not_finished_tasks_of_user call is doing something like:
where(id: array_of_work_ids)
then you could instead pass in the whole works relation and say:
where(id: works.select(:id))
to use a subquery instead of sending all the ids back to the database. The first one will do something like:
where id in (a_big_list_of_ids)
but the second will do:
where id in (select id from ...)
There may or may not be any noticeable difference depending on how big works.ids is. There's also a possibility that the subquery will give you different ids if something is changing the database between your load call and when the subquery executes.

Related

When is an ActiveRecord query executed?

I had a confusion about how Ruby executes a query:
Lets suppose we have two methods:
def meet1
user = User.all
end
everytime I call this method I get a query running which says:
'User Load (18.3ms) SELECT "users".* FROM "users" INNER JOIN "roles" ON "roles"."user_id" = "users"."id" WHERE "users"."banned" = 'f' AND "roles"."description" = 'gogetter''
It means that it queries the users...
lets suppose I have another method:
def meet2
user = User.all
user.to_sql
end
when I call this it returned me the SQL format of that query:
So my question is in the first method the query gets executed but does the query gets executed in the second method? Or its just showing me the end result without executing the query because I never used it?
user = User.all does nothing but create a potential query, and spawn a copy of it into user. This follows the "Builder Pattern"; user could then say user.joins(...), or user.order(...), to build a different potential query. They both return a spawned copy of the query.
What you do with meet1 triggers the actual hit to the database. I suspect that something as innocuous as p meet1, or even your IRB shell, could evaluate the spawned potential database query as an Enumeration, which then hits the database.
I would like you too have a look at this answer
when you call User.all it returns User::ActiveRecord_Relation object, and by itself this object does not issue a database query. It's where you use this object that matters.
so when meet1 is called it issues the query but in case of meet2 User.all issues relational object and user.to_sql issues the query related to database.
You can see same thing happens when you try to chain multiple filters based on condition
u = User.all
u = u.where(name: "Hello") if any_condition?
u = u.where(last_name: 'world') if any_other_condition?
in such cases the in any_condition? and any_other_condition? is true it does only one query merging all 3 thing .all and all the where conditions.
I would like you have a look at blog here it shows some kickers methods which will make you a bit more clear about the way.

What is the difference between .where(nil) and .all?

I have been using Product.all but a lot of code that I have been seeing is using Product.where(nil) when populating a variable. this site has an example that I found for using where(nil). I have searched through documentation and found where where(nil) replaced scoped but can't make heads or tails of what it does.
I believe there used to be a difference, but from Rails 4 there no longer is. This is because from Rails 4 .all returns a relation whereas it used to return an array. So previously:
Product.all
immediately fires a query to the database to return all records, which would get loaded into an array in memory. Basically you are telling Rails that you want the data NOW. See this question for more info.
Product.where(nil)
creates a query (actually an anonymous scope that returns an ActiveRecord:Relation).
The query will only execute when you try to access the data for it. Since it is a scope, you can chain other scopes (without hitting the database each time) and have the entire chain execute as one query when you access the data.
In the Justin Weiss article linked to in the question, we see this code:
def index
#products = Product.where(nil) # creates an anonymous scope
#products = #products.status(params[:status]) if params[:status].present?
#products = #products.location(params[:location]) if params[:location].present?
#products = #products.starts_with(params[:starts_with]) if params[:starts_with].present?
end
That code will execute one database call when the index method ends and returns the data.
But if you changed the first line to:
#products = Product.all
that query would execute immediately. Then the scopes are added, and another query would fire when index method ends. This is more inefficient.
EDIT
I believe the database call would actually happen when you first try to access some data in #products (not when the index method ends). Eg. if you did #products.first then the query would execute.

Rails: Can you impose multiple sort orders, in the same line?

For instance:
#examples = #user.examples.mostrecent.paginate(page: params[:page])
Where "mostrecent" is defined as:
def self.mostrecent
self.order('created_at DESC')
end
So basically the first call to the database is pull every User's example, and then on top of that, order them by most recent first. It seems like this should be doable, but for some reason I can't get it to work.
There is no defined order scope in the model I'm working with, and other calls to order work just fine. By checking the development.log I can see only the first database pulling example by users is respected. The mostrecent order is never called.
Is there a Rails way of doing this all in one line?
You could use a scope, as in:
scope :by_recent, lambda
{ |since_when| order("created_at") }

Querying the cache in rails still runs a call against the DB

I'm probably missing something very simple here, but can't understand what.
I'm trying to cache a simple active record query but every time I touch the cache, it runs the query against the DB again.
Controller Code:
products = Rails.cache.read("search_results")
if products
render :text => products[0].id
else
products = Product.where('name LIKE ?", 'product_name?')
Rails.cache.write("search_results", products)
end
I can see that in my second call I get to the if block and not the else, but any time I'm trying to touch products (like rendering it) I also see an active record call to the DB.
What am I missing?
The line
products = Product.where('name LIKE ?", 'product_name?')
returns an ActiveRecord::Relation, but does not hit the database unless a kicker method is called on it.
While I would still recommend using fetch as mentioned in my comment above, try changing the line to:
products = Product.where('name LIKE ?", 'product_name?').all
which will force the database hit, and save the actual results of the query into the cache, instead of the relation.

stop meta_search doing sql to early

in the docs it's said:
MyObject.search()
returns an instance of MetaSearch::Builder (something like ActiveRecord::Relation). But in my case when I do this I get a collection of objects because the sql-query is send to the database.
I would like to have something like this:
search = MyObject.search() # no sql-query should be done here
count = search.count # count sql done
objects = search.all # select sql done - maybee with pagination
does anyone know how to stop Meta_search from doing the the query to early?
-> ok, something mysterious going on in my shell:
search = MyObject.search() # queries the database
search = MyObject.search(); 0 # stores a MetaSearch-Object in search
the console seems to call an extra method after each comand
If you're testing in irb, be aware that returned objects are inspected. In the case of a MetaSearch builder, this means the relation gets inspected. If you have a look at ActiveRecord's inspect method, in relation.rb, you'll see that it calls to_a, which executes the query and returns the results.

Resources