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.