覆盖 gem 行为

sto*_*e45 0 ruby rubygems ruby-on-rails

我将acts_as_bookablegem 用于 Rails 应用程序中的一些基本预订/预订内容,我需要向Bookinggem 创建的模型添加额外的验证。

我的意思是,在 gem 内部,位于lib/acts_as_bookable/booking.rb以下模块/类:

module ActsAsBookable
  class Booking < ::ActiveRecord::Base
    self.table_name = 'acts_as_bookable_bookings'

    belongs_to :bookable, polymorphic: true
    belongs_to :booker,   polymorphic: true

    validates_presence_of :bookable
    validates_presence_of :booker
    validate  :bookable_must_be_bookable,
              :booker_must_be_booker

    # A bunch of other stuff
  end
end
Run Code Online (Sandbox Code Playgroud)

这很好。但是,我想添加一个额外的逻辑来阻止预订者预订同一个可预订实例。基本上,一个新的验证器。

我以为我可以在我的/models目录中添加一个名为的文件,acts_as_bookable.rb然后像这样修改类:

module ActsAsBookable
  class Booking
    validates_uniqueness_of :booker, scope: [:time, :bookable]
  end
end
Run Code Online (Sandbox Code Playgroud)

但这不起作用。我可以修改 gem 本身(我已经分叉了它以更新一些依赖项,因为它是一个非常古老的 gem),但这并不是正确的解决方案。这是特定于此应用程序实现的逻辑,因此我的直觉是它属于此特定项目内的覆盖,而不是基础 gem。

我在这里做错了什么?是否有更合适的更好/替代方法?

max*_*max 5

为您控制之外的对象创建猴子补丁/增强的一种干净方法是创建一个单独的模块:

module BookingMonkeyPatch
  extend ActiveSupport::Concern
  included do
    validates_uniqueness_of :booker, scope: [:time, :bookable]
  end
end
Run Code Online (Sandbox Code Playgroud)

这使您可以单独测试monkeypatch - 您可以通过包含模块来“打开monkeypatch”:

ActsAsBookable::Booking.include(BookingMonkeyPatch)
Run Code Online (Sandbox Code Playgroud)

这可以在初始化程序或生命周期中的任何其他地方完成。

虽然如果可预订是一个多态关联,您需要使用:

validates_uniqueness_of :booker_id, scope: [:time, :bookable_id, :bookable_type]
Run Code Online (Sandbox Code Playgroud)

仅传递关联名称时,唯一性验证无法正常工作,因为它会创建基于数据库列的查询。这是一个泄漏抽象的例子。

看: