Multiple databases - wiele baz danych w RoR

Wysłane przez Krzysztof Kempiński dnia 12.11.2007

Ruby on Rails daje prosty sposób na łączenie się z wieloma bazami danych. Najczęściej można to wykorzystać, gdy chcemy aby różne modele zapisywały/odczytywały z różnych baz danych. Aby nie zmieniać połączenia z bazą przy każdorazowej operacji na modelu można wykorzystać gotowe mechanizmy, które są dostępne w ActiveRecord.
W tym artykule chciałbym przedstawić i omówić kilka sposobów na łączenie się z wieloma bazami danych z naszej aplikacji Ruby on Rails.

1. Wstęp

Powodów, dla których chcielibyśmy się łączyć z wieloma bazami danych może być wiele. Możemy chcieć wykorzystywać dane z wielu baz/serwerów, mieć możliwość replikacji bazy przy zmianie danych, bądź też przełączania się na inną bazę w różnych porach dnia, dla różnych użytkowników. W zależności, jakie zastosowanie nas interesuje, możemy skorzystać z różnych sposobów podłączenia wielu baz (multiple databases).

2. Przełączanie bazy w kontrolerze

Przełączanie bazy w kontrolerze stosujemy gdy chcemy odgórnie wybierać bazę dla całej aplikacji i uzależniać ją od ustalonych warunków. Przykład takiego przełączania podaję poniżej. W tym przypadku zmieniamy bazę po godzinie 13tej.

class ApplicationController < ActionController::Base

  before_filter :change_db

  def change_db
    db_name = (Time.now.hour > 12) ? "db_1" : "db_2"

    # connect to database
    ActiveRecord::Base.establish_connection(
      :adapter  => "mysql",
      :host     => "localhost",
      :username => "db_user",
      :password => "db_pass",
      :database => db_name
    )
  end
end

Podany warunek przełączania bazy jest może mało "życiowy". Częściej można pewnie spotkać sytuację gdy takie przełączanie ma miejsce dla różnych subdomen itd.

Istnieje również możliwość ustalenia/zmiany połączenia dla poszczególnych modeli. Wystarczy użyć poniższy kod:

class ApplicationController < ActionController::Base  
  require 'yaml'

  before_filter :set_extra_db_connection

  def set_extra_db_connection
   extra_coord = YAML.load(File.open(File.join(RAILS_ROOT,"config/database.yml"),"r"))["extradb"]
   Cat.establish_connection(extra_coord)
   Dog.establish_connection(extra_coord)
  end

end

zakładając, że w pliku ./config/database.yml mamy zdefiniowane połączenie o nazwie "extradb":

extradb:
  adapter:     mysql
  host:        localhost
  username:    root
  password:    blabla
  database:    extradb

3. Abstrakcyjne modele dla połączeń z różnymi danymi

W sytuacji gdy chcemy korzystać/zapisywać do różnych baz danych, możemy wykorzystać fakt, iż różne modele (mapujące tabele w bazie) mogą być podłączone do kilku baz danych. W aplikacji zatem operujemy na modelu a ten wie do której bazy skierować zapytanie.
Żeby wykorzystać takie rozwiązanie należy w pliku ./config/database.yml zdefiniować dostęp do innej bazy, np. tak:

security_db:
  adapter: postgresql
  database: security
  host: localhost
  username: uuuu
  password: xxxxx

Następnym krokiem jest zdefiniowanie abstrakcyjnego modelu, z którego to będą dziedziczyć wszystkie modele korzystające z innej bazy. Tworzymy zatem plik ./app/models/security_base.rb :

class SecurityBase < ActiveRecord::Base
   self.abstract_class = true
   establish_connection "security_db" 
 end

wystarczy wtedy, aby model dziedziczył z tej klasy, np. tak:

class SecurityUser < LegacyBase
   ...
end

a wszelkie operacje na takim modelu będą się przekładały na zmianie w bazie "security_db".

4. Magic Multi-Connections

Jest to gem o którym poczytać i którego pobrać można pod adresem: http://magicmodels.rubyforge.org/magic_multi_connections/
Gem instalujemy poleceniem:

sudo gem install magic_multi_connections


Następnie na końcu pliku environment.rb dodajemy

require 'magic_multi_connections'

a w kontrolerze na górze:

require 'rubygems'
require 'magic_multi_connections'

Oprócz zdefiniowania nowej bazy w database.yml:

private:
  adapter: postgresql
  database: private
  host: localhost
  username: uuuu
  password: xxxxx

konieczne jest też utworzenie pliku :

cp config/environments/development.rb config/environments/private.rb


Trzeba pamiętać, że migrację w takim przypadku wywołujemy poprzez:

rake db:migrate RAILS_ENV=private


W pliku definiujemy nowe połączenie:

module Private
  establish_connection :private
end

a wykorzystujemy je np. tak:

class PeopleController < ApplicationController

  before_filter :check_private
  
  def index
    @people = @mod::Person.find(:all)
  end

  private
  def check_private
    @mod = params[:private] ? Private : Object
  end
end

zatem w zależności od parametru z URL system przełącza się na różne bazy. Jednakowoż w każdym z tych przypadków dostęp do odpowiedniego modelu następuje zawsze poprzez @mod::Person.

5. Active Delegate

ActiveDelegate to plugin, który możemy zainstalować komendą:

script/plugin install http://svn.planetargon.org/rails/plugins/active_delegate


Następnie tradycyjnie w database.yml definiujemy połączenie z bazą:

master_database:
  adapter: postgresql
  database: master_database
  host: localhost
  username: uuuu
  password: xxxxx

Następnie tworzymy klasę obsługującą polecenia z nową bazą:

# app/models/master_database.rb
  class MasterDatabase < ActiveRecord::Base
    handles_connection_for :master_database # <-- this matches the key from our database.yml
  end 

Pozostaje już stworzyć model, który będzie korzystał z nowego bazy oddelegowując połączenie z nią do powyżej stworzonego pliku:

# app/models/animal.rb
  class Animal < ActiveRecord::Base
     delegates_connection_to :master_database, :on => [:create, :save, :destroy]
  end