在 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
杂志有name
、publisher
和is_deleted
列。
这本书只有name
,publisher
- 没有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:不能包含 pg_search Gem 提供的 PgSearch 模块
在 Rails 中使用 config.assets.precompile 在子目录中包含资产