我一直在撞墙试图绕过这头,所以任何指导都会非常感激......
我希望用户系统设置能够反映以下层次结构:
User
|- email address
|- password
|- billing information
|- contact information
|- account preferences
|
|=> Agent
|=> - agent-specific information
|=> - has_many Users
|=> - belongs_to Manager
|
|=> Manager
|=> - manager-specific information
|=> - has_many Agents, Users
|
|=> Administrator
|=> - can manage everything
我已经设置了User
模型Devise和CanCan来处理身份验证和授权,因此我知道如何使用角色将用户类型限制为特定操作,以及等等。
我失去的是如何在我的Rails代码和数据库中组织这些子类关系。从上面可以看出,Agent
,Manager
和Administrator
都共享User
中包含的信息,但每个信息都包含与之相关的其他功能和信息。
我读过一些关于STI,polymorphic associations和self-referential associations的内容。
如果我使用STI,则User
表必须包含我所有[Agent
/ Manager
/ Administrator
]特定信息的字段,对吧?这会使我的User
表变大,这是我想避免的。相反,如果我使用多态,那么我不是必须在User
所有其他类型的User
子类表中复制所有公共信息吗?
为了增加我的困惑,我无法理解上述问题的答案如何与子类之间的关系起作用(例如,Manager
has_many Agents
,但两者都是User
的子类...... ??)。
我真的很感谢有人通过一个详细的答案让我直截了当,该答案适当考虑了代码的可读性和数据完整性,这简单地解释了(好像是一个Rails新手)为什么A是最好的方法,为什么B或< em> n - 相比之下 - 对于这种情况来说不是一个好方法,它提供了实现上述关系的示例代码。我想解决这个问题,但更重要的是,我想学习 为什么 解决方案有效!
答案 0 :(得分:8)
我认为没有一个简单的解释为什么任何方法在任何情况下都是最好的。关于Stack Overflow的大量问题是关于如何设计模型之间关系的问题。这是一个复杂的主题,正确的解决方案需要对您正在解决的问题有深入的了解。即使在那时,你可能也不会在前几次做到正确。
到目前为止,最好的方法是在这个问题上完全使用TDD / BDD并让测试/规格驱动你的设计。并且不要害怕重构你是否找到了更好的方法。大多数时候,只有在尝试了几个错误的解决方案之后才能看到正确的解决方案。你会了解边缘情况。正如Ward Cunningham在他的“技术债务”类比中所说的那样:“事后重构,好像你从一开始就知道你在做什么”。尽管如此,请务必进行验收测试以验证其行为。
更具体地解决您的问题。还有第三个选项,即完全拆分类,每个类都有自己的表。我在我目前的项目中尝试过,我喜欢它。如果在您的业务领域中没有意义,则无需定义类似用户的内容。如果他们有共享行为,请使用mixins。最重要的警告是让他们再次登录同一个表格并不是直截了当的。
我有管理员,招聘人员,供应商和Visistor模型。它们都是独立的模型,与mixin共享一些行为。他们都有自己的命名空间控制器来行动。例如,管理员的所有操作都在Backend命名空间中。还有一个名称空间的ApplicationController。 Backend :: ApplicationController只指定before_filter :authorize_admin!
。没有转换,没有复杂的案例陈述,没有。
您需要特别注意惯例。如果您在模型中使用相同的名称,您的mixins可以变得非常简单。阅读ActiveSupport :: Concern以使mixins更易于使用。我有这样的混音:
module Account
extend ActiveSupport::Concern
included do
devise :database_authenticatable, :trackable, :recoverable, :rememberable
end
end
在我的路线中:
devise_for :recruiters
devise_for :suppliers
# etc...
app/controllers/backend/application_controller.rb
看起来像这样:
class Backend::ApplicationController < ::ApplicationController
layout "backend"
before_filter :authenticate_admin!
def current_ability
@current_ability ||= AdminAbility.new(current_admin)
end
end
所以,总结一下。任何架构都可以。 STI和多态性有其自己的位置,但一定要根据您的域建模您的架构。 Ruby是一种非常灵活的语言,您可以利用它来获得优势。 Devise和CanCan是出色的宝石,可以轻松应对这些情况。我向您展示了我正在进行的项目的解决方案。它适用于我,但我不能说它是否适合你。当你觉得自己做出了错误的决定时,不要害怕尝试和重构,而不是继续修补你原来的想法。
PS。说到STI和关系:它们也很好地协同工作。您可以定义从一个子类到另一个子类的关系。它将按预期工作。
答案 1 :(得分:0)
过度思考可能很诱人,但我不相信你想要做的事情非常复杂。
STI可能不是最好的主意,因为您将用户限制为不超过其中一个关键角色。
至于存储会员资格特定信息,我不确定当您为STI添加列时,您的表格会“巨大”。如果它们为null,则它们实际上并不消耗任何资源。如果您不需要选择该数据,则可以考虑使用单个列role_information
,然后使用serialize :role_information
,以便您可以定义哈希并将其存储在列中。
另一种选择是覆盖方法,以便在响应时检查角色成员资格,即:
has_many :agents
def agents
return nil unless self.has_role(:manager)
return self[:agents]
end
将“has_role”替换为您需要检查角色成员资格的任何内容。
您也可以在设置方面执行此操作。
特定功能的授权应该在您的控制器中完成,因此您应该在那时检查角色成员资格。