12

在过去的几个小时里,我一直在努力思考我应该走哪条路。我有一个通知模型。到目前为止,我一直使用 notification_type 列来管理类型,但我认为为通知类型创建单独的类会更好,因为它们的行为不同。

目前,可以通过 3 种方式发送通知:短信、推特、电子邮件

每个通知将具有:

id
subject
message
valediction
sent_people_count
deliver_by
geotarget
event_id
list_id
processed_at
deleted_at
created_at
updated_at

似乎 STI 是一个不错的候选人,对吧?当然 Twitter/SMS 不会有主题,Twitter 不会有 sent_people_count, valediction。我想说在这种情况下,他们共享大部分领域。但是,如果我为 twitter 添加一个“reply_to”字段并为 DM 添加一个布尔值怎么办?

我的观点是,现在 STI 是有道理的,但在这种情况下,我将来可能会因为不只是从 MTI 开始而自责吗?

更复杂的是,我想要一个 Newsletter 模型,它是一种通知,但不同之处在于它不会使用 event_id 或 Deliver_by。

我可以使用大约 2/3 的通知基类字段来查看通知的所有子类。STI 是不是很简单,还是我应该使用 MTI?

4

4 回答 4

20

您是否考虑过混合模型方法?

您对核心通知字段使用单表继承的地方。然后将所有唯一项卸载到属于您的通知子类/与您的通知子类有一种关系的特定表/模型。

设置的开销要多一些,但是一旦定义了所有类和表,它就会变得非常干燥。似乎是一种非常有效的存储方式。通过急切加载,您不应该对数据库造成太大的额外压力。

出于本示例的目的,我们假设电子邮件没有唯一的详细信息。这是它的映射方式。

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 没有经过很好的测试,尽管它在我的用例中有效。

于 2009-10-28T01:43:44.747 回答
3

鉴于信息有限,我会说坚持使用 STI。

关键问题是:您的应用程序中是否存在您希望将所有类型的通知一起考虑的地方?如果是这样,那么这是您想要坚持使用 STI 的强烈迹象。

于 2009-10-28T14:06:17.873 回答
2

我知道这很旧,但是在提出解决方案之后,我看到了可以在任何地方使用它的潜在答案!我最近分叉了一个很有前途的项目来在 Rails 中实现多表继承和类继承。我花了几天时间对其进行快速开发、修复、评论和文档,并将其重新发布为 Rails 的 CITIER 类继承和表继承嵌入。

我认为它应该允许你通过简单地构建 Twitter、Email 和 SMS 从 Notification 继承的模型来做你需要的事情。然后让 Notifications 的迁移只包含公共属性,而三个子类型的迁移包含它们的唯一属性。

甚至在根 Notification 类中定义一个函数并在子类中重载它以返回不同的东西。

考虑看看:https ://github.com/PeterHamilton/citier

我发现它非常有用!我会(顺便说一句)欢迎在问题和测试、代码清理等方面为社区提供任何帮助!我知道这是很多人会欣赏的。

但是请确保您定期更新,因为就像我说的那样,它每天都在改进/发展。

于 2011-04-28T23:36:52.037 回答
1
has_one :details, :class_name => "#{self.class.name}Detail"

不起作用。类定义上下文中的 self.class.name 是 'Class' 所以 :class_name 始终是 'ClassDetail'

所以它必须是:

has_one :details, :class_name => "#{self.name}Detail"

但是非常好的主意!

于 2009-11-13T08:14:44.533 回答