2009-10-21 6 views
9

Ich arbeite an einer Rails (derzeit 2.3.4) App, die Subdomains verwendet, um unabhängige Account-Sites zu isolieren. Um es klar zu sagen, was ich meine, ist foo.mysite.com sollte den Inhalt des foo-Accounts anzeigen und bar.mysite.com sollte den Inhalt des Balkens anzeigen.Rails: Best Practice, um Abfragen basierend auf Subdomain abzugrenzen?

Wie können Sie sicherstellen, dass alle Modellabfragen auf die aktuelle Subdomäne beschränkt sind?

Zum Beispiel einer meines Controller sieht ungefähr so ​​aus:

@page = @global_organization.pages.find_by_id(params[:id])   

(Hinweis @global_organization wird im application_controller über Sub-Domain-fu.) Wenn das, was ich würde es vorziehen, ist so etwas wie:

@page = Page.find_by_id(params[:id]) 

Wenn das Page-Modell gefunden wird, wird automatisch auf die richtige Organisation beschränkt. Ich habe versucht, die default_scope Richtlinie wie folgt aus: (in der Seite Modell)

class Page < ActiveRecord::Base 
    default_scope :conditions => "organization_id = #{Thread.current[:organization]}" 
    # yadda yadda 
end 

(Wieder nur zu beachten, die gleiche application_controller setzt Thread.current [: Organisation] auf die ID der Organisation für den weltweiten Zugang.) Das Problem bei diesem Ansatz besteht darin, dass der Standardbereich bei der ersten Anforderung festgelegt wird und sich bei nachfolgenden Anforderungen an verschiedene Unterdomänen nie ändert.

Drei scheinbare Lösungen so weit:

1 Verwenden Sie separate vhosts für jede Sub-Domain und nur laufen verschiedene Instanzen der App pro Sub-Domain (mit mod_rails). Dieser Ansatz ist für diese App nicht skalierbar.

2 Verwenden Sie den obigen Ansatz des Originalcontrollers. Leider gibt es eine ganze Reihe von Modellen in der App und viele der Modelle sind ein paar Joins aus der Organisation entfernt, so dass diese Notation schnell umständlich wird. Was noch schlimmer ist, ist, dass Entwickler aktiv dazu aufgefordert werden, sich an die Einschränkungen zu erinnern und diese anzuwenden oder ein signifikantes Sicherheitsproblem zu riskieren.

3 Verwenden Sie einen before_filter, um den Standardbereich der Modelle bei jeder Anforderung zurückzusetzen. Sie sind sich nicht sicher, welche Leistung hier erzielt wird oder wie Sie am besten auswählen können, welche Modelle per Update aktualisiert werden sollen.

Gedanken? Irgendwelche anderen Lösungen, die ich vermisse? Dies scheint ein allgemein genug Problem zu sein, dass es eine Best Practice geben muss. Alle Eingaben geschätzt, danke!

Antwort

2

Haben Sie versucht, die default_scope a lambda zu definieren? Das Bit lambda, das die Optionen definiert, wird bei jeder Verwendung des Bereichs ausgewertet.

class Page < ActiveRecord::Base 
    default_scope lambda do 
    {:conditions => "organization_id = #{Thread.current[:organization]}"} 
    end 
    # yadda yadda 
end 

Es ist im Wesentlichen tun Ihre dritte Option, indem Sie zusammen mit Ihrem vor Filter Magie arbeiten. Aber es ist ein wenig aggressiver als das, jeden einzelnen Fund, der auf dem Page-Modell verwendet wird, anzugreifen.

Wenn Sie dieses Verhalten für alle Modelle möchten, können Sie das default_scope zu ActiveRecord :: Base hinzufügen, aber Sie erwähnen ein paar, die ein paar Joins weg sind. Wenn Sie also diesen Weg gehen, müssen Sie die Standardbereiche in diesen Modellen überschreiben, um die Joins zu adressieren.

+0

Danke für den Input! Haben Sie diesen Code in der Praxis getestet? Ich habe es gerade in der App ausprobiert, und während ich bestätigen kann, dass das Lambda tatsächlich bei jeder Suche ausgewertet wird, wendet das default_scope die zurückgegebenen Werte nicht an. Bei einem Blick auf die Ausgabe-SQL scheinen die Bedingungen einfach ignoriert zu werden. Aus verschiedenen anderen Gründen konnte ich meinen Debugger noch nicht starten, aber ich werde einen Höhepunkt erreichen, sobald er wieder läuft. Gedanken darüber, warum es nicht funktioniert? – qfinder

+0

Ehrlich gesagt habe ich keine Ahnung, wie der interne Server Threads verwendet, also nahm ich an, dass 'Thread.current [: organisation]' im Modell funktionierte. Aber ich weiß nicht, ob das erklären würde, warum die Bedingungen ignoriert werden. Da Sie im Wesentlichen den Standardbereich definieren, nach organazation_id = nil zu suchen. – EmFi

+0

Bei näherer Untersuchung scheint es, dass dies noch nicht funktioniert. Es gibt einen Patch, aber YMMV: https://rails.lighthouseapp.com/projects/8994/tickets/1812-default_scope-cant-take-procs – EmFi

2

Seien Sie vorsichtig, wenn Sie mit dem Standardbereich arbeiten, da dies zu einem falschen Sicherheitsgefühl führt, insbesondere beim Erstellen von Datensätzen.

Ich habe früher immer Ihr erstes Beispiel dieses klar zu halten:

@page = @go.pages.find(params[:id]) 

Der wichtigste Grund dafür ist, weil Sie auch auf neue Datensätze angelegt wird diese Assoziation sicherstellen wollen, so wird Ihre neue/erstellen Aktionen aussehen wie folgt aus, um sicherzustellen, dass sie korrekt an den Elternverein scoped sind:

# New 
@page = @go.pages.new 

# Create 
@page = @go.pages.create(params[:page]) 
+0

Guter Punkt, stellt sich in diesem Fall heraus, dass es wegen der Join-Struktur nur ein paar Stellen gibt, an denen dies erzwungen werden muss, aber viele Stellen, an denen es bei Lesevorgängen durchgesetzt werden muss, so möchte ich immer noch einen Weg finden Verwenden Sie das default_scope. – qfinder

+0

Sehen Sie sich auch http://github.com/devinterface/authlogic_subdomain_fu_startup_app an. Dies ist eine Beispielanwendung für den Einstieg in eine mandantenfähige Subdomänenanwendung. Es behandelt die Kontoerstellung mit und den ersten Benutzer (der auch der Besitzer des Kontos ist). – bensie

+1

Es mag wie Extraarbeit/Codefehler in der Steuerung scheinen, aber es ist wirklich nicht. Am Ende zeigt es die Absicht des Programmierers, die es viel einfacher macht, die Straße zu modifizieren. Um die Entwickler daran zu erinnern, die Einschränkung zu beachten und anzuwenden, sollten Sie trotzdem Tests für jede Aktion haben, und einer dieser Tests sollte sicherstellen, dass das Modell dem übergeordneten Bereich korrekt zugeordnet ist. – bensie

0

Sie besser sein könnte, ein database per account and switching the database connection auf der Sub-Domain auf Basis von mit.

Zusätzlich zum obigen Link, wenn Sie ein Modell (in Ihrem Fall Konto) haben, das Sie die Standarddatenbank verwenden möchten, geben Sie einfach establish connection in das Modell ein.

class Account < ActiveRecord::Base 

    # Always use shared database 
    establish_connection "shared_#{RAILS_ENV}".to_sym 
+0

Dies ist zwar eine praktikable Lösung, macht jedoch die Ausführung von Berichten (Analysen) auf mehreren Konten zu einem großen Problem. Auch Migration, etc. –

+0

Ja, Migrationen war ein Punkt der Schmerzen für mich. Ich erinnere mich, dass ich eine Lösung sah, die zulässt, dass Migrationen mit einer Liste von Datenbanken ausgeführt werden können, aber ich kann mich nicht erinnern, wo. – Kris