【问题标题】:Avoiding individual database calls for count避免单独的数据库调用计数
【发布时间】:2011-03-19 21:51:00
【问题描述】:

我的模型如下所示:

class Movie < ActiveRecord::Base
  attr_accessible :title, :year, :rotten_id, :audience_score,
    :critics_score, :runtime, :synopsis, :link, :image

  has_many :jobs, :dependent => :destroy
  has_many :actors, :through => :jobs
end

class Actor < ActiveRecord::Base
  attr_accessible :name
  has_many :movies, :through => :jobs
  has_many :jobs, :dependent => :destroy
end

class Job < ActiveRecord::Base
  attr_accessible :movie_id, :actor_id

  belongs_to :movie
  belongs_to :actor
end

当我显示我的演员索引时,我想显示每个演员出演的电影数量。我可以使用@actor.movies.count 执行此操作,但这会生成一个 SQL查询each actor。假设有 30 个演员,这将导致除了最初的 30 个额外查询。

有没有办法在最初的Actor.all 电话中包含每个演员参与的电影数量?从而只需一个电话即可完成工作。如果这是按所述计数排序的额外奖励。

更新: 提供的所有答案都很有帮助,虽然它在某些时候变成了一些肮脏的比赛,但效果很好。我把你所有的建议混为一谈。我在我的 Actor 模型中添加了 movies_counter 列。在我的工作模型中,我添加了belongs_to :actor, :counter_cache =&gt; :movies_counter。这非常有效,并且在我创建或销毁电影时会自动更新,无需我添加任何进一步的代码。

【问题讨论】:

    标签: sql ruby-on-rails ruby counter-cache


    【解决方案1】:

    正如@Sam 所注意到的,您应该将新列添加到actorsmovies_counter

    rails g migration add_movies_counter_to_actor movies_counter:integer
    

    现在您可以编辑迁移

    class AddMoviesCounterToActor < ActiveRecord::Migration
      def self.up
        add_column :actors, :movies_counter, :integer, :default => 0
    
        Actor.reset_column_information
        Actor.all.each do |a|
          a.update_attribute :movies_counter, a.movies.count
        end
      end
    
      def self.down
        remove_column :actors, :movies_counter
      end
    end
    

    然后运行它

    rake db:migrate
    

    那么你应该添加两个回调:after_saveafter_destroy

    class Movie < ActiveRecord::Base
      attr_accessible :title, :year, :rotten_id, :audience_score,
        :critics_score, :runtime, :synopsis, :link, :image
    
      has_many :jobs, :dependent => :destroy
      has_many :actors, :through => :jobs
    
      after_save :update_movie_counter
      after_destroy :update_movie_counter
    
      private
      def update_movie_counter
        self.actors.each do |actor|
          actor.update_attribute(:movie_count, actor.movies.count)
        end
      end
    end
    

    那你可以拨打some_actor.movies_counter

    【讨论】:

    • 您正在重新实现 counter_cache 功能。
    • 这里是has_many:通过关联,没有counter_cache选项
    • 你说得对,这看起来是最好的解决方案,尽管你可能应该使用 Actor.increment_counter 而不是每次都进行完整计数。
    • @Adam,可以递增或递减(删除时)。所以我们可以用increment_counterdecrement_counter为每个回调编写两个方法,或者我们可以使用这个解决方案,它有一个额外的sql请求,但如果项目不是高负载,这并不重要
    【解决方案2】:

    在您的 Actor 表中添加一个名为“movie_count”的列。然后在您的 Actor 模型中添加一个回调来更新该列。

    class Movie < ActiveRecord::Base
      has_many :actors, :through => :jobs
      before_save :update_movie_count
      def update_movie_count
         self.actor.update_attribute(:movie_count, self.movies.size)
      end
    end
    

    这样您只需更新一个整数,而不是调用所有记录。

    【讨论】:

    • 我会在我的 Job 模型中执行 update_movie_count,对吗?由于一部电影有几个演员,而且直到创建一个 Job 才真正增加计数。但是,是的,我明白了。当您像 before_save 一样了解实用程序时,这是一个简单的解决方法。谢谢
    • @sam 你正在重新实现 counter_cache 功能。
    • @sam 实际上在这里不太行,所以你必须重新实现它。
    • @亚当·拉塞克。除了@flOOr 指出你删除的答案是错误的,因为这是一个has_many,所以我没有重新实现counter_cache。
    • :counter_cache 不起作用,如前所述...但是 :counter_sql 的某些实现会起作用吗?
    猜你喜欢
    • 2020-10-09
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 2015-01-07
    • 2017-09-08
    • 1970-01-01
    相关资源
    最近更新 更多