2010-10-18 70 views
10

我正在学习来自多年c#和MSSQL的RoR。Ruby On Rails多种类型的用户模型

我选择了一个项目为我的兄弟谁是租赁物业经理建立一个网站。我认为这应该是相当容易的,因为模型应该是直截了当的,但是它认为我可能会过度思考一切,或者我无法放弃'旧'方式。无论如何,这是问题。我开始时只有两个模型(用户和财产)。属性模型很简单,用户不多。我认为我们在系统中有三种类型的用户。租户,业主和经理(我的兄弟将是唯一的经理,但我想我会设计它成长)他管理的财产,他们每个人都可以拥有许多财产。每个物业将有一个业主,一个租户和一个经理。

租户将能够登录,只是看到他们租用的财产,可能会填写维护请求或类似的东西......(在这一点上没有真正的要求,即使租户登录系统,但我认为它将是一个很好的锻炼)

同样的事情,所有者都没有真正需要访问系统(他们雇用我的兄弟,所以他们不必参与),但我认为这可能是很好的,又一次好的练习。

我用Nifty_generator生成一个用户,它只是给了电子邮件,密码等我如下扩展了它...

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 

下面是创建属性表的代码

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 

添加以下到先前由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" 

然后我加入这属性模型....

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 

我的问题是,这是否看起来像建模我所描述的情况的一种可接受的方式?

我应该使用单表继承和创建租户模型;经理模式;和所有者模型?我所看到的问题是,单个用户可能既是经理又是所有者。这可以通过为用户提供角色表来解决,其中用户具有多个角色并且角色拥有多个用户。我还查看了一个与用户表进行一对一匹配的配置文件表,并制作了这个多态的表格,但我并不认为这种情况真的需要这样做,它并没有解决用户可以成为拥有者的问题和一个经理.....

这是当我开始认为,也许我是在思考问题,并提出了你在这里看到的。

我欢迎您可能有的任何建设性意见。请记住,我从来没有真正在Rails中构建过任何东西,这是第一次尝试,一周前我甚至从未在我的计算机上安装过rails。

我不知道这是否重要,但我认为管理员/经理将负责创建用户。这不会是一个自我注册类型的网站。经理在签订新业主时会增加新的业主,而租户也是如此。这将更容易确定他正在创建的用户的类型。

感谢您的任何洞察力。

回答

8

FWIW,这对我来说很好。我可能会看declarative_authorization来管理你的角色,这可能最终会有所牵扯,特别是从UI的角度来看。在这种情况下,管理拥有多个角色的用户似乎比STI更合适,因为如您所说,用户既可以是经理,也可以是租户等。

保持管理员,租户和所有者的代码不同的一种方法,同时允许在单个实例中使用所有三个角色,可以根据任何给定用户的角色动态地包含表示这些角色的模块。你仍然有一个基类的用户,但不是使用STI,这会限制你到一个单一的继承结构,你可以根据每个用户拥有的角色混合模块,这可能简单地根据属性属于某个给定的属性用户在初始化期间

为此,我可能创建一个用户工厂,可以检查用户所发挥的各种角色,并使用相应的模块扩展该用户的singleton class:对租户属性为tenant的用户扩展,用户管理模块,可设法性能等

从declarative_authorization的角度来看,你可以声明同样基于像managed_properties协会是否填充role_symbols,例如:

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 

或类似的东西。您可能需要设置角色并同时包含相应的模块。 Ruby和Rails通过metaprogramming techniques为您提供了多种选择,可以动态修饰各个模型所需的功能。我的方法可能适用于您的应用程序,也可能不适合您的应用程序,但您可以通过无数其他方式来解决问题并保持代码清洁和干爽。

总之在我看来,你的数据模型是健全的,你的本能不使用STI来管理多个角色是正确的。它并不像我认为你推翻了它 - 我认为你在正确的轨道上。实际上,Rails-y是第一遍。

编辑:你知道,我想它的越多,我不知道维持经理/租户/业主在单独的模块功能的好处确实是。在我以前作为Java/C#人的化身中,我会一直关注的是所有关于SRP/IOC和完全分离的问题。但是,在Ruby和Rails中,它并不是什么大事,因为它是动态类型的,耦合并没有那么大,或者至少是静态类型环境下的同类问题。将所有单独的角色功能放在单个用户模型中,而不用担心模块,至少目前还不行。

我在这里篱笆,并欢迎来自其他人的意见。对我来说,与Java类/包或.NET类/程序集相反,Ruby类的好处之一是可以随时重构,而不用担心类,包,命名空间,dll或jar是什么耦合到另一个等等。我不是说SRP在Ruby中不重要,完全不是。但我并不像过去那样偏执。

编辑:保罗·拉塞尔提出了一个很好的观点。我认为你应该认真考虑允许多个租户/经理/房东每个财产。在Rails中,这可以通过关系表和has_many表示:通过关联,加上STI来描述不同类型的关系。我也认为有必要扭转用户(作为承租人)和财产之间的关系。物业可以有多个租户,但租客不能住在一个以上的房产。 (或者他们可以......看起来不对,但是......)

也许像(这是非常快速和肮脏的,所以请原谅所有丢失的细节请):

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 

就像我说的,这是快速和肮脏的,高层次的第一阶段。请注意,我们现在创建了可以包含管理器和所有者特定功能的管理权和所有权角色,从而消除了是否将这些功能分散在单独模块中的前两难局面。

**请注意,我也倒转了Tenant/Property角色 - 我认为这是对您的域进行必要的更改。显然一个住宅可以有多个租户。在我看来(目前)您可以在用户模型上保留特定于租户的功能。

+0

+1 decl_auth看起来不错! – SingleNegationElimination 2010-10-18 05:59:31

3

让我感到震惊的是,你现在的模型禁止多个租户或房东在一个给定的财产,并假定这些关系是永久的。

在英国,至少有一种情况是房产属于已婚夫妇,因此可能有多个房东(比如我的妻子和我在这个位置)。同样,对于某些类型的出租来说,在单一房产中有多个租户是非常普遍的。

如果你认为我在这里,值得考虑在用户和属性之间引入一个'user_property_role'模型对象。然后,您将拥有user_property_role和user_property_role的has_many关系。如果此角色具有可以设置为的“关系类型”字段'landlord',那么你可以使用has_many:through和named scopes(在user_property_role)对象来做例如property.users.landlords或property.users.tenants。

采取这种方法也可以让你做这样的事情,例如给这些关系的'开始'和'结束'日期,记录一个事实,即一个属性有多个租户随着时间的推移,或者一个属性可能由随着时间的推移不同人再次,您应该可以将其构建到一组命名范围中,以便您可以执行property.users.current.tenants或甚至user.properties.current.tenants。

希望有帮助,并不只是增加你的困惑!

+0

是的,这是一个很好的观点。用户和属性之间的关系很多,但不是多对一。我认为这适用于美国和英国。 – 2010-10-18 06:20:59

+0

@Paul Russell,这可能与范围有关吗?我尝试了类似的方法(用户has_many:accounts,通过:: roles),它给出了一个缺少错误的方法。所以'def self.managed在角色模型中的where(:type ==“Ownership”)end'似乎不起作用!我无法调用user.accounts.managed,因为managed是在Role模型中,而不是Account模型! – Mohamad 2012-05-04 22:57:44