Hadmut Danisch

Ansichten eines Informatikers

Ruby on Rails: multiple databases migrations

Hadmut
16.9.2009 18:57

Weil bisher keine saubere Lösung dafür dokumentiert ist, hier ein sauberer Ansatz:

Ruby on Rails ist bisher leider nur dafür ausgelegt, mit jeweils einer einzigen Datenbank zu arbeiten. Manchmal muß man aber einen Teil der Daten in einer anderen Datenbank ablegen oder von dort holen. Sei es, daß die Daten einfach verteilt liegen müssen. Oder daß es bisher eben so organisiert war. Oder die Daten in unterschiedlichen Datenbanktypen (mysql,sqlite,…) liegen (müssen). Oder aus Sicherheitsgründen (Hier in meinem Beispiel mußte zu einer großen Datenbank noch eine kleine, separat gespeicherte Datenbank mit Hash-Werten zur Absicherung der großen abgelegt werden – es nutzt ja nichts, wenn die Daten und ihre Hash-Werte in derselben Datenbank liegen.) Und damit benötigt man etwas, was das ansonsten elegante Ruby on Rails (eigentlich) nicht kann.

Es geht aber doch.

  1. Zunächst erstellt man in config/database.yml neue Einträge für die zusätzliche Datenbank. Es bietet sich an, auch hier mehrfache Einträge zu erzeugen und diese auf _production und _development lauten zu lassen.
  2. In den Models der Daten, die auf der anderen Datenbank liegen sollen, trägt man etwas in der Art wie

    establish_connection “trustdb_#{RAILS_ENV}”

    ein (muß natürlich mit den Namen in database.yml übereinstimmen). Laut verschiedenen Forenkommentaren benötigt man noch etwas mehr dazu, es gibt noch Anweisungen, dafür Caching zu modifizieren usw., auch soll man dafür zwischen ActiveRecord::Base und der eigenen Klasse noch eine Zwischenklasse einziehen, der genaue Sinn ergibt sich mir mangels präziser Dokumentation bisher aber nicht. Mit der obigen Anweisung hatte ich bisher aber keine Probleme, funktioniert einwandfrei.

  3. Schwierig sind aber Migrations mit verschiedenen Datenbanken. Die einen sagen, es geht nicht. Die anderen sagen, es geht nicht ohne Probleme. Vor einiger Zeit gab es mal in einem Rails-Wiki eine Seite (inzwischen nicht mehr da), die vor der Migration die connection umbog und danach wieder auf den alten Wert zurückstellte. Alles Murks. Ich denke, ich habe eine saubere und schöne Lösung gefunden:

    Man legt unter db für die zweite Datenbank ein neues Migrationsverzeichnis an, beispielsweise db/migrate_trust und legt seine Migrationen darin ab bzw. verschiebt die mit script/generate erstellten Migrationen für die Datentypen der zweiten Datenbank da rein. Dann gibt es für jede Datenbank eine völlig separate Sammlung von Migrationsskripten.

    Dann legt man in lib/tasks eine neue Datei multidb.rake mit neuen Rake-Tasks an um die Migration auszuführen. Die (derzeit noch verkürzte) Version sieht so aus:

    namespace :db do
    
      desc "Migration der trust-Datenbank"
      task :migrate_trust => :environment do
     
        ActiveRecord::Base.establish_connection "trustdb_#{RAILS_ENV}"
        ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
        ActiveRecord::Migrator.migrate("db/migrate_trust/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
    
        #Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
      end
    
    end
    
    

    und dann kann man mit rake db:migrate und rake db:migrate_trust völlig unabhängig voneinander die beiden Datenbanken migrieren, und es werden auch separate Statustabellen verwaltet. Alles schön sauber und komplikationsfrei getrennt. 🙂

    Das obige Beispiel ist jedoch noch verkürzt und stellt nur den migrate-Befehl dar. Um auch die anderen Befehle wie db:migrate:redo usw. zu haben, muß man leider die anderen Tasks aus /usr/share/rails/railties/lib/tasks/databases.rake übernehmen und entsprechend anpassen (d.h. die Zeile mit …Base.establish_connection… einfügen).