你是否考虑过采用混合模型的方法?
使用单表继承来处理核心通知字段,然后将所有唯一的项目转移到特定的表/模型中,并在这些通知子类与其所属关系中使用belongs to/has one关系。
这需要更多的设置工作,但是一旦定义了所有的类和表,它就变得相当DRY。看起来是一种相当有效的存储方式。通过急切加载,不应该对数据库造成太大的额外负担。
为了举例说明,假设电子邮件没有独特的详细信息。这是如何映射出来的。
class Notification < ActiveRecord::Base
...
def self.relate_to_details
class_eval <<-EOF
has_one :details, :class_name => "#{self.name}Detail"
accepts_nested_attributes_for :details
default_scope -> { includes(:details) }
EOF
end
end
class SMS < Notification
relate_to_details
...
end
class Twitter < Notification
relate_to_details
...
end
class Email < Notification
...
end
class SMSDetail < ActiveRecord::Base
belongs_to :SMS, :class_name => "SMS"
...
end
class TwiterDetail < ActiveRecord::Base
belongs_to :twitter
...
end
每个详细表都将包含一个通知ID和仅形成通信需要但未包含在通知表中的列。虽然这会导致额外的方法调用以获取特定媒体的信息。
“这很好,但你认为这是必要的吗?”
在设计方面,只有极少数事情是必要的。随着CPU和存储空间成本的降低,必要的设计概念也会降低。我提出了这个方案,因为它提供了STI和MTI的最佳结合,并消除了它们的一些弱点。
就优势而言:
这种方案提供了STI的一致性。不需要重新创建表格。
链接表格避免了75%的行中存在大量空列的问题。您还可以轻松创建子类。如果新类型没有完全涵盖基本通知字段,则只需要创建匹配的详细信息表即可。它还使对所有通知的迭代简单易行。
从MTI中,您可以获得存储节省和满足类需求的定制化简易性,而无需为每种新通知类型重新定义相同的列。只需要定义唯一的列即可。
但是,这种方案也带有STI的主要缺陷。该表格将替换4张表。一旦它变得巨大,可能会开始导致减速。
简短的回答是,不,这种方法并非必要。我认为这是以最DRY的方式高效地处理问题的方式。在短期内,STI是解决它的方式。在很长一段时间内,MTI是前进的方式,但我们谈论的是您达到数百万通知的时候。这种方法是一种很好的中间地带,易于扩展。
详细的gem
我已经建立了一个基于您的解决方案的gem:
https://github.com/czaks/detailed。使用它,您可以将Notification类简化为:
class Notification < ActiveRecord::Base
include Detailed
end
其余的部分按照之前的方式处理。
额外的好处是,现在您可以直接访问(读取、写入、关联)子类特定的属性:`notification.phone_number`,而无需使用 `notification.details.phone_number`。您还可以在主类和子类中编写所有代码,使 Details 模型保持为空。您还将能够在大型数据集上执行较少的查询(在上面的示例中为 4,而不是 N+1),使用 `Notification.all_with_details` 而不是常规的 `Notification.all`。请注意,目前这个 gem 并没有经过很好的测试,尽管它适用于我的用例。