Search code examples
ruby-on-railsrubyactivesupportoverridingactivesupport-concern

Overriding methods in an ActiveSupport::Concern module which are defined by a class method in the same module


I have an ActiveSupport::Concern module which looks roughly like the following:

module MyModel
  module Acceptance

    extend ActiveSupport::Concern

    included do
      enum status: [:declined, :accepted]
    end

    def declined!
      self.status = :declined
      # some extra logic
      self.save!
    end

    def accepted!
      self.status = :accepted
      # some extra logic
      self.save!
    end
  end
end

This is only ever going to be included into ActiveRecord classes, hence the use of enum. Basically, I'm overriding the declined! and accepted! methods that are created by ActiveRecord::Enum.enum with some extra, custom logic of my own.

The problem is, this doesn't work, because when I call @model.declined! it justs call the original implementation of declined! and ignores my custom method.

Looks like my custom methods are being included into the calling class before the included block is being run - meaning my custom methods are being overridden by the ones defined by enum, instead of the other way around.

There some easy workarounds in this particular situation (e.g. I could move the call enum back into the including class and make sure it's above the line include MyModel::Acceptance, but I'm wondering if there's a way I can solve this problem while keeping it all in the same module.

Is there any way I can call a class method within included that defines an instance method, then override that instance method from within the same Concern module?


Solution

  • I think you're looking for define_method.

    module MyModel
      module Acceptance
    
        extend ActiveSupport::Concern
    
        included do
          enum status: [:declined, :accepted]
    
          define_method :declined! do
            self.status = :declined
            # some extra logic
            self.save!
          end
    
          define_method :accepted! do
            self.status = :accepted
            # some extra logic
            self.save!
          end
    
        end
      end
    end