ROR - Active record avoid n+1 queries - ruby-on-rails

I have a model (News) associated with another model (Category), so in News model i have:
has_and_belong_to_many :news_categories, join:table: 'news_categories_news'
I want to take all news with own categories, so:
News.find(/*conditions*/).includes(:news_categories)
If I check in console I see the right inner join query, but when I call
#news.news_categories
(Where news is a single news in the result array) if I check in console I see another query to take the categories for the current news, how can I avoid this redundant query?
p.s: sorry for my english...

First of all, .includes can't work when chained after .find. Reason - find will not return the ActiveRecord::Relation which is necessary for relational chaining; it will rather return the matching News object or error.
You should do:
#all_news = News.includes(:news_categories).where(id: 1)
#news = #all_news.first
#news.news_categories # shouldn't invoke new query

Thank you to all but i resolved with eager_load()
It's generate just once query!

Related

How to add attribute/property to each record/object in an array? Rails

I'm not sure if this is just a lacking of the Rails language, or if I am searching all the wrong things here on Stack Overflow, but I cannot find out how to add an attribute to each record in an array.
Here is an example of what I'm trying to do:
#news_stories.each do |individual_news_story|
#user_for_record = User.where(:id => individual_news_story[:user_id]).pluck('name', 'profile_image_url');
individual_news_story.attributes(:author_name) = #user_for_record[0][0]
individual_news_story.attributes(:author_avatar) = #user_for_record[0][1]
end
Any ideas?
If the NewsStory model (or whatever its name is) has a belongs_to relationship to User, then you don't have to do any of this. You can access the attributes of the associated User directly:
#news_stories.each do |news_story|
news_story.user.name # gives you the name of the associated user
news_story.user.profile_image_url # same for the avatar
end
To avoid an N+1 query, you can preload the associated user record for every news story at once by using includes in the NewsStory query:
NewsStory.includes(:user)... # rest of the query
If you do this, you won't need the #user_for_record query — Rails will do the heavy lifting for you, and you could even see a performance improvement, thanks to not issuing a separate pluck query for every single news story in the collection.
If you need to have those extra attributes there regardless:
You can select them as extra attributes in your NewsStory query:
NewsStory.
includes(:user).
joins(:user).
select([
NewsStory.arel_table[Arel.star],
User.arel_table[:name].as("author_name"),
User.arel_table[:profile_image_url].as("author_avatar"),
]).
where(...) # rest of the query
It looks like you're trying to cache the name and avatar of the user on the NewsStory model, in which case, what you want is this:
#news_stories.each do |individual_news_story|
user_for_record = User.find(individual_news_story.user_id)
individual_news_story.author_name = user_for_record.name
individual_news_story.author_avatar = user_for_record.profile_image_url
end
A couple of notes.
I've used find instead of where. find returns a single record identified by it's primary key (id); where returns an array of records. There are definitely more efficient ways to do this -- eager-loading, for one -- but since you're just starting out, I think it's more important to learn the basics before you dig into the advanced stuff to make things more performant.
I've gotten rid of the pluck call, because here again, you're just learning and pluck is a performance optimization useful when you're working with large amounts of data, and if that's what you're doing then activerecord has a batch api you should look into.
I've changed #user_for_record to user_for_record. The # denote instance variables in ruby. Instance variables are shared and accessible from any instance method in an instance of a class. In this case, all you need is a local variable.

proper way to debug sql result in rails

I'm currently using
puts #object.inspect
to debug the results of queries in rails, but the output doesn't seem to include any joins I've defined for the query. How do I get the full array to show?
For example, if I do the following
#object = Object.joins("JOIN associations ON associations.id = object.association_id")
.where(:id => params[:object_id])
.select("objects.*, associations.*")
.first
puts #object.inspect
I get the all the Object fields in my debug array, but none of the association fields. Yet they are there when I try to use them in my view (#object.association_field etc)
PS - the above query looks ugly, I'm only trying to pull one record, but I was getting various errors if I tried to use .find() instead of .where().first. Suggestions welcome on how to make it more railsy
Why not the simplest possible way:
#object = Object.find(params[:object_id])
#association = #object.associations.first
puts "#{#object.inspect}, #{#association.inspect}"
(assuming that has_many :associations is defined in Object)
And the reason you are not getting fields for association is because the only thing that joins does is joining to another table in SQL. It does not fetch joined data. select only select subset of object's attributes, I think the rest is just ignored.

How to efficiently retrieve all record in rails 3.2 and assign them to a query-able object?

In our rails 3.2 app, we need to retrieve all customer records out of customer table and assign them to a variable customers and do query (such as .where(:active => true) on variable customers late on. There are 2 questions here:
what's the better way to retrieve all records?
Customer.all works. However according to rails document, it may have performance issue when Customer table gets large. We tried Customer.find_each and it has error "no block given (yield)".
How to make the variable customers query_able?
When performing query on variable customers (like customers.where(:active => true)), there is an error: undefined methodwhere' for #. It seems that thecustomersis an array object and can't takewhere. How can we retrievecustomers` in such a way it can be query-able?
Thanks for help.
In Rails < 4 .all makes database call immediately, loads records and returns array. Instead use "lazy" scoped method which returns chainable ActiveRecord::Relation object. E.g.:
customers = Customer.scoped
...
customers = customers.where(:active => true)
customers = customers.where(...)
etc...
And at the moment when you will need to load records and iterate over them you can call find_each:
customers.find_each do |customer|
...
end

Refactor and Improve Query in Rails

Outlet.rb:
def latest_reports
weekly_reports.limit(10)
end
Outlet_controller.rb:
#all_outlets = Outlet.includes(:weekly_reports)
#search = #all_outlets.search(params[:q]) # load all matching records
#outlets = #search.result.order("created_at DESC").page(params[:page])
outlet/index.slim:
- #outlets.each do |outlet|
tr
td= link_to outlet.name, outlet_path(outlet)
th
ul.reports
li class="#{'done' if outlet.monitored_today}"
th
ul.reports
- for report in outlet.latest_reports
li class="#{'done' if report.quota_met}"= report.times_monitored
I'm not sure why, but this loads it up as several different queries. I'm pretty sure it's because the include in my controller isn't correct (because I'm using a method in the model).
If anyone could help me improve this, I would be extremely grateful :).
Note: I'm developing on PostgreSQL
Update:: Posted the full controller action.
In rails 3 at least, if you use
Model1.includes :model2
then the result is one query for each model. You can access instances of the associated model from the result and no extra queries will be made.
If you really want it all in one query, you can do this:
Model1.joins(:model2).includes(model2)
This will produce a nice long JOIN query that loads all the data for both models in one go. Rails will populate the result with instances of both models already loaded.
So, you should be able to replace
#all_outlets = Outlet.includes(:weekly_reports)
with
#all_outlets = Outlet.includes(:weekly_reports).joins(:weekly_reports)
and it should combine everything into one query.

Rails 3: How to eager load an association AND apply a condition with includes()?

My app has Question models that has_many UserResponse models.
I'd like to get the last 5 questions that a particular User has answered with the associated UserResponse objects filtered on the user_id field.
Here's what I currently have:
Question.group("questions.id").
joins("inner join user_responses r on r.question_id = questions.id").
where("r.user_id = #{user_id}").
order("questions.id asc").limit(5)
This query gives me back what I want, but the problem is when I get a Question out of the array and do question.user_responses the result is all of the responses for that question, not just the ones that should be filtered by the join/where clause above.
I've tried to do this:
Question.includes(:user_responses).group("questions.id").
joins("inner join user_responses r on r.question_id = questions.id").
where("r.user_id = #{user_id}").
order("questions.id asc").limit(5)
thinking it would eager load each response...but it doesn't appear to function that way.
Where am I going wrong?
If it makes a difference, the reason I need everything eagerly loaded is because I want to take the array and call to_json on it and return the json from a web service for my mobile app, so I need the entire graph available.
I think you're trying to get too complicated here. How about the following?
Question
.includes(:user_responses)
.where("user_id = ?", user_id)
.order("questions.id asc")
.limit(5)

Resources