Ruby On Rails多种类型的用户模型

时间:2010-10-18 04:19:39

标签: ruby-on-rails model

我正在学习来自多年c#和MSSQL的RoR。

我选择了一个项目来为我的兄弟建立一个网站,他是一名出租物业经理。我认为这应该是相当容易的,因为模型应该是直截了当的,但它认为我可能在思考一切,或者我在放弃“旧”方式时遇到了麻烦。无论如何这里是问题所在。我开始只有两个模型(用户和属性)。属性模型很简单,用户不是那么多。我想我们系统中有三种类型的用户。租户,业主和经理(我的兄弟将是唯一的经理,但我认为我会设计它成长)他管理几个业主的房产,每个业主可以拥有许多房产。每个房产将拥有一个房主,一个房客和一个房屋。

租户将能够登录并只看到他们租用的房产可能会填写维护请求或类似的东西......(此时没有真正要求甚至让租户登录系统但我认为它会是一个很好的锻炼)

对于所有者来说同样的事情,他们都不需要访问系统(他们雇用我的兄弟,所以他们不必参与)但我认为这可能是好的,并且再次是一个很好的练习。

我使用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

以下是创建Properties表的代码

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。

我不知道这是否重要,但我认为管理员/经理将负责创建用户。这不是自我注册类型的网站。经理将在他新的所有者注册时添加新的所有者,并且租户也会这样做。这样可以更容易地确定他正在创建的用户类型。

感谢您的任何见解。

2 个答案:

答案 0 :(得分:8)

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

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

为此,我可以创建一个用户工厂,可以检查用户所扮演的各种角色,并使用相应的模块扩展该用户的singleton class:对于具有租户属性的用户,使用Tenant模块进行扩展,对于具有托管属性等的用户,使用Manager模块进行扩展。

从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中并不重要,根本不是。但我对此并不像以前那样偏执。

编辑:Paul Russell提出了一个很好的观点。我认为你应该认真考虑允许每个房产多个租户/经理/房东。在Rails中,这可以通过关系表和has_many:通过关联来表达,再加上STI来描述不同类型的关系。我还认为有必要颠倒User(作为Tenant)和Property之间的关系。物业可以拥有多个租户,但租户不能住在多个物业。 (或者他们可以?看起来不对,但......)

也许类似的东西(这是非常快速和肮脏的,所以原谅任何错过的细节请):

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
像我说的那样,这是快速而又肮脏的,高级别的第一次通过。请注意,我们现在已经创建了Managership和Ownership角色,这些角色可以包含Manager和Owner特定的功能,消除了之前的困境,即是否在单独的模块中实现该功能。

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

答案 1 :(得分:3)

让我感到震惊的是,您当前的模型禁止在特定财产中拥有多个租户或房东,并假设这些关系是永久性的。

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

如果您认为我就在这里,那么值得考虑在用户和属性之间引入'user_property_role'模型对象。然后,您将拥有从user和property到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。

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