Essentail Rails Design Pattern

Write Good Rails Code

Counter Cache

#TODO

1
2
-<h1>Listing posts</h1>
+<h1>Listing posts - Total <%= @board.posts.size %> posts </h1>

使用 @board.posts.size 機制直接算出 boards 下有幾篇文章,產生的 SQL query 是使用 SELECT COUNT(*)

1
SQL (0.1ms)  SELECT COUNT(*) FROM "posts" WHERE ("posts".board_id = 1)

若在 posts 數量相當大時,存取 boards#index 將會造成沈重的負擔。我們應該找個方法把這個數值 cache 起來。

Solution 1

  • 應該在 board model 新增一個 integer 欄位紀錄有多少篇文章
  • 每當文章被 新增 / 刪除之際,在 controller +1/-1 文章數量

Solution 2

這樣要寫太多行 code 了,事實上 Rails 內建了一個 counter_cache 的機制。當文章建立或刪除時,會自動進行 +1/-1 功能。

修改 app/models/post.rb

1
2
-  belongs_to :board
+  belongs_to :board, :counter_cache => true
1
$ rails g migration add_posts_count_to_board

修改 db/migrate/20110529014114_add_posts_count_to_board.rb

1
2
3
4
5
6
7
8
9
class AddPostsCountToBoard < ActiveRecord::Migration
  def self.up
    add_column :boards, :posts_count , :integer, :default => 0
  end

  def self.down
    remove_column :boards, :posts_count
  end
end

之後 @board.posts.size 就是直接去撈 posts_count 欄位的數字了。

注意事項

若 boards 內早已有文章,直接加上 counter_cache => true 會導致抓到的 size 是初始值 0。建議先開設 posts_count 欄位,手動跑個計算,先將正確統計數字加總後回存 posts_count,再宣告 counter_cache => true

Comments