Tạo Configuration Object trong Ruby
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 ...
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.