2010-10-18 13 views
10

Ich lerne RoR aus vielen Jahren C# und MSSQL.Ruby On Rails Benutzermodell für mehrere Typen

Ich habe ein Projekt ausgewählt, um eine Website für meinen Bruder zu erstellen, der ein Mietwohnungsverwalter ist. Ich dachte mir, das sollte ziemlich einfach sein, da die Models geradlinig sein sollten, aber ich denke, dass ich vielleicht über alles nachdenke, oder dass ich Schwierigkeiten habe, den "alten" Weg loszulassen. Wie auch immer, hier ist das Problem. Ich beginne mit nur zwei Modellen (User und Property). Das Eigentumsmodell ist einfach, der Benutzer nicht so sehr. Ich habe festgestellt, dass wir drei Arten von Benutzern im System haben. Mieter, Eigentümer und Manager (mein Bruder wird der einzige Manager sein, aber ich dachte, ich würde es entwerfen, um zu wachsen) Er verwaltet Eigenschaften für mehrere Besitzer, von denen jeder viele Eigenschaften besitzen kann. Jede Immobilie hat einen Eigentümer, einen Mieter und einen Manager.

Mieter können sich anmelden und nur die Eigenschaft sehen, die sie vermieten, um vielleicht eine Wartungsanfrage oder etwas Ähnliches auszufüllen ... (keine wirkliche Anforderung an diesem Punkt, dem Mieter sogar einen Login zu geben, aber ich dachte es wäre eine gute Übung)

Das gleiche gilt für Besitzer, keiner von ihnen braucht wirklich Zugang zu dem System (sie mieten meinen Bruder, so dass sie nicht beteiligt sein müssen), aber ich dachte, es könnte nett sein und wieder ein gute Übung.

verwendete ich die Nifty_generator einen Benutzer zu erzeugen, die nur E-Mail gibt, ich Passwort usw. es erweitert als ...

class AddProfileDataToUsers < ActiveRecord::Migration 
    def self.up 
    add_column :users, :first_name, :string 
    add_column :users, :last_name, :string 
    add_column :users, :address1, :string 
    add_column :users, :address2, :string 
    add_column :users, :city,:string 
    add_column :users, :state, :string 
    add_column :users, :zip, :string 
    add_column :users, :phone, :string 
    add_column :users, :email, :string 
    add_column :users, :user_type, integer 
    end 

    def self.down 
    remove_column :users, :first_name 
    remove_column :users, :last_name 
    remove_column :users, :address1 
    remove_column :users, :address2 
    remove_column :users, :city 
    remove_column :users, :state 
    remove_column :users, :zip 
    remove_column :users, :phone 
    remove_column :users, :email 
    remove_column :users, :user_type 
    end 
end 

Hier folgt der Code ist es, die Eigenschaften Tabelle

class CreateProperties < ActiveRecord::Migration 
    def self.up 
    create_table :properties do |t| 
     t.string :address 
     t.string :city 
     t.string :type 
     t.integer :beds 
     t.float :baths 
     t.float :price 
     t.float :deposit 
     t.string :terms 
     t.string :laundry 
     t.datetime :date_available 
     t.integer :sqft 
     t.integer :owner_id 
     t.integer :manager_id 
     t.integer :tenant_id 
     t.timestamps 
    end 
    end 

    def self.down 
    drop_table :properties 
    end 
end 
erstellen

Ich habe Folgendes zu dem Benutzermodell hinzugefügt, das von dem Generator nifty_authentication

class User < ActiveRecord::Base 

    #other stuff in the user model up here...... 
    validates_length_of :password, :minimum => 4, :allow_blank => true 

    #this is the stuff that I have added to the user model 
    has_many :managed_properties, :class_name => "Property", :foreign_key => "manager_id" 
    has_many :owned_properties, :class_name => "Property", :foreign_key => "owner_id" 
    has_one :rented_property, :class_name => "Property", :foreign_key => "tenant_id" 
generiert wurde

ich diese dann auf das Objekt-Modell hinzugefügt ....

class Property < ActiveRecord::Base 
    belongs_to :manager, :class_name => "User" #picked up by the manager_id 
    belongs_to :owner, :class_name => "User" #picked up by the owner_id 
    belongs_to :tenant, :class_name => "User" #picked up by the tenant_id 
end 

Meine Frage ist, sieht dies wie ein akzeptabler Weg, um die Situation der Modellierung I beschrieben?

Sollte ich die Vererbung einzelner Tabellen verwenden und ein Tenant-Modell erstellen? ein Managermodell; und ein Besitzermodell? Das Problem, das ich dabei sah, war, dass ein einzelner Benutzer sowohl ein Manager als auch ein Besitzer sein konnte. Dies könnte dadurch gelöst werden, dass eine Rollentabellen für den Benutzer vorhanden sind, wobei ein Benutzer viele Rollen hat und eine Rolle viele Benutzer hat. Ich hatte auch eine Profiltabelle mit einer Eins-zu-Eins-Übereinstimmung mit der Benutzertabelle betrachtet und diese polymorph gemacht, aber ich dachte nicht, dass diese Situation das wirklich erfordert und es das Problem nicht gelöst hat, wo ein Benutzer ein Besitzer sein kann und ein Manager .....

Dies ist, wenn ich zu denken begann, dass ich vielleicht über das Denken des Problems war und kam auf, was Sie hier sehen.

Ich begrüße alle konstruktiven Kommentare, die Sie haben können. Bitte beachte, dass ich in Rails noch nie etwas gebaut habe und dies ist alles ein erster Versuch, vor einer Woche hatte ich noch nie Schienen auf meinem Computer installiert.

Ich weiß nicht, ob das wichtig ist, aber ich dachte, dass der Admin/Manager für die Erstellung von Benutzern verantwortlich wäre. Dies ist keine Selbstregistrierungsart der Site. Der Verwalter wird neue Besitzer hinzufügen, wenn er einen neuen Eigentümer anmeldet, und das gleiche gilt für die Mieter. Dadurch wird es einfacher, den Typ des Benutzers zu bestimmen, den er erstellt.

Vielen Dank für Ihre Einsichten.

Antwort

8

FWIW, das sieht gut aus für mich. Ich könnte declarative_authorization betrachten, um Ihre Rollen zu verwalten, die am Ende etwas involviert sein könnten, besonders vom Standpunkt der Benutzeroberfläche. Das Verwalten von Benutzern mit mehreren Rollen scheint in dieser Situation geeigneter als STI, da ein Benutzer sowohl Manager als auch Mandant sein kann usw.

Ein Ansatz, um den Code für Manager, Mandanten und Eigentümer getrennt zu halten Wenn Sie alle drei Rollen in einer einzigen Instanz verwenden möchten, können Sie möglicherweise Module, die diese Rollen darstellen, basierend auf den Rollen eines bestimmten Benutzers dynamisch einbeziehen. Sie würden immer noch eine Basisklasse von Benutzer haben, aber anstatt STI zu verwenden, was Sie auf eine einzige Vererbungsstruktur beschränken würde, könnten Sie Module basierend auf den Rollen jedes Benutzers mischen, die sich einfach über die Eigenschaft hingehen Benutzer während der Initialisierung.

Dafür könnte ich eine User factory erstellen, die den Benutzer für die verschiedenen Rollen inspizieren und die singleton class dieses Benutzers mit dem entsprechenden Modul erweitern: mit dem Tenant-Modul für Benutzer erweitern, die Mietereigenschaften haben, mit erweitern Manager-Modul für Benutzer, die Eigenschaften haben es geschafft, etc.

Vom Standpunkt der declarative_authorization könnten Sie die role_symbols in ähnlicher Weise auf der Grundlage erklären, ob Verbände wie managed_properties bevölkert wurden, zum Beispiel:

def role_symbols 
    @roles ||= {:manager => :managed_properties, 
    :tenant => :rented_property, 
    :owner => :owned_properties}.map do |k,v| 
     k if !send(v).blank? 
    end.compact 
end 

oder etwas ähnliches. Wahrscheinlich möchten Sie die Rollen festlegen und das entsprechende Modul gleichzeitig hinzufügen. Ruby and Rails gibt Ihnen viele Möglichkeiten über metaprogramming techniques dynamisch mit der Funktionalität zu dekorieren, die individuelle Modelle benötigen. Mein Ansatz mag für Ihre Anwendung geeignet sein oder auch nicht, aber es gibt unzählige andere Möglichkeiten, wie Sie das Problem angehen und Ihren Code sauber und DRY halten können.

Insgesamt scheint mir Ihr Datenmodell solide zu sein, und Ihr Instinkt, STI nicht zu verwenden, um mehrere Rollen zu verwalten, ist korrekt. Es sieht für mich nicht so aus als hättest du es überlegt - ich denke du bist auf dem richtigen Weg. Es ist eigentlich ziemlich Rails-y für einen ersten Durchgang.

EDIT: Weißt du, je mehr ich darüber nachdenke, ich bin mir nicht sicher, was der Vorteil ist, die Manager/Mieter/Besitzer-Funktionalität in separaten Modulen zu halten. In meiner früheren Inkarnation als Java/C# Typ hätte ich alles über die SRP/ und totale Trennung von Sorgen. Aber in Ruby and Rails ist es nicht annähernd so groß, da es dynamisch typisiert ist und die Kopplung nicht annähernd so groß oder zumindest gleich wichtig ist wie in statisch typisierten Umgebungen. Sie könnten vollkommen in Ordnung sein, wenn Sie nur die einzelnen Rollenfunktionen in das einzelne Benutzermodell einfügen und sich nicht mit den Modulen beschäftigen, zumindest noch nicht.

Ich bin hier auf dem Zaun, und würde gerne Eingang von anderen. Für mich ist einer der Vorteile von Ruby-Klassen, im Gegensatz zu Java-Klassen/-Paketen oder .NET-Klassen/Assemblys, dass Sie jederzeit nach Bedarf refactorieren können und nicht annähernd so besorgt sind, welche Klasse, welches Paket, welcher Namespace, dll oder jar ist gekoppelt mit einem anderen, etc. Ich sage nicht, SRP ist in Ruby nicht wichtig, überhaupt nicht. Aber ich bin nicht annähernd so paranoid wie früher.

EDIT: Paul Russell macht einen ausgezeichneten Punkt. Ich denke, Sie sollten ernsthaft in Erwägung ziehen, mehrere Mieter/Manager/Vermieter pro Immobilie zuzulassen. In Rails könnte dies durch eine relationale Tabelle und eine has_many: through-Assoziation ausgedrückt werden, plus STI, um die verschiedenen Arten von Beziehungen zu beschreiben. Ich denke auch, dass es notwendig sein wird, die Beziehung zwischen Benutzer (als Mieter) und Eigentum umzukehren. Eine Immobilie kann mehr als einen Mieter haben, aber ein Mieter kann nicht in mehr als einer Immobilie wohnen. (oder vielleicht können sie? Scheint nicht richtig, aber ...

)

Vielleicht wie etwas (das ist sehr schnell und schmutzig, so verzeiht jede verpasstes Detail bitte):

class PropertyRole < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :property 
end 

class Managership < PropertyRole 
    # Manager functionality here 
end 

class Ownership < PropertyRole 
    # Owner functionality here 
end 

class User < ActiveRecord::Base 
    belongs_to :residence, :class_name => 'Property', :foreign_key => 'residence_id' 
    has_many :managerships 
    has_many :ownerships 

    has_many :owned_properties, :through => :ownerships, :classname => 'Property' 
    has_many :managed_properties, :through => :managerships, :classname => 'Property' 
end 

class Property < ActiveRecord::Base 
    has_many :tenants, :class_name => 'User', :foreign_key => 'residence_id' 
    has_many :managerships 
    has_many :ownerships 
    has_many :owners, :class_name => 'User', :through => :ownerships 
    has_many :managers, :class_name => 'User', :through => :managerships 
end 

Wie ich schon sagte, ist dies schnell und schmutzig, ein hochrangiger erster Durchgang. Beachten Sie, dass wir jetzt Managership- und Ownership-Rollen erstellt haben, die Manager- und Owner-spezifische Funktionen enthalten können, wodurch das frühere Dilemma beseitigt wird, ob diese Funktionalität in separaten Modulen zusammengefasst werden soll.

** Beachten Sie auch, dass ich auch die Tenant/Property-Rolle invertiert habe - ich denke, dies war eine notwendige Änderung für Ihre Domain. Offensichtlich kann eine Residenz mehr als einen Mieter haben. Es scheint mir (im Moment), dass Sie die mandantenspezifische Funktionalität auf dem Benutzermodell beibehalten können.

+0

+1 decl_auth sieht gut aus! – SingleNegationElimination

3

Was mir auffällt, ist, dass Ihr derzeitiges Modell es untersagt, mehrere Mieter oder Vermieter in einer bestimmten Immobilie zu haben, und nimmt an, dass diese Beziehungen dauerhaft sind.

Zumindest in Großbritannien ist es sehr üblich, dass eine Immobilie einem Ehepaar gehört und daher mehrere Vermieter haben könnte (meine Frau und ich sind zum Beispiel in dieser Position). Ebenso ist es bei bestimmten Arten der Vermietung sehr üblich, dass mehrere Mieter in einer einzigen Immobilie wohnen.

Wenn Sie denken, ich bin hier, dann ist es eine Überlegung wert, ein "user_property_role" -Modellobjekt zwischen dem Benutzer und der Eigenschaft einzuführen. Sie hätten dann eine has_many-Beziehung von user und property zu user_property_role. Wenn diese Rolle ein Feld "Beziehungstyp" hätte, das Sie z. 'Vermieter', könnten Sie dann has_many: through und benannte Bereiche (auf dem Objekt user_property_role) verwenden, um z.B. property.users.landlords oder property.users.tenants.

Wenn Sie diesen Ansatz verwenden, können Sie auch Dinge wie 'Beginne' und 'Ende' von Beziehungen angeben und die Tatsache aufzeichnen, dass eine Eigenschaft im Laufe der Zeit mehrere Mandanten hat oder dass eine Eigenschaft verwaltet werden kann verschiedene Leute im Laufe der Zeit. Auch hier sollten Sie in der Lage sein, eine Reihe von benannten Bereichen zu erstellen, so dass Sie z. property.users.current.tenants oder sogar user.properties.current.tenants.

Hoffe, dass hilft, und nicht nur zu Ihrer Verwirrung hinzufügen!

+0

Ja, das ist ein toller Punkt. Die Beziehung zwischen Benutzern und Eigenschaften ist "Viele-zu-Mai", nicht "Viele-zu-eins". Ich denke, das gilt sowohl für die USA als auch für das Vereinigte Königreich. –

+0

@Paul Russell, ist das mit Oszilloskopen möglich? Ich habe etwas Ähnliches versucht (Benutzer has_many: accounts, through:: roles), was zu einer fehlenden Methode führt. Also 'def self.managed wo (: type ==" Ownership ") endet im Role-Modell scheint nicht zu funktionieren! Ich kann user.accounts.managed nicht aufrufen, da managed im Role-Modell ist, nicht im Account-Modell! – Mohamad