Tạo Configuration Object trong Ruby
Bài đăng này đã không được cập nhật trong 7 năm
Ta có thể thay đổi cách một ứng dụng hoạt động một cách dễ dàng mà không phải động vào source code thông qua việc chỉnh sửa một sỗ config được thiết lập sẵn.
Việc thay đổi cách hoạt động của code thông qua config mang lại nhiều lợi ích cho chúng ta. Nó hạn chế việc thay đổi source code mà vẫn đảm bảo tính mềm dẻo dễ thay đổi của hệ thống.
Vậy làm cách nào để ta tạo một config object ? Hãy đi vào bài viết.
Một Configuration Object đơn giản
Nhiều ứng dụng không cần đến một hệ thống configuration phức tạp mà đơn giản chỉ cần sử dụng một object Ruby đơn giản nhất đó là Hash
.
Một kiểu dữ liệu key-value là một khái niệm đơn giản nhất mà một Configuration Object cần. Ruby Hash
là nền tảng chính để implement một Configuration Object.
config = {}
config[:my_key] = :value
config[:my_key]
# => :value
Một cách implement phức tạp hơn đó là sử dụng một object thuần của Ruby và sử dụng metaprogramming để tạo các method động có thể trả về các giá trị tương ứng với các key truyền vào.
Một Configuration Object đơn thuần trong Ruby mà bao gồm cả 2 cách implement trên đó là OpenStruct
. Object này sử dụng hash bên trong implement của nó và nó cũng có thể được sử dụng để định nghĩa các method mới bên trong nó:
require 'ostruct'
config = OpenStruct.new
config.my_key = :value
config.my_key
# => :value
# config[:my_key]
# # => :value
Ruby Hash
sẽ luôn trả về nil
nếu như key chưa được định nghĩa bên trong nó. Việc này là bình thường đối với những trường hợp đơn giản, tuy nhiên nó là một cách làm không hướng đối tượng lắm. Đôi khi ta lại muốn nó trả về một giá trị mặc định nào đấy. Hash
cung cấp cho chúng ta một method là default_prc
để ta có thể định nghĩa việc xử lý nếu như key chưa được định nghĩa trong Hash
Ví dụ: tạo một Hash
mà có thể tạo các key lồng nhau trong một lần tạo
module MyProject
def self._hash_proc
->hsh,key{
hsh[key] = {}.tap do |h|
h.default_proc = _hash_proc()
end
}
end
def self.config
@config ||= begin
hsh = Hash.new
hsh.default_proc = _hash_proc()
hsh
end
end
end
MyProject.config
# => {}
MyProject.config[:apple][:banana][:cherry] = 3
MyProject.config
# => {:apple=>{:banana=>{:cherry=>3}}}
Tuy nhiên, với phương pháp này, ta chỉ đơn giản là thay nil
bằng một Hash
rỗng khác. Phương pháp này chỉ phù hợp với những ứng dụng đơn giản và không phức tạp. Ta cần phải xử lý những giá trị mặc định này khác nhau tùy theo từng tính chất ứng dụng.
Ngoài ra, trong Rails còn cung cấp thêm cho chúng ta một loại Hash
đó là HashWithIndifferentAccess
. Nó cho phép ta sử dụng cả String
và Symbol
như là cùng một key.
config = HashWithIndifferentAccess.new
config["asdf"] = 4
config[:asdf]
# => 4
Configuration Object trong Rails
Rails có một module hỗ trợ ta tạo các Configuration Object đó là ActiveSupport::Configurable
:
class DatabaseConfig
include ActiveSupport::Configurable
config_accessor :database_api do
DummyDatabaseAPI.new
end
end
Trong đoạn code trên, config_accessor
tạo một hàm config database_api
cho DatabaseConfig
và sử dụng một object DummyDatabaseAPI
làm giá trị mặc định. Điều này giúp ta có thể định nghĩa một số behavior mặc định nếu như chưa có một database cụ thể nào. Nếu ta cần thiết lập một database cụ thể thì chỉ cần gọi hàm database_api=
.
Lưu lại các Configuration
Để lưu các Configuration thì ta có thể sử dụng file, database, biến môi trường...
Ruby hỗ trợ config thông qua file YAML. Một file YAML có cấu trúc như sau:
---
# A list of tasty fruits
fruits:
- Apple
- Orange
- Strawberry
- Mango
Và để load file này, trong Ruby ta sẽ viết như sau:
require 'yaml'
YAML.load(
File.open('example.yml').read
)
# => {"fruits"=>["Apple", "Orange", "Strawberry", "Mango"]}
Nếu muốn bạn cũng có thể tạo file YAML từ Ruby Hash
config = {
"default" => {
"adapter"=>"postgres",
"encoding"=>"utf8",
"pool"=>5,
"timeout"=>5000
}
}
puts config.to_yaml
# ---
# default:
# adapter: postgres
# encoding: utf8
# pool: 5
# timeout: 5000
Biến môi trường
Thông thường ta nên hạn chế việc sử dụng trực tiếp biến môi trường trong source code. Ta chỉ nên sử dụng một hệ thống config trong code thay để dễ quản lý hơn. Nếu như đã sử dụng YAML thì ta chỉ nên sử dụng config thông qua YAML mà thôi.
Tuy nhiên, đôi khi có những giá trị mà ta buộc phải set trong biến môi trường như token hay access_key của api bên thứ 3. Điều ta cần làm là cần lấy gía trị từ biến môi trường gắn vào file YAML.
YAML không hỗ trợ trực tiếp việc lấy giá trị từ biến môi trường, tuy nhiên ta có thể sử dụng thêm ERB để có thể lấy được giá trị thông qua <%= %>
.
# # YAML file
# production:
# secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
# # End YAML file
require 'yaml'
require 'erb'
yaml_contents = File.open('config.yml').read
config = YAML.load( ERB.new(yaml_contents).result )
config
# => {"production"=>{"secret_key_base"=>"uih...943"}}
Tổng kết
Việc tạo Configuration Object trong ứng dụng không quá phức tạp. Ta chỉ cần quan tâm đến một số thứ như cách implement, scope và size của dự án để lựa chọn phương án phù hợp nhất.
All rights reserved