Reputation: 1798
Let's consider the following relationships (3 models):
class Author < ActiveRecord::Base
has_many :authorships
has_many :papers, :through => :authorships
end
class Paper < ActiveRecord::Base
has_many :authorships, inverse_of: :paper, :dependent => :destroy
has_many :authors, :through => :authorships
accepts_nested_attributes_for :authorships, :allow_destroy => true
end
class Authorship < ActiveRecord::Base
belongs_to :paper
belongs_to :author
validate: rank, presence: true # position of the author
end
As you can see, I use a lot has_many
and also :through
. When I browse paper.html.erb
page which basically shows all the papers and corresponding authors, it slows down the system a lot. Right now I have roughly 400 papers and 2000 authorships, and it takes 3 seconds to load the page, plus, from the log I saw RoR made a lot of SQL queries, the majority of the queries are to retrieve authors. I was wondering this is a typical thing in RoR. Thanks for your help.
Upvotes: 2
Views: 1000
Reputation: 510
This sounds like more of an optimization problem. To answer your original question, has_many :through by itself does NOT cause any noticeable difference. You would have the exact same problem if you only only had two tables for Paper
and Authorship
.
Based on what you reported about your logs, I assume your code looks something like this:
# Controller:
@papers = Paper.all
# View:
<% @papers.each do |paper| %>
<%= paper.authors.join(', ') %>
<%# other code... %>
<% end %>
What rails does is for every time you call paper.authors
, it will run a query to fetch the authors associated with that paper. If you have 400 papers, rails will run 400 queries. This is called the n+1 query problem, where n queries are run for each record, along with the first query to get the initial records. Rails has a nice guide on how to tackle this issue: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
You basically want to avoid running a query every time you want to get the authors for a paper. You could instead load your papers in the controller like this: @papers = Paper.includes(:author).all
. This would load all the papers along with their associated authors and only run 2 queries: one for all the papers, and another for all the authors associated with those papers.
However, be careful with overusing .includes
because it could also slow down your loading time once you have too many records. Rails also takes time to create each Paper
and each Author
object in memory; it would currently be creating 2,400 objects based on your database records. After you get to a certain number of records, you should look into adding pagination or search functionality instead of loading tens of thousands of records on a single page.
Upvotes: 2