Rails,如何避免关联中的总数(count,size,counter_cache)的"N + 1"查询?

15 ruby activerecord ruby-on-rails relationship ruby-on-rails-4

我有这些型号:

class Children < ActiveRecord::Base
    has_many :tickets
    has_many :movies, through: :tickets
end


class Movie < ActiveRecord::Base
    has_many :tickets
    has_many :childrens, through: :tickets
    belongs_to :cinema
end


class Ticket < ActiveRecord::Base
    belongs_to :movie, counter_cache: true
    belongs_to :children
end


class Cinema < ActiveRecord::Base
    has_many :movies, dependent: :destroy
    has_many :childrens, through: :movies
end
Run Code Online (Sandbox Code Playgroud)

我现在需要的是在"电影院"页面中我想打印儿童电影的总和(数量,大小?)仅仅是为了电影院的电影,所以我写了这个:

  • 在cinemas_controller.rb中:

@childrens = @cinema.childrens.uniq

  • 在电影院/ show.html.erb中:

<% @childrens.each do |children| %><%= children.movies.size %><% end %>

但显然我有一个子弹宝石警告我Counter_cache我不知道在哪里放这个counter_cache因为电影的不同id.

而且没有counter_cache我所拥有的并不是我想要的东西,因为我想要计算那个电影中有多少孩子从那个电影院的门票中取出它们.

如何?

UPDATE

如果在我看来我使用此代码:

<% @childrens.each do |children| %>
  <%= children.movies.where(cinema_id: @cinema.id).size %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

宝石子弹不要说我什么,每个人都正常工作.

但我有一个问题:由于视图中的代码,这种查询数据库的方式更加繁重?

小智 6

这可能对你有帮助.

@childrens_count = @cinema.childrens.joins(:movies).group("movies.children_id").count.to_a
Run Code Online (Sandbox Code Playgroud)


XML*_*yer 5

您可以使用includes来提前加载所有关联.例如:

@childrens = @cinema.childrens.includes(:movies).uniq
Run Code Online (Sandbox Code Playgroud)

这将在控制器中加载所有孩子的电影,防止视图需要访问循环中的数据库.


Igo*_*dov 5

您可能会同意,电影属于一个孩子的数量等于他们购买的门票数量.这就是为什么你可以只是缓存门票的数量并在电影院#show上显示它.您甚至可以创建一种方法来使其更清晰.

class Children < ActiveRecord::Base
  has_many :tickets
  has_many :movies, through: :tickets

  def movies_count
    tickets.size
  end
end

class Ticket < ActiveRecord::Base
  belongs_to :movie, counter_cache: true
  belongs_to :children, counter_cache: true
end

class Movie < ActiveRecord::Base
  belongs_to :cinema
  has_many :tickets
  has_many :childrens, through: :tickets
end

class Cinema < ActiveRecord::Base
  has_many :movies, dependent: :destroy
  has_many :childrens, through: :movies
end
Run Code Online (Sandbox Code Playgroud)

然后:

<% @childrens.each do |children| %><%= children.tickets.size %><% end %>
Run Code Online (Sandbox Code Playgroud)

要么

<% @childrens.each do |children| %><%= children.movies_count %><% end %>
Run Code Online (Sandbox Code Playgroud)

但是如果你想显示每部电影的门票数量,你肯定需要考虑以下事项:

@movies = @cinema.movies
Run Code Online (Sandbox Code Playgroud)

然后: <% @movies.each do |movie| %><%= movie.tickets.size %><% end %> 既然你有belongs_to :movie, counter_cache: true,tickets.size就不会进行计数查询.并且不要忘记添加tickets_count列.关于counter_cache的更多信息......

PS只是一个注释,根据惯例,我们将模型命名为Child,将协会命名为Children.


Her*_*aña 5

实际上比其余的解决方案简单得多

您可以使用lazy loading

在你的控制器中:

def index
  # or you just add your where conditions here
  @childrens = Children.includes(:movies).all 
 end
Run Code Online (Sandbox Code Playgroud)

在您看来index.hml.erb

<% @childrens.each do |children| %>
  <%= children.movies.size %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

如果您使用上面的代码,则不会进行任何额外的查询size,但如果您使用count,您将面临select count(*)n + 1 次查询