Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby prepend doesn't work for class method

Tags:

ruby

I'm trying to use prepend method in ruby to overwrite methods of a class, here is how my code works:

module PrependedModule
  def self.klass_method
    puts 'PrependedModule klass_method'
  end

  def instance_method_a
    puts 'PrepenedModule instance method'
  end
end

class SomeClass
  prepend PrependedModule

  def self.klass_method
    puts 'SomeClass klass_method'
  end

  def instance_method_a
    puts 'SomeClass instance_method'
  end
end

SomeClass.klass_method
SomeClass.new.instance_method_a

#output:
#SomeClass klass_method
#PrependedModule instance method

#expected:
#PrependedModule klass_method
#PrependedModule instance method

The output of this script is shown above, as we can see, the instance method instance_method_a was overwritten by module PrependedModule, but not the class method klass_method, when I called klass_method on SomeClass, it still executes it's origin method, instead of the one defined in PrependedModule.

I'm confused about this and don't know what happend with class methods when using prepend. It would be a great help if anyone can solve this question for me.

like image 658
Keith.Y Avatar asked Oct 17 '25 11:10

Keith.Y


2 Answers

Singleton classes do not work that way. You need to explicitly prepend the methods to the eigenclass of SomeClass:

module PrependedModule
  module ClassMethods
    def klass_method
      puts 'PrependedModule klass_method'
    end
  end

  def instance_method_a
    puts 'PrepenedModule instance method'
  end
end

class SomeClass
  # prepending to the class
  prepend PrependedModule
  class << self
    # prepending to the eigenclass
    prepend PrependedModule::ClassMethods
  end

  def self.klass_method
    puts 'SomeClass klass_method'
  end

  def instance_method_a
    puts 'SomeClass instance_method'
  end
end
like image 52
Aleksei Matiushkin Avatar answered Oct 19 '25 12:10

Aleksei Matiushkin


With include or prepend you can only gain access to a module's instance methods1. You therefore might ask if there is any reason to define module methods on a module. The answer is a resounding "yes". There are two reasons you might want to do that.

The first has nothing to do with including or prepending the module. You need only look at the module Math to understand why you might want to do that. All methods defined on Math are module methods. These constitute a library of useful functions. They are of course methods, but since all have Math as their receiver, they behave like functions in non-OOP languages.

The second reason is that you might want to define a callback method (aka hook method) on a module that is to be included or prepended by another module. The main ones are Module#included, Module#prepended, Module#extended, Class#inherited and BasicObject#method_missing. The last of these is an instance method; the others are module methods. An example of Module#prepended is given below.

@mudasoba has shown how to confine instance methods to a sub-module Sub of Mod so that Mod::Sub can be prepended (or included) to a class's (or module's) singleton class. A commonly-used pattern for doing that employs the callback Module#prepended. It follows.

module PrependedModule
  module ClassMethods
    def klass_method
      puts 'PrependedModule klass_method'
    end
  end

  def instance_method_a
    puts 'PrepenedModule instance method'
  end

  def self.prepended(mod)
    mod.singleton_class.prepend(ClassMethods)
  end
end

class SomeClass
  def self.klass_method
    puts 'SomeClass klass_method'
  end

  def instance_method_a
    puts 'SomeClass instance_method'
  end

  prepend PrependedModule
end

SomeClass.klass_method
  # PrependedModule klass_method
SomeClass.new.instance_method_a
  # PrepenedModule instance method

1 I've always found it curious that instance methods can be defined on modules (that are not classes), considering that such modules cannot have instances. True, these methods become instance methods in classes that include or prepend the module, but keep in mind that those modules can be included or prepended by other modules (that are not classes) as well. One might therefore expect such methods to have some name other than "instance method". Finding a suitable alternative would be a challenge, however, which is perhaps one reason why that nomenclature has persisted.

like image 31
Cary Swoveland Avatar answered Oct 19 '25 13:10

Cary Swoveland