Does MongoID do a separate query for .count(true)? - ruby-on-rails-3

I have a ruby on rails 3 project in which I query for a certain number of objects by using a .limit(3) . Then, in my view, I loop through these objects. After that, if there are 3 objects in the view, I display a "load more" button. Here is the view code:
<% #objects.each do |object| %>
<%= render object._type.pluralize.underscore + '/teaser', :object => object %>
<% end %>
<% if #objects.size(true) == 3 %>
#load more link here
<% end %>
The size(true) is passed a boolean to ensure that mongoID takes into account the .limit and .offset on my query (otherwise it returns the total number of objects that matched, regardless of the limit / offset). Here are the relevant development log lines:
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['objects'].find({:deleted_at=>{"$exists"=>false}}).limit(3).sort([[:created_at, :desc]])
#some rendering of views
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['$cmd'].find({"count"=>"objects", "query"=>{:deleted_at=>{"$exists"=>false}}, "limit"=>3, "fields"=>nil})
My question is: does MongoID do a separate query for my #objects.size(true)? I imagine the ['$cmd'] might indicate otherwise, but I'm not sure.

I don't think so, there was a pull request month ago to add aliases for :size, :length to :count to avoid re-running queries. You can check that.

Related

optimize sql query rails

On posts index page I list all posts this way:
posts_controller.rb
def index
#posts = Post.includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
35 posts per page
When I first load the page in dev env,
rack-mini-profiler shows this time: 1441.1 ms
after a few reloads: ~700 ms
Can I somehow decrease this time and number of sql requests?
Here're rmp images if it helps:
You could decrease the number of sql queries by:
including user as well as comments, since you seem to be using that when displaying the gravatar
changing post.comments.count to post.comments.size
While size, count, length are synonymous for arrays, for active record relations or associations they are not the same:
length loads the association (unless it is already loaded) and returns the length of the array
count does a select count(*) query whether the association is loaded or not
size uses length if the association is loaded and count if not.
In your case the comments association is loaded, but because you are using count, it's not actually used
Further, you don't actually seem to be using the comments collection for anything other than printing the number of records. If that's indeed the case, use counter_cache (4.1.2.3) instead of querying for the comments (the number of comments will be available in the parent record Post).
Also consider a client side alternative to time_ago_in_words. It will also help if you later decide to cache the entire section/page.
And finally retrieve only the fields you're going to use. In this case, I can imagine the Post contains a large amount of text for the content and it's not used anywhere (but still needs to be transmitted from the DB).
Adding an index on the reference column (comments in your case) might help.
add_index :posts, :comment_id

ActiveRecord Joins

Ok, so, if I do a User.joins(:session_users), I only get the attributes of users table.
How do I get the attributes of both tables in ActiveRecord way, i.e., not SQL?
EDIT ONE
Ok, based on the first answer, I'm trying to have it displayed.
So, this is the method written in Users Controller
def blah
#users = User.includes(:session_users)
#users.each do |user|
user.session_users
end
end
Then I have this in the users view blah.html.erb
<%= #users.session_users %>
And this in the routing section:
match "/users/blah" => "users#blah"
I think you want includes instead of joins. See http://railscasts.com/episodes/181-include-vs-joins for more info. This should fetch columns for both,
users = User.includes(:session_users)
users.each do |user|
user.session_users
end
Note, this still performs 2 SQL queries.
Edit
Updated answer assumes that a user has_many :session_users
Routes:
# config/routes.rb
get '/users/blah' => 'users#blah'
Controller:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def blah
#users = User.includes(:session_users)
end
end
View:
# app/views/users/blah.html.erb
<% #users.each do |user| %>
<%= user.name %> // assumes user has a name attribute
<% user.session_users.each do |session_user| %>
<%= session_user.attributes %> // prints all the attributes
<%= session_user.created_at %> // assumes the user has a created_at attribute
<% end %>
<% end %>
If you really need to add fields from a joined table to the yielded objects, you can add select:
User.joins("INNER JOIN stolen_passwords
ON users.social_security_number=stolen_passwords.ssn")
.select("*").find_each do |user|
logger.debug {
"A #{user.full_name} has a password hash #{user.password_hash}"
}
end
Here imaginary user.full_name is an instance method of User and user.password_hash comes from the stolen_passwords table. You could also limit the queried/returned fields by listing them in the call to select explicitly.
We sometimes use this in rake tasks that enrich the database from or compare it against third party data sources: we would join our tables with provided tables and generate a 'side-by-side' CSV with columns from both. Plain SQL would work just as well, but going via active record often allows to reuse familiar scopes, or methods that perform calculations in ruby.
Caveat
Unfortunately the fields coming from the joined tables will not be cast to appropriate ruby types, they will all be strings (which is especially logical if using SQL string for the joins). But it is easy to cast the joined attributes with something like:
module CastJoinedColumns
def cast_joined_columns joined_record
columns_hash.each do |column_name, column|
if joined_record.attributes.include?(column_name)
joined_record[column_name] = column.type_cast(joined_record[column_name])
end
end
end
end
This module is meant to be extended into a model appearing on the right side of the join and the method be called with a joined record. It might misbehave and should be improved for the cases where the same column name appears in multiple tables, but is an ok starting point (works perfectly for us with third party data sources using column names guaranteed not to clash with our own).

Rails List record and count for each

I have a model called Note. Each note belongs_to :call_reason. And call_reason has_many :notes.
What I want to do in a view is display a list of call_reasons and a total count of each next to it so we can see what the most popular call reasons are.
Here's what I have so far:
dashboard_controller:
def index
#notes = Note.all
end
dashboard view:
<% #notes.each do |n| %>
<%= n.call_reason.reason %>
<% end %>
This lists all notes' call_reasons.
I'm stumbling on how to list each call_reason once with a total count next to it. What I have now just lists all the call_reasons per note which is a mess. I think I could scope this out somehow or change the instance variable but I'm having a hard time getting it right.
Any thoughts?
Since you want to list call reasons, you should start with that:
def index
#call_reasons = CallReason.all
end
Then in your view you can do this:
<% #call_reasons.each do |call_reason| %>
<%= call_reason.reason %> <%= call_reason.notes.count %>
<% end %>
Note that this will perform a query for every call reason in your database. To prevent this you can use a counter cache. Check out the section on counter cache on Rails Guides too.

Editing multiple records at once - how to update without IDs

Still new to Rails. I'll try to provide as much detail as possible.
I have a form that lets me update multiple records at one time.
It's based off the 'Editing Multiple Individually' Railscast episode.
<%= form_tag(auction_clerk_path(#auction), :method => :put) do %>
<% #lots.each do |lot| %>
<%= fields_for "lots[]", lot do |f| %>
<%= f.number_field :sale_price %>
<% end %>
<% end %>
<% end %>
(Simplified to just include a single input for each instance)
An Auction contains multiple Lots (items for sale).
The auction_clerk_path is the route I'm using to just show all lots on one auction.
Everything is working just fine... until I use try to customize my lot paths...
I've added the following to my lot.rb file to be able to use:
/auctions/:auction_id/lots/:lot_number
instead of /auctions/:auction_id/lots/:id
def to_param
lot_number
end
So, in the form mentioned earlier, the fields render with name="lots[12][sale_price]" where 12 is the id.
However with the to_param change, now the fields render with name="lots[1][sale_price]" where 1 is the lot_number.
When I save, the submitted parameters are lot_numbers instead of ids.
So obviously when it tries to update, it won't find the correct records.
My method definition looks like this:
def save_clerking
#updated_lots = Lot.update(params[:lots].keys, params[:lots].values).reject { |l| l.errors.empty? }
if #updated_lots.empty?
flash[:notice] = "Lots updated"
redirect_to auction_clerk_path(#auction)
else
render :action => "clerk"
end
end
I either need to change my method definition to lookup by lot number, or change the form to somehow output IDs in the first place... but I don't know how.
Any help is appreciated. Thanks!
Fixed this through some help on another question.
I changed my method def to
#updated_lots = []
params[:lots].each do |lot_number, attributes|
lot = Lot.where("lot_number = ? AND auction_id = ?", lot_number, params[:auction_id]).first
if lot.update_attributes(attributes)
#updated_lots << lot
end
end
You could fetch the ids by lot number in the controller action and feed those to the update method instead of the params keys.

Running a SQL query in rails

I have a rails application where I have to subtract 2 time stamps and display the result on the webpage
I have used the following sql statement and I am getting the results.
select (julianday(resolutions.created_at)-julianday(tickets.created_at))*24 from tickets,resolutions
the value of tickets.created_at is 2010-04-05 18:59:02 which is a time stamp and value of resolutions.created_at is 2010-04-08 08:10:33
Now where do I put this sql so that I can be seen in my webpage.
I tried it in the views page with the following but nothing showed up:
<% #sql = "select (julianday(resolutions.created_at)-julianday(tickets.created_at))*24 from tickets,resolutions" %>
<% #t=(ActiveRecord::Base.connection.execute(#sql)) %>
So how do I display it on my webpage?
when I perform the above I get the output printed on the webpage as
061.1919444389641(julianday(resolutions.created_at)-julianday(tickets.created_at))*2461.1919444389641
only 61.1919444389641 is supposed to be printed but the query statement is also getting printed.
You should put your custom SQL in your model class, definitely not in the view.
In your controller
#tickets = Tickets.find(
:all,
:joins => :resolution,
:select => '(julianday(resolutions.created_at)-julianday(tickets.created_at))*24 AS interval'
)
In your view
<% #tickets.each do |ticket|%>
<%= ticket.interval %>
<% end %>
Generally you would put this logic in one of your models though, like this:
In your Tickets Model
def time_to_resolve
resolution.created_at - created_at
end
To reduce the number of queries when iterating over multiple queries you would use this:
Tickets.find(:all, :include => :resolution)