我想要一个用户系统设置来反映以下层次结构:
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
我已经有一个用户模型,其中设置了Devise和CanCan以处理身份验证和授权,因此我知道如何使用角色将用户类型限制为特定操作,等等.
我失去的是如何在我的Rails代码和数据库中组织这些子类关系.从上面可以看到,Agent,Manager和Administrator都共享User中包含的信息,但每个信息都有与之关联的附加功能和信息.
我读过一些关于STI,polymorphic associations和self-referential associations的内容.
如果我使用STI,那么User表必须包含我所有[Agent / Manager / Administrator]特定信息的字段,对吧?这会让我的用户表变得庞大,这是我想避免的.相反,如果我使用多态,那么我不必在所有其他类型的User子类表中复制User中的所有公共信息吗?
并且为了增加我的困惑,我无法理解上述问题的答案如何与子类之间的关系一起工作(例如,一个Manager has_many Agents,但两者都是User的子类……? ?).
我真的很感谢有人通过一个详细的答案让我直截了当,该答案适当考虑了代码的可读性和数据完整性,这简单地解释了(好像是一个Rails新手)为什么A是最好的方法,为什么B或n是 – 通过比较 – 对于这种情况不是一个好的方法,并且提供了实现上述关系的示例代码.我想解决这个问题,但更重要的是,我想了解解决方案的工作原理!
解决方法
到目前为止,最好的方法是在这个问题上完全使用TDD / BDD,让测试/规格驱动你的设计.并且不要害怕重构你是否找到了更好的方法.大多数时候,只有在尝试了几个错误的解决方案之后才能看到正确的解决方案.你会了解边缘情况.正如沃德·坎宁安(Ward Cunningham)在其“技术债务”(Technical Debt)中所说的那样:“事后重构就好像你从一开始就知道自己在做什么”.尽管如此,请务必进行验收测试以验证其行为.
更具体地解决您的问题.还有第三个选项,即完全拆分类,每个类都有自己的表.我在我目前的项目中尝试过,我喜欢它.如果在您的业务领域中没有意义,则无需定义类似用户的内容.如果他们有共享行为,请使用mixins.最重要的警告是让他们再次通过相同的表单登录并不是直截了当的.
我有管理员,招聘人员,供应商和Visistor模型.它们都是独立的模型,与mixin共享一些行为.他们都有自己的命名空间控制器来行动.例如,管理员的所有操作都在Backend命名空间中.还有一个名称空间的ApplicationController. Backend :: ApplicationController只是指定before_filter:authorize_admin!.没有转换,没有复杂的案例陈述,没有.
你需要特别注意约定.如果您在模型中使用相同的名称,您的mixins可以变得非常简单.阅读ActiveSupport :: Concern以使mixins更易于使用.我有这样的mixin:
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和关系:它们也很好地协同工作.您可以定义从一个子类到另一个子类的关系.它将按预期工作.