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 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.

Create the custom database files

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.rbseeds.rb, cùng với thư mục migrate chỉ tạo ra và để trống.

Add Rake tasks

Để 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

Add a custom generator

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

Finalize connection and models

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

Kết luận

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.

Tài liệu tham khảo

http://www.ostinelli.net/setting-multiple-databases-rails-definitive-guide/


All Rights Reserved