Multidatenbank-Models und RSpec
15. Februar 2008
Das Standardverhalten einer Rails-Applikation ist, alle Models über die gleiche Verbindung an eine Datenbank zu knüpfen. In seltenen Fällen, und gerade in Legacy oder nicht homogenen Umgebungen kann es jedoch vorkommen, dass eine Anwendung mehr als eine Datenbank verwendet. D.h. konkret: Mehrere Models mit mehreren eigenständigen Datenbankverbindungen. Ab hier wirds schwierig, da RSpec von Haus aus nicht mit verschiedenen Datenbankverbindungen umgehen kann.
Ein Beispiel: Ich habe 3 Models die jeweils von verschiedenen ‘Master’-Klassen erben:
1 2 3 |
class Google < Search::Google; end class Live < Search::Live; end class Yahoo < Search::Yahoo; end |
Jede ‘Master’ Klasse baut zwangsläufig eine eigene Datenbankverbindung zu den jeweiligen Services auf, um seine Daten abzufragen. Der Kern des Problems befindet sich nun in der Natur von RSpec, wie es die einzelnen Examples ausführt: Vor jedem Spec initialisiert (Begin) RSpec eine Datenbanktransaktion und bricht diese (Rollback) nach dem Spec wieder ab. Es verwendet dazu die Verbindung von ActiveRecord::Base, die wiederum während der Initialisierung der Test-Environment aufgebaut wird. Verwendet nun ein Model eine andere Verbindung als diese, kann die erstellte DB-Transaktion von RSpec nicht mehr greifen und es bleiben Test-Daten in den jeweiligen anderen Datenbanken zurück. Führt man die Specs erneut aus, brichts.
Um das Problem zu lösen, muss man RSpec dazu bringen, nicht nur die Standardverbindung (AR::Base) in eine Transaktion einzuhüllen, sondern alle Datenbankverbindungen.
Der Code dazu sieht folgendermaßen aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
module Spec module Example module ExampleMethods MASTER_MODELS = [Search::Google, Search::Live, Search::Yahoo] protected def before_example_with_multi_db_transactions MASTER_MODELS.each { |cls| cls.connection.begin_db_transaction } before_example_without_multi_db_transactions end alias_method_chain :before_example, :multi_db_transactions def after_example_with_multi_db_transactions after_example_without_multi_db_transactions MASTER_MODELS.each { |cls| cls.connection.rollback_db_transaction } end alias_method_chain :after_example, :multi_db_transactions end end end |
Im Grunde ganz einfach, nicht wahr? Vor jedem Example, wird für jedes ‘Master’-Model eine Transaktion gestartet die nach dem Speclauf wieder zurückgerollt wird.