Extra queries listed by MiniProfiler - ruby-on-rails

In my controller action, I included all associations needed by the view, to avoid multiple calls to the database. (I'm trying to isolate the the views layer to only render the data collected by the controller).
I'v found out that the view still communicates with the database (17 Queries):
These 17 extra queries are not needed. Since I have tested the controller queries from the console, and it successfully collects all the data needed by the partial _dropdown (in 5 Queries) without any further database communication.
Here is the query in my controller, It meants to avoid the N+1 problem. (Including all the variables called in the view)
Here is the dropdown code:
- #messages.each do |message|
%li.conversation-container
%a{href: conversation_path(message.conversation_id)}
- if message.sender != current_user
.notification-avatar{style: "background: url(#{message.sender.avatar_url}); background-size: contain; background-repeat: no-repeat; background-position: 50% 50%;"}
- else
- other_participant = message.conversation.conversation_participants.select{|p| p.user_id != current_user.id }.first.user
.notification-avatar{style: "background: url(#{other_participant.avatar_url}); background-size: contain; background-repeat: no-repeat; background-position: 50% 50%;"}
%p
%strong
- if message.sender != current_user
= message.sender.name
- else
= other_participant.name
%br
- if message.sender == current_user
%i.fa.fa-mail-reply-all
= truncate(message.body,length: 25)
.time
= time_ago_in_words(message.created_at)
ago
- if #messages.count == 0
%li
.empty-state-text-white
No messages
Output from console:
2.0.0-p353 :006 > ms = Message.dropdown_for(3).all
Message Load (1.2ms) SELECT "messages".* FROM "messages" LEFT JOIN messages AS m ON messages.id != m.id
AND m.conversation_id = messages.conversation_id
AND messages.created_at < m.created_at INNER JOIN conversation_participants AS cp ON cp.conversation_id = messages.conversation_id AND cp.user_id = 3 WHERE (m.id IS NULL) ORDER BY cp.seen , cp.updated_at DESC LIMIT 5
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (6, 4, 5)
Conversation Load (0.4ms) SELECT "conversations".* FROM "conversations" WHERE "conversations"."id" IN (4, 2, 3)
ConversationParticipant Load (0.2ms) SELECT "conversation_participants".* FROM "conversation_participants" WHERE "conversation_participants"."conversation_id" IN (4, 2, 3)
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (6, 3, 4, 5)
=> [#<Message id: 8, body: "saSasa", sender_id: 6, conversation_id: 4, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 16:05:40", updated_at: "2014-11-17 16:05:40">, #<Message id: 2, body: "asdnas dagsdashjdg jahs d", sender_id: 4, conversation_id: 2, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 11:32:36", updated_at: "2014-11-17 11:32:36">, #<Message id: 6, body: "SADASD A DSA ", sender_id: 5, conversation_id: 3, sent: true, attachment_id: nil, attachment_type: nil, created_at: "2014-11-17 13:43:34", updated_at: "2014-11-17 13:43:34">]
2.0.0-p353 :007 > ms.first.conversation.conversation_participants.select{|cp| cp.user_id != 3}.first.user
=> #<User id: 6, first_name: "Ddsfsd", middle_name: nil, last_name: "Fsdfsd", photo: nil, email: "1#k.com", encrypted_password: "$2a$10$5sGIb2DbQ1ctMrTzD3AJ0uV18hhiC5Ei1wcfE7MSAvRU...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2014-11-17 15:27:06", last_sign_in_at: "2014-11-17 15:27:06", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmation_token: nil, confirmed_at: "2014-11-17 15:27:48", confirmation_sent_at: "2014-11-17 15:27:05", unconfirmed_email: nil, failed_attempts: 0, unlock_token: nil, locked_at: nil, authentication_token: nil, created_at: "2014-11-17 15:27:05", updated_at: "2014-11-17 15:27:48", slug: "ddsfsd_fsdfsd">
2.0.0-p353 :008 > ms.count
=> 3
How can I stop these queries from running without a purpose?

* Debugging
Well after debugging every possible factor that could cause this issue.
I have tried to set config.cache_classes to true, in my development.rb. This successfully removed all the extra queries.
I concluded that (by default) the schema is not loaded for any model when the classes are not cached. In other words, when config.cache_classes is set to false, the schema for each model is loaded for every request as a separate query.
Here is a similar issue column_definitions method being called before and after every single SQL statement on PostgreSQL.
* Conclusion
cache_classes should be set to false in development environment.
Ignore the extra internal queries from postgresql connection adapter
loading the schema for each model since it not going to affect your
production environment (production has config.cache_classes set to
true).

You can try bullet gem which will tell you is there any N+1 prolem in query. If there is no problem of N+1 problem then you should try to implement fragment caching.

Simple question. Have you tried put a .to_a in the end of your method call?
Like #messages.to_a ?

I would check the log to see what those 17 queries are, or perhaps clicking on the 17 sql link will show those queries. From there, you may be able to see that you forgot to includes a table that causes the N+1 problem.
EDIT:
As noted in the 'Lazy Loading' section of this site, you can add .all to the end of your query in your controller action to trigger its execution, and prevent the query from lazily executing in your view. As mentioned in my comment, Rails scopes let you build up queries and execute when you use them. To execute, you can call .all, .count, .each or .first. In Rails 4, you can use .load to execute the query in the controller.

This may be so called 'N + 1' problem, it happens due to lazy loading. I can't say for sure without application log. You can use eager loading as described here.

Your query is not well formulated. You should either use includes or joins.
Break down your query into two as follows:
message_ids = Message.joins("LEFT JOIN messages AS m ON messages.id != m.id
AND m.conversation_id = messages.conversation_id
AND messages.created_at < m.created_at")
.where('m.id IS NULL')
.joins("INNER JOIN conversation_participants AS cp
ON cp.conversation_id = messages.conversation_id
AND cp.user_id = #{user_id}")
.order("cp.seen, cp.updated_at DESC")
.limit(5).map(&:id)
messages = Message.includes(:sender).
includes(conversation: [{conversation_participants: :user}]).
where(id: message_ids)

Related

Rails 4: using offset on a randomized list produces unexpected results

...or maybe, i just don't get it.
TL;DR - i get totally randomized results with duplicates and such. No idea why.
Here's the problem - i'm trying to make a rails app generating tournament brackets. The user can select whether he'd like to randomize the team list when generating the bracket, or later assign the teams by himself.
If i choose the latter option and generate the bracket as the ids go, everything is fine.
If i choose to randomize the list, the list itself is fine, but later when assigning teams to particular matches (in a loop), the resulst are completely unorganized and unexpected (at least to me) eg.
instead of
Match 1: Team 1 vs Team 2
Match 2: Team 3 vs Team 4
etc. (as in the normally generated list)
i get something completely random with frequent duplicates like:
Match 1: Team 1 vs Team 1
Match 2: Team 1 vs Team 9
if #seed = 'random'
#team_list = #tournament.teams.order("RAND()")
else
#team_list = #tournament.teams.order(:id)
end
Here's how i assign the teams. The thing that screws everything up seems to be the "offset" part, and i don't get it, why.
#match.team_a = #team_list.offset((2*match_number)-2).limit(1).first
#match.team_b = #team_list.offset((2*match_number)-1).limit(1).first
EDIT:
A sample of #team_list data as requested. That's the randomized one.
#<ActiveRecord::AssociationRelation
[#<Team id: 2, team_name: "test_team_a", reputation: 0, created_at: "2016-08-25 09:29:20", updated_at: "2016-08-26 11:08:21", team_leader_id: 2>,
#<Team id: 9, team_name: "test_team_b", reputation: 0, created_at: "2016-08-30 23:01:17", updated_at: "2016-08-30 23:01:17", team_leader_id: 2>,
#<Team id: 3, team_name: "test_team_c", reputation: 0, created_at: "2016-08-30 22:59:16", updated_at: "2016-08-30 22:59:16", team_leader_id: 2>,
#<Team id: 7, team_name: "test_team_d", reputation: 0, created_at: "2016-08-30 23:00:35", updated_at: "2016-08-30 23:00:35", team_leader_id: 2>,
#<Team id: 5, team_name: "test_team_e", reputation: 0, created_at: "2016-08-30 23:00:07", updated_at: "2016-08-30 23:00:07", team_leader_id: 2>,
#<Team id: 6, team_name: "test_team_f", reputation: 0, created_at: "2016-08-30 23:00:23", updated_at: "2016-08-30 23:00:23", team_leader_id: 2>,
#<Team id: 4, team_name: "test_team_g", reputation: 0, created_at: "2016-08-30 22:59:41", updated_at: "2016-08-30 22:59:41", team_leader_id: 2>,
#<Team id: 8, team_name: "test_team_h", reputation: 0, created_at: "2016-08-30 23:00:46", updated_at: "2016-08-30 23:00:46", team_leader_id: 2>]>
Just to shed some more light on the above answer, when you do #team_list = #tournaments.teams.order("RAND()"), that does this query (presumably in SQL):
SELECT teams.* FROM teams ORDER BY RAND()
(I know teams belongs to tournaments, but you get my point.)
And then when you do #team_list.offset(x).limit(1), that does this query:
SELECT teams.* FROM teams ORDER BY RAND() LIMIT 1 OFFSET x
Because of ActiveRecord relations, each time you do a new thing, you're doing a query. You can test this out in rails console - it'll tell you the queries it does.
EDIT:
If you want to use your code as is, just convert the result of the first query to an array and don't use offset/limit. So:
#team_list = #tournaments.teams.order("RAND()").to_a
...
#match.team_a = #team_list[2 * match_number - 2]
#match.team_b = #team_list[2 * match_number - 1]
This means that you will be loading all of the teams at once (I think) and not lazily loading them as ActiveRecord relations allow you to do.
It is because you are using offset. Each time you change the match_number you end up doing a new query (which includes the call to order_by rand()). You need to make #team_list the result of your database query and then to organize the matches in some other way so you are not hitting the database repeatedly. This will also dramatically improve performance.
Try something like
#team_list.each_slice(2) do |team_a, team_b|
end

Basic ActiveModel methods not working on objects

I asked a question earlier about calling associated model attributes.
Specifically, I asked about trying to display the names of the Vip model instances associated with an event.
Initially I had
<% #organization.events.each do |event| %>
<p>
<table>
<tr>
<td>
<%= event.name %>
</td>
<td>
<%= event.vips.name %>
</td>
</tr>
</table>
</p>
<% end %>
Which I changed to
<%= event.vips.first.name %>
and it worked for a little while. I eventually adjusted it to
<% event.vips.each do |vip| %>
<%= vip.name %>
which also worked for a little while. But when I came back to the computer after taking a break, new event form submissions were no longer displaying the vip's name on the organization show page, even though the database was being updated with the vip_id foreign key.
In fact, when I tried
<%= event.vips.first.name %>
again, I got an error saying "undefined method `name' for nil:NilClass"
I'm really not sure what changed, or what I'm doing wrong. So any help would be appreciated.
Update:
To help clarify the problem, I get the following outputs from the console:
irb(main):005:0> Event.first
Event Load (0.2ms) SELECT "events".* FROM "events" ORDER BY "events"."id" ASC LIMIT 1
=> #<Event id: 1, when: "2015-08-25 00:00:00", organization_id: 1, created_at: "2015-08-25 04:47:43", updated_at: "2015-08-25 04:47:43", name: "John's Event", vip_id: 1>
irb(main):006:0> Vip.first
Vip Load (0.2ms) SELECT "vips".* FROM "vips" ORDER BY "vips"."id" ASC LIMIT 1
=> #<Vip id: 1, name: "John", created_at: "2015-08-25 04:46:23", updated_at: "2015-08-25 04:46:23", organization_id: 1, event_id: nil>
irb(main):007:0> #organization
=> #<Organization id: 1, name: "Test Org", created_at: "2015-08-25 04:46:03", updated_at: "2015-08-25 04:46:03", user_id: 1>
irb(main):008:0> #organization.events.first
=> #<Event id: 1, when: "2015-08-25 00:00:00", organization_id: 1, created_at: "2015-08-25 04:47:43", updated_at: "2015-08-25 04:47:43", name: "John's Event", vip_id: 1>
irb(main):009:0> #organization.events.first.vips
Vip Load (0.2ms) SELECT "vips".* FROM "vips" WHERE "vips"."event_id" = ? [["event_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):010:0>
Your event has no vips. Since event.vips is basically an empty array, calling #first on it returns nil. You then try to call #name on nil. But #name isn't a method of nil, hence the error undefined method 'name' for nil:NilClass.
Normally,
event.vips.first.name will return the error you received if event.vips.first is Nil (which just means that that specific event has no vips, because .first returned Nil or nothing).
In some other times however event.vips.first is a Vip record (which just means that that specific event has at least one Vip).
Since in my first Scenario it returned nil, then nil.name will obviously return an error. However, in my second scenario that it retuned vip, then vip.name will now work as you were expecting it to be.
You may try this if you want to disregard the error being raised: event.vips.first.try(:name)
Your code will work if update the event_id of vip
Vip.first.update_attribute event_id, Event.first.id
Issue happen because we don't have an association between Event.first and Vip.first
btw, I don't think we should use event.vips.first.name because vips can be empty
We can change it a little, ex:
event.vips.first.try(:name)
or
event.vips.map(&:name).join(", ")

How to show values of a instance with line break in Rails console

I start to using pry in a rails console.
When I get a instance of a Rails model, the values are shown without line breaks like this:
pry(#<Class:0x1022f60e0>):1> first
=> #<Article id: 1, name: "What is Music", content: "Music is an art form in which the medium is sound o...", created_at: "2011-08-24 20:35:29", updated_at: "2011-08-24 20:37:22", published_at: "2011-05-13 23:00:00">
from http://railscasts.com/episodes/280-pry-with-rails?view=asciicast
Is there way to show the values with line breaks like this?
Article
id: 1
name: "What is Music"
content: "Music is an art form in which the medium is sound o..."
created_at: "2011-08-24 20:35:29"
updated_at: "2011-08-24 20:37:22"
published_at: "2011-05-13 23:00:00"
You could call .to_yaml on the model instance! It returns a string that's formatted almost exactly like you're requesting it to be.
Here are some examples of to_yaml output:
http://yaml4r.sourceforge.net/doc/page/examples.htm
I would recommend that you install awesome_print.
Add it to your Gemfile:
group :development do
gem 'awesome_print'
end
And install it with bundle install.
Now use ap to print it in the console:
pry(#<Class:0x1022f60e0>):1> ap first
#<Article:0x1022f60e0> {
:id => 1,
:name => "What is Music"
:content => "Music is an art form in which the medium is sound o..."
:created_at => "2011-08-24 20:35:29"
:updated_at => "2011-08-24 20:37:22"
:published_at => "2011-05-13 23:00:00"
}
I think, the below trick will work for you.
arup#linux-wzza:~/Rails/model_prac> rails c
Loading development environment (Rails 4.1.4)
2.1.2 :001 > Comment.first
Comment Load (0.4ms) SELECT "comments".* FROM "comments" ORDER BY "comments"."id" ASC LIMIT 1
=> #<Comment id: 1, value_old: "I am a good Boy.", value_new: "I am a bad Boy.", created_at: "2014-08-02 17:36:14", updated_at: "2014-08-02 18:21:42">
2.1.2 :002 > y Comment.first
Comment Load (0.4ms) SELECT "comments".* FROM "comments" ORDER BY "comments"."id" ASC LIMIT 1
--- !ruby/object:Comment
attributes:
id: 1
value_old: I am a good Boy.
value_new: I am a bad Boy.
created_at: 2014-08-02 17:36:14.249466000 Z
updated_at: 2014-08-02 18:21:42.511522000 Z
=> nil
2.1.2 :003 >

Getting column value in Rails

I have a table named students.
I need to get email field from specific students
for example, I get the following output in IRB
Student.all
Student Load (0.1ms) SELECT "students".* FROM "students"
=> [#<Student id: 1, name: "Bob", grade: 1, email_address: "bob#school.com", created_at: "2014-03-27 08:55:51", updated_at: "2014-03-27 08:55:51">, #<Student id: 2, name: "Neo", grade: 1, email_address: "robert#neo.com", created_at: "2014-03-27 08:56:05", updated_at: "2014-03-27 08:56:05">, #<Student id: 3, name: "Phil", grade: 3, email_address: "phil#school.com", created_at: "2014-03-27 08:56:21", updated_at: "2014-03-27 08:56:21">]
now I need to get email addresses of grade 1 students.
How could I get it?
I solved it,
it can be done by using pluck function:
mail_addresses = Student.where(grade: 1).pluck(:email_address)
mail_addresses.each do|a|
puts a
end
Output :
bob#school.com
robert#neo.com
=> ["bob#school.com", "robert#neo.com"]
Voila!
I think that it will help you.
There are many way to write queries to get email addresses of student with grade 1.
Student.where(:grade => 1).map(&:email)
or
Student.where(:grade => 1).collect(&:email)
or
Student.where("grade = ?", 1).map(&:email)
or
Student.find(:all, :conditions => ["grade = ?", 1]).map(&:email)
you can use select also.
Student.where("grade = ?", 1).select("email").map(&:email)
always use rails query with where instead of find. rails query with `where' is working fast instead of find.

Error with "to_sql" on Rails 3 Beta 4

I'm testing Rails 3 beta 4 on Ruby 1.9.2-head, and when I start a
console and do:
Game.first.to_sql
I get this error:
ArgumentError: wrong number of arguments (0 for 1)
I know it can find the Game record, because when I type:
Game.first
it returns:
=> #<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37",
updated_at: "2010-06-19 11:02:37">
What am I missing? I just want to make the to_sql work in a very simple
case.
.
When you run Game.first you are returning a Game object, not a ActiveRecord::Relation object as you are expecting.
To do what you're trying to do, you'll need to do:
Game.limit(1).to_sql
This lets you run it without to_sql and return the object as you expected it, although it will be in an array, which then you can run .first on it like you wanted anyways.
irb(main):004:0> Game.limit(1).to_sql
=> "SELECT `games`.* FROM `games` LIMIT 1"
irb(main):005:0> Game.limit(1).class
=> ActiveRecord::Relation
irb(main):006:0> Game.limit(1)
=> [#<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37", updated_at: "2010-06-19 11:02:37">]
irb(main):007:0> Game.limit(1).first
=> #<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37", updated_at: "2010-06-19 11:02:37">
When you dig into the source, when you run .first on an ActiveRecord::Relation it runs the following (which is the same as I showed you):
def find_first
if loaded?
#records.first
else
#first ||= limit(1).to_a[0]
end
end

Resources