Setting up multiple databases in Rails: the definitive guide
Có nhiều lý do khác nhau khiến bạn có thể cân nhắc việc có nhiều cơ sở dữ liệu trong ứng dụng Ruby on Rails. Trong trường hợp cụ thể của tôi, tôi cần phải lưu trữ số lượng lớn dữ liệu đại diện cho hành vi của người dùng: nhấp chuột, các trang truy cập, những thay đổi lịch sử, v.v ... Tôi đã đọc ...
Có nhiều lý do khác nhau khiến bạn có thể cân nhắc việc có nhiều cơ sở dữ liệu trong ứng dụng Ruby on Rails. Trong trường hợp cụ thể của tôi, tôi cần phải lưu trữ số lượng lớn dữ liệu đại diện cho hành vi của người dùng: nhấp chuột, các trang truy cập, những thay đổi lịch sử, v.v ... Tôi đã đọc nhiều giải pháp khác nhau, tuy nhiên tôi không thể tìm thấy một trong đó có thể có đầy đủ bao gồm làm thế nào để:
- Có file migrate và schema riêng biệt cho mỗi cơ sở dữ liệu.
- Dùng Rails generate để tạo các file migration riêng biệt cho mỗi cơ sở dữ liệu.
- Cung cấp các rake task cho từng cơ sở dữ liệu cụ thể (Ví dụ rake db:migrate cho từng cơ sở dữ liệu khác nhau).
- Tích hợp RSpec.
- Làm việc với Database Cleaner.
- Làm việc trên Heroku.
Trong bài hướng dẫn này, chúng ta sẽ thiết lập cơ sở dữ liệu thứ hai có tên là Stats. Để làm được như vậy, chúng ta sẽ bắt trước cách Rails xử lý với cơ sở dữ liệu chính. Đầu tiên, ta tạo một file config/database_stats.yml là file cấu hình kết nối đến cơ sở dữ liệu Stats (nó có chức năng giống như config/database.yml mà chúng ta thường dùng cho cơ sở dữ liệu chính). Nội dung của file này như sau:
development: adapter: postgresql encoding: utf8 host: localhost pool: 10 database: myapp_stats_development username: postgres password: test: adapter: postgresql encoding: utf8 host: localhost pool: 10 database: myapp_stats_test username: postgres password: production: adapter: postgresql encoding: utf8 url: <%= ENV["DATABASE_STATS_URL"] %> pool: <%= ENV["DB_POOL"] || 5 %>
Lưu ý rằng chúng ta đã đặt tên cụ thể cho các cơ sở dữ liệu, thực hiện theo các đúng quy ước đặt tên của Rails.
Tiếp đến, chúng ta sẽ tạo một thư mục để lưu trữ tất cả các file migration và file schema của cơ sở dữ liệu Stats. Về cơ bản sao chép lại thư mục db của dự án Rails. Tạo thư mục db_stats trong thư Rails root và đảm bảo cấu trúc và các tệp tin giống với thư mục db . Bạn sẽ có một cái gì đó như sau
-- db |-- migrate schema.rb seeds.rb -- db_stats |-- migrate schema.rb seeds.rb
Các file schema.rb và seeds.rb, cùng với thư mục migrate chỉ tạo ra và để trống.
Để xử lý và điều khiển được cơ sở dữ liệu Stats, cho phép create , migrate, drop v.v..., chúng ta sẽ cần tạo ra Rake task. Chúng ta sẽ tạo ra các hàm giống với các hàm mà Rails cung cấp đối với cơ sở dữ liệu chính.
Tạo file lib/tasks/db_stats.rake có nội dung như sau:
task spec: ["stats:db:test:prepare"] namespace :stats do namespace :db do |ns| task :drop do Rake::Task["db:drop"].invoke end task :create do Rake::Task["db:create"].invoke end task :setup do Rake::Task["db:setup"].invoke end task :migrate do Rake::Task["db:migrate"].invoke end task :rollback do Rake::Task["db:rollback"].invoke end task :seed do Rake::Task["db:seed"].invoke end task :version do Rake::Task["db:version"].invoke end namespace :schema do task :load do Rake::Task["db:schema:load"].invoke end task :dump do Rake::Task["db:schema:dump"].invoke end end namespace :test do task :prepare do Rake::Task["db:test:prepare"].invoke end end # append and prepend proper tasks to all the tasks defined here above ns.tasks.each do |task| task.enhance ["stats:set_custom_config"] do Rake::Task["stats:revert_to_original_config"].invoke end end end task :set_custom_config do # save current vars @original_config = { env_schema: ENV['SCHEMA'], config: Rails.application.config.dup } # set config variables for custom database ENV['SCHEMA'] = "db_stats/schema.rb" Rails.application.config.paths['db'] = ["db_stats"] Rails.application.config.paths['db/migrate'] = ["db_stats/migrate"] Rails.application.config.paths['db/seeds'] = ["db_stats/seeds.rb"] Rails.application.config.paths['config/database'] = ["config/database_stats.yml"] end task :revert_to_original_config do # reset config variables to original values ENV['SCHEMA'] = @original_config[:env_schema] Rails.application.config = @original_config[:config] end end
Trong đoạn code trên, ta tạo tất cả các task có tên giống với các task mà Rails cung cấp (migrate, setup, v.v...) với namspace là stats:db. Chúng ta chạy vòng lặp qua tất cả các task của namspace db và đảm bảo rằng task stats:set_custom_config luôn chạy trước các task khác, còn task stats:revert_to_original_config luôn chạy sau khi mỗi task đã kết thúc
# append and prepend proper tasks to all tasks defined in stats:db namespace ns.tasks.each do |task| task.enhance ["stats:set_custom_config"] do Rake::Task["stats:revert_to_original_config"].invoke end end
Chúng ta phải làm điều này vì, không may, Rails hỗ trợ cho nhiều cơ sở dữ liệu không phải là tuyệt đối, vì thế chúng ta cần cung cấp các thủ thuật nhỏ để làm cho mọi thứ hoạt động. Vì lý do này, chúng ta phải đặt các biến môi trường và biến cấu hình cụ thể cho các giá trị tùy chỉnh phù hợp với cơ sở dữ liệu Stats của chúng ta trước khi chúng ta chạy các task và sau đó đảm bảo rằng các giá trị ban đầu được thiết lập lại khi các task đó đã được chạy. Hai task sau giúp làm điều đó:
task :set_custom_config do # save current vars @original_config = { env_schema: ENV['SCHEMA'], config: Rails.application.config.dup } # set config variables for custom database ENV['SCHEMA'] = "db_stats/schema.rb" Rails.application.config.paths['db'] = ["db_stats"] Rails.application.config.paths['db/migrate'] = ["db_stats/migrate"] Rails.application.config.paths['db/seeds'] = ["db_stats/seeds.rb"] Rails.application.config.paths['config/database'] = ["config/database_stats.yml"] end task :revert_to_original_config do # reset config variables to original values ENV['SCHEMA'] = @original_config[:env_schema] Rails.application.config = @original_config[:config] end
Cuối cùng, nếu bạn đang sử dụng RSpec, bạn có thể thêm một task spec, để đảm bảo rằng cơ sở dữ liệu Stats được tự động chuẩn bị khi chạy test:
task spec: ["stats:db:test:prepare"]
Việc cài đặt đã hoàn thành, bây giờ chúng ta có thể tạo và migrate cơ sở dữ liệu Stats như sau:
$ rake stats:db:create $ rake stats:db:migrate
Thật không may chúng ta không thể sử dụng ActiveRecord::Generators::MigrationGenerator bởi vì nó đã được fix cứng code trong thư mục db.migrate/ như bên dưới:
def create_migration_file set_local_assigns! validate_file_name! migration_template @migration_template, "db/migrate/#{file_name}.rb" end
Do đó, chúng ta cần tạo một generator riêng cho cơ sở dữ liệu Stats. Tạo generator trong file lib/generators/stats_migration_generator.rb như sau
require 'rails/generators/active_record/migration/migration_generator' class StatsMigrationGenerator < ActiveRecord::Generators::MigrationGenerator source_root File.join(File.dirname(ActiveRecord::Generators::MigrationGenerator.instance_method(:create_migration_file).source_location.first), "templates") def create_migration_file set_local_assigns! validate_file_name! migration_template @migration_template, "db_stats/migrate/#{file_name}.rb" end end
Cách sử dụng generator này rất đơn giản như sau:
$ rails g stats_migration create_clicks create db_stats/migrate/20151201191642_create_clicks.rb
Sau đó ta chay rake stats:db:migrate để tạo bảng cho cơ sở dữ liệu Stats
Tạo file config/initializers/db_stats.rb có nội dung như sau để đọc các cấu hình kết nối đến Stats:
# save stats database settings in global var DB_STATS = YAML::load(ERB.new(File.read(Rails.root.join("config","database_stats.yml"))).result)[Rails.env]
Sau đó trong mỗi model của cơ sở dữ liệu Stats, ta thêm đoạn sau để kết nối đến Stats. Ví dụ
class Click < ActiveRecord::Base establish_connection DB_STATS end
Như vậy, trong bài viết trên đây, đã trình bày cách cài đặt để một ứng dụng Rails có thể sử dụng nhiều cơ sở dữ liệu. Hy vọng nó sẽ giúp ích cho mọi người. Cảm ơn đã đọc bài viết.
http://www.ostinelli.net/setting-multiple-databases-rails-definitive-guide/