在一個 Rails 專案,常需要使用一些設定值。最常見的就是各種 API 使用的 key。通常我們會把這些設定獨立起來放在一個 yaml 檔案裡面,避免紀錄在 git 的版本管理系統裡面。個人使用了 Settingslogic ,覺得十分合用,不過在一些細節上我想要推廣一些撇步:

讓設定檔成為能獨立運作的 class

Rails 的啟動是十分花時間的,在 Ruby 1.9 下面通常要等個幾十秒。有時候有些可以獨立運作的套件為了使用一個 Rails 下面的設定值,就得花時間啟動整個 Rails 才能取得設定。要避免這種情形,Settingslogic 我是這樣寫的:

# config/app_config.rb 檔案
require 'settingslogic'
class AppConfig < Settingslogic
  source File.dirname(__FILE__) << "/app_config.yml"
end

首先是手動 require Settingslogic,然後其 yaml 檔案則是使用相對路徑作參照(與 app_config.rb 同一個目錄)。這樣子在其他環境就能簡單 require "./path/to/app_config" 讀取設定了。

我是把我的 app_config 放在 config 資料夾下面,然後在 config/initializers/ 下開個 app_config.rb 檔案 require 我的 app_config

我的 config/app_config.yml 長這樣:

exception_notification:
  recipients:
    - lulz@example.com
    - lols@example.com
sendgrid:
  user_name: 'foobar'
  password: '2000'
rollbar:
  access_token: dghgkalt4k5439fkgio43pb2

當然這個檔案不會放進版本控制。會放進版本控制的是 config/app_config.yml.example 檔案,裡面只有放 key 沒有值,讓大家知道需要設定什麼。我也沒有用 namespace 之類的東西,反正一個地方就一個設定,依照 exmaple 照樣畫葫蘆即可。

把網域資訊存進設定檔中

一個網站的網域資訊是很多設定不可或缺的一部分。比如說我使用 capistrano_nginx 就常常需要像是 .example.com 的設定。網域設定又會因為環境的不同而改變。 production 跟 staging 各會有不同的設定。所以我覺得把網域寫在 config 中是最恰當的。

# app_config.rb 中
  # returns domain string
  # pass in false for segment key to hide that segment, e.g. `protocol:false`
  def self.domain(params = {})
    params = {protocol:'http'}.merge(params)
    params = self.get('domain_setting').to_hash.merge(params)

    url = ''
    url << params[:protocol].clone << '://' if params[:protocol]
    url << params[:subdomain].clone << '.' if params[:subdomain]
    url << params[:domain]
    url << ':' << params[:port].to_s if params[:port]
    url
  end
# app_config.yml 中
# 注意我的 key 是 symbol 喔
domain_setting:
  :domain: 'lvh.me'
  :port: 3000

這樣子,我在就可以呼叫下列指令來取得不同的網域資訊:

# irb 中
AppConfig.domain #=> "http://lvh.me:3000"
AppConfig.domain(protocol:false) #=> "lvh.me:3000"
AppConfig.domain(protocol:false, subdomain:'admin') #=> "admin.lvh.me:3000"

這裡使用的參數跟 url_for 類似。

我在 production.rb 跟 staging.rb 當然也就這樣改了:

config.asset_host = AppConfig.domain(subdomain:'assets')
config.action_mailer.default_url_options = { :host => AppConfig.domain(protocol:false) }

像是 staging/development 這種地方,把可能變動的網域給寫死在 rb 檔然後存進去 git 中本來就怪怪的,所以我都用設定檔來作設定。另一個好處是,想要修改網域就只要修改一個地方,徹底貫徹DRY懶人精神!

最後,記得也要準備一份只有 key 沒有 value 的 app_config.yml.example ,放入版本管理中,給以後的人作參考喔。