stooshie45
stooshie45

Reputation: 69

Override gem behaviour

I'm using the acts_as_bookable gem for some basic reservation/booking stuff in a Rails app, and I need to add an additional validation to the Booking model that the gem creates.

What I mean by that is, inside the gem, located at lib/acts_as_bookable/booking.rb is the following module/class:

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

Which is fine. However, I want to add an additional piece of logic that stops a booker from booking the same instance of a bookable. Basically, a new validator.

I thought I could just add a file in my /models directory called acts_as_bookable.rb and just modify the class like this:

module ActsAsBookable
  class Booking
    validates_uniqueness_of :booker, scope: [:time, :bookable]
  end
end

But this doesn't work. I could modify the gem itself (I've already forked it to bring a few dependencies up to date, since it's a pretty old gem) but that doesn't feel like the right solution. This is logic specific to this app's implementation, and so my gut feeling is that it belongs in an override inside this specific project, not the base gem.

What am I doing wrong here? And is there a better/alternative approach that would be more suitable?

Upvotes: 0

Views: 407

Answers (1)

max
max

Reputation: 101811

A clean way to create monkeypatches/augmentations to objects outside of your control is to create a seperate module:

module BookingMonkeyPatch
  extend ActiveSupport::Concern
  included do
    validates_uniqueness_of :booker, scope: [:time, :bookable]
  end
end

This lets you test the monkeypatch seperately - and you can "turn the monkeypatch on" by including the module:

ActsAsBookable::Booking.include(BookingMonkeyPatch)

This can be done in an initializer or anywhere else in the lifecycle.

Altough if bookable is a polymorpic assocation you need to use:

validates_uniqueness_of :booker_id, scope: [:time, :bookable_id, :bookable_type]

The uniqueness validation does not work correctly when just passed the name of an assocation as it creates a query based on database columns. This is an example of a leaky abstraction.

See:

Upvotes: 5

Related Questions