在 Rails 中,对常量使用模型方法而不是 Active Record 列有什么缺点?

In Rails, what are the disadvantages of using model methods for constants, instead of Active Record columns?

我正在构建一个 Rails 应用程序,它将具有 非常 大量使用单一 table 继承的模型。对于模型子类,我希望能够为 namedescription 之类的东西设置常量值。我知道我可以使用 attribute 设置默认值,如下所示:

class SpecialWidget < Widget
  attribute :name, :string, default: "Special Widget"
  attribute :description, :text, default: "This is an awfully important widget."
end

据我了解,这里的优点是通过将默认值存储在数据库中,我保留了使用 #order 按名称排序和分页的能力。但是像这样将常量存储在数据库中似乎很糟糕。使用常量方法似乎更好,像这样:

class SpecialWidget < Widget
  def name
    "Special Widget"
  end

  def description
    "This is an awfully important widget."
  end
end

事实上,这就是我最初所做的,但后来我读到了这样的帖子 (one, two, ),其中指出如果我想做一些好的事情,比如按方法排序,我' d 必须将整个 Widget.all 加载到内存中,然后进行普通的 Ruby 排序。

我的应用程序大量围绕这些 STI 模型构建,我肯定必须按常量排序,例如 name。对排序和分页的担忧是否会导致我将来后悔使用方法的重大缺点,或者差异可以忽略不计?我还有什么 disadvantages/problems?我真的很想能够使用方法而不是将常量存储在数据库中,如果可能的话,不会影响我的应用程序的性能。

这完全取决于您的数据的形状以及您希望如何使用它。您没有提供足够的上下文细节来保证我的建议适用于您的情况,但这是专门设计用于 95% 以上所有情况的建议。

把数据放到关系型数据库里就可以了

数据库是您域中所有动态且需要持久化的事物的存储,即状态。它应该是内部一致的、有意义的自我描述和结构良好的,以便充分利用关系数据库的强大功能来灵活地操作和表示复杂的相互关联的数据。

根据你所说的,假设有一堆不同的“小部件类型”使用 Rail 的 STI 实现和 type 列来实现,我会建模 WidgetSpecialWidget 在数据库中像这样:

widgets
id | type
-------------------
 1 | 'Widget'
 2 | 'SpecialWidget'
 3 | 'Widget'
 4 | 'Widget'

widget_types
type            | name             | description
--------------------------------------------------------------
'Widget'        | 'Normal Widget'  | 'A normal widget.'
'SpecialWidget' | 'Special Widget' | 'This is an awfully important widget.'

您将这些值称为“常量”,但它们真的吗?在您的域中,它们 永远不会 会像 Matth::PI 的值永远不会改变一样改变吗?还是会更改描述、重命名小部件、添加小部件和过期小部件?在不确定的情况下,我会假设它们实际上不是 Constant.

namedescription 作为方法有效地将 widget_types table 存储在您的应用程序源代码中,将数据 移出 你的数据库。如果你真的负担不起额外的毫秒数,一个简单的 JOIN 用于每个 Widget 上的两个小字符串,那么只需在应用程序上将完整的 widget_types table 加载到缓存中启动,它将执行与在源代码中保存它相同的操作。

这个模式更加规范化(产生benefits),数据本身描述了所有我需要知道的,正如你所指出的,我可以灵活地操作那些数据(重要的是因为你会“绝对必须排序”)。这种形式的数据也可以扩展以适应未来的变化。

再说一遍:数据库存储结构化数据,目的是按需灵活操作——你可以动态地构造查询,数据库可以回答它。

真的不想把数据放到数据库里

好吧...然后每次要对其进行操作时,您都必须将该数据传递到数据库。你可以这样做:

SELECT w.id, w.type, wt.name
FROM widgets w
INNER JOIN (
  VALUES ('Widget', 'Normal Widget'), ('SpecialWidget', 'Special Widget')
) wt(type, name) ON wt.type = w.type
ORDER BY wt.name

VALUES 表达式创建一个临时 table 映射 class 到名称。通过传入该映射并(每次)加入它,您可以告诉数据库 ORDER BY 它。

将默认值存储在数据库中有很多好处和很少的缺点。但如果它困扰你,你可以通过像这样构造你的排序来获得类似的排序效率:

class SpecialWidget < Widget
  DefaultAttrs = {name: 'Special Widget', description: 'This is... etc'}
end

class Widget < ApplicationRecord
  def self.sort_by_name
    types = pluck(:type).uniq
    case_statements = types.map{|type| "WHEN '#{type}` THEN `#{type.constantize.const_get(:'DefaultAttrs')[:name]}'"
    case_sql = "CASE type #{case_statements.join(' ') END"
    order(case_sql)
  end
end

...不是很优雅,但它完成了工作!

将常量放在数据库中可能更好!