Ruby on Rails中的多表继承与单表继承

时间:2009-10-28 01:18:44

标签: single-table-inheritance ruby-on-rails

过去几个小时我一直在努力思考应该走哪条路。我有一个通知模型。到目前为止,我已经使用了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,告别。我会说在这种情况下他们分享他们的大多数领域。但是,如果我为twitter添加一个“reply_to”字段,为DM添加一个布尔值呢?

我的观点是,现在STI是有道理的,但这是一个案例,我可能会在未来踢我自己而不只是从MTI开始?

为了使事情更复杂,我想要一个通讯模型,这是一种通知,但区别在于它不会使用event_id或deliver_by。

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

4 个答案:

答案 0 :(得分: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是要走的路,但我们谈论的是你达到数百万次通知的地步。这种方法是一些很容易扩展的好中间地带。

详细的宝石

我为你的解决方案构建了一个宝石: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在大型数据集上执行更少的查询(在上面的示例4而不是N + 1中)。

请注意,目前这个宝石测试得不是很好,虽然它可以在我的用例中使用。

答案 1 :(得分:2)

鉴于信息有限,我认为坚持使用STI。

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

答案 2 :(得分:2)

我知道这已经过时了,但在提出解决方案后,我看到了可以在任何地方使用它的潜在答案!我最近分叉了一个有希望的项目来在Rails中实现多表继承和类继承。我花了几天时间对它进行快速开发,修复,评论和文档处理,然后重新发布它作为Rails的CITIER类继承和表继承嵌入。

我认为它应该允许您通过简单地构建Twitter,电子邮件和SMS从Notification继承的模型来完成您所需的操作。然后,通知的迁移仅包含公共属性,三个子类型的迁移包括其唯一属性。

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

考虑一下:https://github.com/PeterHamilton/citier

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

请确保定期更新,但正如我所说,它一直在改善/发展。

答案 3 :(得分: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"

但非常好主意!