andy
andy

Reputation: 8875

How to override class_method in rails model concern

How does one override a class method defined in a model concern?

This is a bit tricky since you’re not really overriding a class method right? Because it’s using the concern api of definining class methods in the class_methods block.

so say I have a concern that looks like this:

module MyConcern
  extend ActiveSupport::Concern

  class_methods do
    def do_something
       #some code
    end
  end

end

In model.. how would I override that method so that I could call it like we do with super when using inheritance? So in my model I’d like to go:

def self.do_something
  #call module do_something
end

?

Upvotes: 4

Views: 4001

Answers (3)

Paul Fioravanti
Paul Fioravanti

Reputation: 16793

If you've included MyConcern in the model that defines self.do_something, you should just be able to use super:

module MyConcern
  extend ActiveSupport::Concern

  class_methods do
    def do_something
      puts "I'm do_something in the concern"
    end
  end
end

class UsesMyConcern < ActiveRecord::Base
  include MyConcern

  def self.do_something
    super
  end
end

UsesMyConcern.do_something
# => "I'm do_something in the concern"

If you haven't or don't want to include MyConcern in the model and you want to invoke do_something on the module without creating any intermediary objects, you can change your model to:

class UsesMyConcern < ActiveRecord::Base
  def self.do_something                
    MyConcern::ClassMethods.instance_method(:do_something).bind(self).call
  end
end

UsesMyConcern.do_something
# => "I'm do_something in the concern"

ActiveSupport::Concern.class_methods defines a ClassMethods module in the concern if there isn't one already, and that's where we can find the do_something method.

Upvotes: 6

Ivan
Ivan

Reputation: 235

Why not simply call the module's method: MyConcern.do_something?

I'm not sure if there's an easy of doing super for modules (though I can see why that may be useful).

The next best solution could be doing something like calling #included_modules and manually iterating with #responds_to?:

def self.do_something
  self.super_module(__method__)
end

def self.super_module(method)
  self.included_modules.find { |m| m.responds_to? method }.public_send(method)
end

Upvotes: 1

siegy22
siegy22

Reputation: 4413

The old way using alias_method_chain: https://ernie.io/2011/02/03/when-to-use-alias_method_chain/

The new way (requires > ruby 2.0.0) you really should use this, as there will be a DEPRECATION WARNING when using it in rails 5.0: http://paweljaniak.co.za/2014/09/30/understanding-ruby-module-prepend-and-include/

Upvotes: 0

Related Questions