【问题标题】:Ruby On Rails User Model for multiple types多种类型的 Ruby On Rails 用户模型
【发布时间】:2011-04-26 18:36:47
【问题描述】:

我从多年的 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

这里是创建属性表的代码

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。

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

感谢您提供的任何见解。

【问题讨论】:

    标签: ruby-on-rails model


    【解决方案1】:

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

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

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

    从 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 :through 关联来表达,加上 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
    

    就像我说的,这是快速而肮脏的,高水平的第一关。请注意,我们现在已经创建了可以包含经理和所有者特定功能的管理和所有权角色,从而消除了之前关于是否将该功能单独存储在单独模块中的困境。

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

    【讨论】:

      【解决方案2】:

      让我印象深刻的是,您当前的模型禁止在给定的财产上拥有多个租户或房东,并假设这些关系是永久性的。

      至少在英国,房产由已婚夫妇拥有的情况很常见,因此可能有多个房东(例如,我和我的妻子就处于这种情况)。同样,对于某些类型的出租,一个物业有多个租户是很常见的。

      如果您认为我在这里,值得考虑在用户和属性之间引入一个“user_property_role”模型对象。然后,您将拥有从用户和属性到 user_property_role 的 has_many 关系。如果此角色有一个“关系类型”字段,您可以设置为例如'地主',然后您可以使用 has_many :through 和命名范围(在 user_property_role 上)对象来执行例如property.users.landlords 或 property.users.tenants。

      采用这种方法还可以让您执行以下操作,例如为这些关系指定“开始”和“结束”日期,例如,记录一个物业随着时间的推移有多个租户的事实,或者一个物业可能由不同的租户管理。人们随着时间的推移。同样,您应该能够将其构建到一组命名范围中,以便您可以执行例如property.users.current.tenants 甚至 user.properties.current.tenants。

      希望对您有所帮助,而不仅仅是增加您的困惑!

      【讨论】:

      • 是的,这是一个很好的观点。用户和属性之间的关系是多对可能,而不是多对一。我认为这对美国和英国都适用。
      • @Paul Russell,这可能与范围有关吗?我尝试了类似的方法(User has_many :accounts, through: :roles),它给出了一个方法丢失错误。所以角色模型中的def self.managed where(:type == "Ownership") end 似乎不起作用!我无法调用 user.accounts.managed,因为 managed 是 Role 模型,而不是 Account 模型!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      • 2020-04-23
      • 1970-01-01
      • 2021-03-02
      • 1970-01-01
      相关资源
      最近更新 更多