您是否考虑过混合模型方法?
您对核心通知字段使用单表继承的地方。然后将所有唯一项卸载到属于您的通知子类/与您的通知子类有一种关系的特定表/模型。
设置的开销要多一些,但是一旦定义了所有类和表,它就会变得非常干燥。似乎是一种非常有效的存储方式。通过急切加载,您不应该对数据库造成太大的额外压力。
出于本示例的目的,我们假设电子邮件没有唯一的详细信息。这是它的映射方式。
class Notification < ActiveRecord::Base
# common methods/validations/associations
...
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
# sms specific methods
...
end
class Twitter < Notification
relate_to_details
# twitter specific methods
...
end
class Email < Notification
# email specific methods
...
end
class SMSDetail < ActiveRecord::Base
belongs_to :SMS, :class_name => "SMS"
# sms specific validations
...
end
class TwiterDetail < ActiveRecord::Base
belongs_to :twitter
# twitter specific validations
...
end
每个详细信息表都将包含一个通知 ID,并且仅包含未包含在通知表中的通信形式所需的列。虽然这意味着需要额外的方法调用来获取媒体特定信息。
很高兴知道,但您认为有必要吗?
就设计而言,很少有东西是必需的。随着 CPU 和存储空间成本的下降,这些必要的设计理念也随之下降。我提出这个方案是因为它同时提供了 STI 和 MTI 的优点,并消除了它们的一些弱点。
就优势而言:
该方案提供了 STI 的一致性。使用不需要重新创建的表。链接表中有几十个列,其中 75% 的行是空的。您还可以轻松创建子类。如果您的新类型没有完全被基本通知字段覆盖,您只需要创建一个匹配的详细信息表。它还保持对所有通知的简单迭代。
从 MTI,您可以节省存储空间并轻松定制以满足班级的需求,而无需为每种新的通知类型重新定义相同的列。只有独一无二的。
然而,这个方案也继承了 STI 的主要缺陷。该表将替换 4。一旦它变得巨大,它就会开始导致减速。
简短的回答是,没有这种方法是没有必要的。我认为这是有效处理问题的最干燥的方法。在很短的时间内,STI 是实现它的方法。从长远来看,MTI 是要走的路,但我们谈论的是您会收到数百万条通知。这种方法是一些很好的中间立场,很容易扩展。
详细的宝石
我在您的解决方案上构建了一个 gem:https ://github.com/czaks/detailed 。使用它,您可以将 Notification 类简化为:
class Notification < ActiveRecord::Base
include Detailed
end
其余的走之前的路。
作为额外的好处,您现在可以直接访问(读取、写入、关联)子类特定属性:notification.phone_number
,而无需求助于:notification.details.phone_number
。您还可以在主类和子类中编写所有代码,将 Details 模型留空。您还可以在大型数据集上使用Notification.all_with_details
而不是常规的Notification.all
.
请注意,目前这个 gem 没有经过很好的测试,尽管它在我的用例中有效。