2013年9月27日 星期五

Rails 的 Counter Cache 機制

雖然很基本,但是一定要做一下記錄。
我們先想像一個情況:你目前的專案中,有user及comment兩種model:

class User < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :user
end

如果在一個頁面需要將使用者列表,並同時顯示他所擁有的回應數時,我們直覺上會在迴圈中,一個個進行user.comments.count,並同時產生一個db query,這樣的情況就是經典的「N+1」問題。


讓我們把counter cache住吧!

在系統架構上還滿常用到的一個手法是,將「讀取機會比寫入機會大很多」的數值記錄起來,例如這次會講到的counter cache,就是乾脆在user資料表上開一個comments_count欄位,在新增或刪除comment的同時,記錄他目前所擁有的comment數。
如果你對rails有一定程度的了解,應該會想到使用ActiveRecord callback的機制在新增或刪除comment後進行user.comments_count的更新。但是也因為這是一個還算常見的作法,Rails本身就內建了這個功能,能夠讓我們所做的工減少很多。


進入正題

1. 新增counter欄位

首先我們要先在user資料表中開一個專門儲存comment數的欄位,由於rails「Convention over configuration」的核心精神,這個欄位應該被命名為 [resource複數]_count ,否則rails可能會認不得他。以這個例子來說,就是要新增一個叫 comments_count 的欄位,並且預設值為0。新增欄位的過程就不贅述了。


2. 修改relation選項

接下來要做的是在comment.rb中的relation上加上一個選項::counter_cache ,告知Rails這個relation是要做counter的同步動作的。程式碼變成:

class Comment < ActiveRecord::Base
  belongs_to :user, :counter_cache => true
end

如果目前的專案增加這個欄位時,並沒有存在任何資料,也就是所有的user所擁有的comment是0,這樣的情況下其實這樣已經ok了。但如果之前就已經有一些user有創造comment,這時我們就要做counter的更新。


更新counter cache

可能有些人會想說,直接遍尋所有user,讓他們的 comments_count = user.comments.count 就好。
很不幸的,Rails對於counter欄位有保護機制,我們不能這樣直接修改他,而必須使用 update_counters(id, counters) 或 reset_counters(id, *counters) 兩種方法,以reset_counter來說,migration的程式碼就像這樣:

User.find_each do |user|
  User.reset_counters user.id, :comments
end



PostgreSQL 新增使用者及設定「唯讀」權限

新增user

以下是新增使用者之指令及相關選項:

Usage:
  createuser [OPTION]... [ROLENAME]

Options:
  -c, --connection-limit=N  connection limit for role (default: no limit)
  -d, --createdb            role can create new databases
  -D, --no-createdb         role cannot create databases
  -e, --echo                show the commands being sent to the server
  -E, --encrypted           encrypt stored password
  -i, --inherit             role inherits privileges of roles it is a
                            member of (default)
  -I, --no-inherit          role does not inherit privileges
  -l, --login               role can login (default)
  -L, --no-login            role cannot login
  -N, --unencrypted         do not encrypt stored password
  -P, --pwprompt            assign a password to new role
  -r, --createrole          role can create new roles
  -R, --no-createrole       role cannot create roles
  -s, --superuser           role will be superuser
  -S, --no-superuser        role will not be superuser
  --help                    show this help, then exit
  --version                 output version information, then exit

Connection options:
  -h, --host=HOSTNAME       database server host or socket directory
  -p, --port=PORT           database server port
  -U, --username=USERNAME   user name to connect as (not the one to create)
  -w, --no-password         never prompt for password
  -W, --password            force password prompt

If one of -d, -D, -r, -R, -s, -S, and ROLENAME is not specified, you will
be prompted interactively.

其中比較重要的是 -P 這個選項,在建立使用者的同時為他設定密碼。如最後一行(紅字)提到的,「創建DB」(d)、「創建user」(r)、「是否為最高權限」(s)。


為使用者設定唯讀權限


在postgres中,並沒有所謂的「唯獨權限」,所以我們必須透過修改使用者的預設參數來達到這個目的:

alter role digoal set default_transaction_read_only=true;



開放 Tables 和 Sequences 權限給使用者


特定 Table:
GRANT SELECT ON table_name TO role_name;

所有 Tables:
GRANT SELECT ON ALL TABLES IN SCHEMA schema_name(通常是public) TO role_name;

所有 Sequences:
GRANT SELECT ON ALL SEQUENCES IN SCHEMA schema_name TO role_name;

Reference

德哥@Digoal的博格:http://blog.163.com/digoal@126/blog/static/1638770402011111274336235/