Refactor and Improve Query in Rails - ruby-on-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.

Related

Performance difference when calling class methods in model vs controller in rails?

In my rails app I am fetching a batch of data from the DB with around a million records. I am simply calling the following query combined with some pagination logic, and right now it is working very well. The code is defined in my model, like so:
def find_records(current_page, max_records, start_value, end_value)
where(value_range: start_value..end_value)
.offset((current_page - 1) * max_records).limit(max_records)
end
However, in my previous attempt, I had the following code defined in my model:
def find_records(max_records, start_value, end_value)
where(value_range: start_value..end_value)
end
And I called .offset and .limit inside the controller like so:
def index
current_page = params[:page]
max_records = 3
start_value = 4
end_value = 8
Model.find_records(start_value, end_value).offset((current_page - 1) * max_records).limit(max_records)
end
When I did this, my memory completely gave up on the 3rd or 4th page and my app just crashed. I don't know why calling .limit and .offset in the model solved the issue.
So my question is, how does calling class methods in your model rather than the controller improve code execution performance? I mean this query is obviously data-related so it makes sense to call it inside the model anyways, but I would still like to know the wonders behind the magic.
Thank you!
how does calling class methods in your model rather than the controller improve code execution performance?
It should not. Both your queries return a ActiveRecord::Relation. Both offset and limit are used to build the query, so in both scenarios you should see the same query in your logs. Please check your development.log when in doubt.
Having the code query code in your model makes sense. The controller shouldn't know all those details.
About the pagination, there are a few solutions in the rails world - Kaminari, will_paginate

ROR - Active record avoid n+1 queries

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!

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.

How to search for wild card in Rails

Im trying to search my User model for all Users that start with any integer, I have code for individual letters and it works, but Im having trouble getting it working with a wild card. Right now I have this code:
in my view:
<%= link_to '#', users_charlist_path(:char => '[0123456789]' %>
and in my controller I have:
def charlist
#a = User.where('goal like ?', "#{params[:char]}%").to_a
end
how ever, '[0123456789]', doesnt seem to work as it does not return anythign to me even though I have users whose names begin with an integer. how do i do this?
The where method is a part of ActiveRecord which maps the objects to the database. So how you can query the database with a regex depends on which db you are using not on ruby. You need to look up the regex functions of the database your using. For mysql you can find them here:
http://dev.mysql.com/doc/refman/5.1/de/regexp.html
An alternative is to select all objects an use the select method to filter the results that match your needs. That method is documented here:
http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-select
On big amounts of data I whould suggest to use the database even if that means your application isnt 100% portable between different database systems.

Avoiding lazy loading in Datamapper in loop

I am new to Ruby on Rails and Datamapper. I have written models using Datamapper, one of my model name is Student. In one view haml file I have written the following code:
-students = Student.all
-students.each |student|
%tr
%td= student.roll_no
%td= student.type if student.type
%td= student.department.name
Here I have used newrelic -rpm for profiling my code. Here I found that in each iteration of the above block, one query of the form select prop1, prop2,... from students where id ="some value" is being generated. This is very undesired as it is taking time on each iteration of the block. I think it's due to lazy loading. I have spent nearly a week on that but found nothing to avoid this. If anyone have any idea regarding this please help me. Thank you.
It could be helpful if you could show us the schema of the Students table in app/db/schema.rb.
I suspect your problem is not that each iteration of student takes so long because of lazy evaluation of each row in students, but that it has to load the department in each step. The Student.all is, since Rails 4, lazily evaluated, but it loads the whole set at once.
In order to solve your issue, you have to write Student.includes(:department) in your first line.
You can force DataMapper to preload data before embarking on the loop.
After loading the Collection into students, add a line such as:
prefetch = students.collect { |s| s.department }
You don't have to do anything with the prefetch object; DataMapper will load all the data and associate it with the students object. You should then find that you can iterate through the loop as expected without a separate query been generated for each. (I've just tested this on a project I'm working on and it succeeds.)

Resources