Nathan Long
Nathan Long

Reputation: 125902

How can I override a module's singleton method?

Given a module with a singleton method like this:

module Foo
  class << self
    def bar
      puts "method bar from Foo"
    end
  end
end

How can I override Foo.bar using another module?

Upvotes: 3

Views: 1877

Answers (3)

Guilherme Bernal
Guilherme Bernal

Reputation: 8293

This code

module Foo
  class << self
    def bar
      puts "method bar from Foo"
    end
  end
end

is equal to

class << Foo
  def bar
    puts "method bar from Foo"
  end
end

that is also equal to

def Foo.bar
  puts "method bar from Foo"
end

So, you can call it to redefine this method everywhere where Foo is defined (ever withing another module, without including Foo).

Upvotes: 3

Nathan Long
Nathan Long

Reputation: 125902

Extend and alias

My problem was that I forgot to think through the inheritance chain. I was looking for a way to override the method by modifying the inheritance chain, but that's not possible.

The reason is that bar is defined on Foo itself, so it never looks up its inheritance chain for the method. Therefore, to change bar, I have to change it on Foo itself.

While I could just re-open Foo, like this:

module Foo
  def self.bar
    puts "new foo method"
  end
end

... I prefer a way to be able to wrap the original bar method, as though I were subclassing and could call super. I can achieve that by setting up an alias for the old method.

module Foo
  class << self
    def bar
      "method bar from Foo"
    end
  end
end

puts Foo.bar # => "method bar from Foo"

module FooEnhancement

  # Add a hook - whenever a class or module calls `extend FooEnhancement`,
  # run this code
  def self.extended(base)

    # In the context of the extending module or class 
    # (in this case, it will be Foo), do the following
    base.class_eval do

      # Define this new method
      def self.new_bar
        "#{old_bar} - now with more fiber!"
      end

      # Set up aliases. 
      # We're already in the context of the class, but there's no
      # `self.alias`, so we need to call `alias` inside this block
      class << self

        # We can call the original `bar` method with `old_bar`
        alias :old_bar :bar

        # If we call `bar`, now we'll get our `new_bar` method
        alias :bar :new_bar
      end
    end

  end

end

# This will fire off the self.extended hook in FooEnhancement
Foo.extend FooEnhancement

# Calls the enhanced version of `bar`
puts Foo.bar # => 'method bar from Foo - now with more fiber!'

Upvotes: 4

derp
derp

Reputation: 3030

You can do the following:

module Foo
    class << self
        def bar
            puts "method bar from Foo"
        end
        def baz
            puts "method baz from Foo"
        end
    end
end
module Foo2
    def Foo.bar
        puts "new version of bar"
    end
end
include Foo
Foo.baz #=> method baz from Foo
Foo.bar #=> new version of bar

Or instead of naming it Foo2 simply re-open Foo.

module Foo
    class << self
        def bar
            puts "method bar from Foo"
        end
        def baz
            puts "method baz from Foo"
        end
    end
end
module Foo
    class << self
        def bar
            puts "new version of bar"
        end
    end
end
include Foo
Foo.baz #=> method baz from Foo
Foo.bar #=> new version of bar

Upvotes: 0

Related Questions