在 Rails 中包含模块

Posted

技术标签:

【中文标题】在 Rails 中包含模块【英文标题】:Include module contionally in Rails 【发布时间】:2021-12-25 18:01:58 【问题描述】:

考虑具有重叠字段和功能的多个 ActiveRecord 类,其中许多重叠字段具有相同的验证。我正在尝试共享验证,但如果满足条件(基于模型的非重叠属性之一)则不运行共享代码。

class Book < ApplicationRecord
  include SharedValidation
end

class Magazine < ApplicationRecord
  include SharedValidation
end

module SharedValidation
  extend ActiveSupport::Concern
  include ActiveModel::Validations

  validates_presence_of :name, :publisher, :author
end

假设Magazine.is_deleted 是一个仅限杂志的字段,我们只想在 is_deleted 为 false 时运行共享验证。关于如何在课堂上实现这一点的任何想法?


注意:我尝试通过执行字段检测和评估来修改模块,但不确定这是否有意义或是否有效:

module SharedValidation
  extend ActiveSupport::Concern
  include ActiveModel::Validations

  included do
    proc do |rcd|
      has_deleted_field = self.column_names.include?('is_deleted') 
      
      if (has_deleted_field && !rcd.is_deleted) || !has_deleted_field
        validates_presence_of :name, :publisher, :author
      end 
    end
  end
end

【问题讨论】:

1. 请不要试图评价这个例子的实用性——我不擅长做例子。我将它包含在上面是为了演示技术挑战。 2. 还有比这更多的字段和验证器,这只是一个示例。我最初在每个类中都有验证器,并试图合并以减少名称更改时的错误数量。 validates_* 应该在班级级别,对吧?你试过extend SharedValidation而不是include SharedValidation吗? 我想一个单独的问题可能是 - 如果有这么多重叠,是否有理由不将 STI 与派生“Book”和“Magazine”的基类一起使用。如果你能做到这一点,那么基类将具有共享验证。这可能不适合您的用例,但可能值得考虑。 @mrrogers 好主意。对于模型/表格设计,我没有太多的引导方式。我应该补充一点,我认为模块验证在 included 块内工作,并且不确定在 proc 内。当我可以访问计算机时,我将尝试一个自定义类函数,它可能能够在 if 块中调用 include(并将逻辑从模块中取出)。 我想我有点错过了关于条件验证的内容。这肯定会让事情变得更加棘手。我认为因为这些验证是在课堂上进行的,所以该过程可能不起作用。但也许如果条件是在:if 中定义的,您可以将其传递给validates_* 方法。我可能会试一试。超级有趣的问题。 【参考方案1】:

看起来您可以将条件添加到验证方法中(在 SharedModule 中),而不是有条件地包含模块。

使用您的示例:

class Book < ApplicationRecord
  include SharedValidations
end

class Magazine < ApplicationRecord
  include SharedValidations
end

module SharedValidations
  extend ActiveSupport::Concern
  include ActiveModel::Validations

  def deleted
    return unless self.class.column_names.include?("is_deleted")

    is_deleted
  end

  included do
    validates :name, :publisher, presence: true, unless: :deleted
  end
end

杂志有namepublisheris_deleted 列。 这本书只有namepublisher - 没有is_deleted

而且看起来这个设置有效。

irb> book = Book.new()
=> #<Book id: nil, name: nil, publisher: nil, created_at: nil, updated_at: nil>
irb> book.valid?
=> false
irb> book.errors.full_messages
=> ["Name can't be blank", "Publisher can't be blank"]

irb> magazine = Magazine.new
=> #<Magazine id: nil, name: nil, publisher: nil, is_deleted: nil, created_at: nil, updated_at: nil>
irb> magazine.valid?
=> false
irb> magazine.errors.full_messages
=> ["Name can't be blank", "Publisher can't be blank"]

irb> magazine.is_deleted=true
=> true
irb> magazine.valid?
=> true

【讨论】:

我目前无法对此进行测试,但看起来很有希望。我真的希望我可以在一个条件下而不是每次验证下包装验证。我有很多validates 语句,有些是存在的,有些是值检查,还有一些以其他字段的值为条件。因此,您可以了解为什么成为 DRYer 并评估一次 is_deleted 会很好。 根据您的 cmets,很明显该示例很简单,并没有真正展示全貌。因为所有这些验证都是类级别的函数(稍后作用于实例),所以在你有一个实例之前你不能添加条件是有道理的......有点。但是,有了这个,至少您仍然可以将所有条件验证逻辑放在一个地方。 我确信我犯了 Railsian 失礼,尽管我不再对它进行太多编程。老实说,我不喜欢将验证逻辑与类分开。这感觉很顽皮,但我这次要打破这种犹豫。 我对此进行了测试,效果很好。我不知道在self.class.column_names 中使用了class,我想我可以使用self.attribute_names,但我有点喜欢检查表模式的类。罗杰,你知道validates ... presence: 是否比validates_presence_of 更受欢迎吗?我认为后者有点老,但是当许多字段有不同的验证器(例如,validates_length_of)时,我发现它更容易阅读。 冒着异花授粉的风险,我还问了一个类似的设置问题,您可能会在这里发现简单/有趣的问题:***.com/questions/69977690/…

以上是关于在 Rails 中包含模块的主要内容,如果未能解决你的问题,请参考以下文章

Rails 单个包含多个类

Rails:不能包含 pg_search Gem 提供的 PgSearch 模块

在 Rails 中使用 config.assets.precompile 在子目录中包含资产

如何在 rake 任务中包含模块类?

>=Rails 3.1 如何在资产管道中包含 IE 特定的 YAML-CSS 文件

如何在 Rails 的 JSON 响应中包含 cookie?