如何避免来自View in Rails的数据库调用?

时间:2013-11-28 22:49:28

标签: ruby-on-rails erb rails-activerecord

我们假设这个例子:

型号:

class User < ActiveRecord::Base
  has_many :posts
end

控制器:

def index
  @users = User.all
end

查看:

<ul>
  <% @users.each do |u| %>
    <li>
      Username: <%= u.username %><br />
      <%= pluralize(u.posts.count, "post") %>
    </li>
  <% end %>
</ul>

根据我的理解(通过在命令行中查看WEBrick),它不会为u.username执行数据库调用,但为每个u.posts.count执行在循环中循环。我想避免这种情况,所以我将“posts”存储在控制器中的实例变量中(例如@posts = Post.all)并将u.posts.count替换为@posts.where(:user_id => u.id).count,但它仍然会执行每个周期的数据库调用。应用程序是否将所有信息都存储在控制器的@posts数组中?

注意:这些都不是特定于我的情况(我没有显示用户列表);我只是用这个作为例子。

2 个答案:

答案 0 :(得分:3)

处理此问题的一种方法是counter_cache。您有效地向posts_count模型添加了新的User属性,并在每次修改用户帖子时更新它。 Rails使counter_cache关联选项变得容易:

class User < ActiveRecord::Base
  has_many :posts, counter_cache: true
end

使用该设置,调用u.posts.size将返回存储在用户模型中的计数,而不是点击数据库(确保使用size,而不是count)。

有关:counter_cache选项的详细信息,请查看Rails Association Basics guide (section 4.1.2.3)This blog post介绍了如何实际添加一个,包括迁移和初始化值。

你可以这样做的第二种方法是加载你控制器方法中的所有帖子,就像你想象的那样,但它可能比简单的加载它们更加简洁(@users = User.includes(:posts).all)。它不适合您的原因是因为您使用count而非size - count始终使用SELECT COUNT(*)语句命中数据库,而size更聪明,如果可能的话,将避免命中数据库。 (这就是为什么你在使用size时也要确保使用counter_cache。)

这也是有效的,但是如果你实际上不打算以任何方式使用这些帖子,最好避免将它们全部从数据库中删除,因此counter_cache方法的吸引力。

答案 1 :(得分:0)

另一种选择是使用subselect进行SQL查询以进行计数。以下查询假定User.id = Post.user_id

@users = User.select("u.username").
select("count(p.id) AS post_count").
from("users u").
joins("LEFT JOIN posts p ON u.id = p.user_id").
group("u.id").
all

然后在你看来:

<% @users.each do |u| %>
<li>
  Username: <%= u.username %><br />
  <%= pluralize(u[:post_count], "post") %>
</li>
  

这应该是你输出到控制台:

Started GET "/reports" for 127.0.0.1 at 2013-11-28 16:33:31 -0700
Processing by ReportsController#index as HTML
  User Load (0.8ms)  SELECT u.username, count(p.id) AS post_total FROM users u LEFT JOIN posts p ON u.id = p.user_id GROUP BY u.id
  Rendered users/index.html.erb within layouts/application (0.5ms)
Completed 200 OK in 10ms (Views: 7.3ms | ActiveRecord: 1.0ms)