简体   繁体   中英

Ruby prepend doesn't work for class method

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.

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

Someone can comment on when this was introduced, but my personal experience and also suggested by https://stackoverflow.com/users/256970/cary-swoveland in comments section of the selected answer, you can always do

class Source
  def self.hello
    puts "hello"
  end
end

module Extension
  def hello
    # you're also allowed to call super from here
    puts "world"
  end
end

Source.singleton_class.prepend Extension

Now if you call Source.hello , the method from the Extension module will be called.

This is also valid for overriding Module's static/class methods But I've mentioned the code explicitly because I've rarely come accross this solution on S/O.

module Source
  def self.hello
    puts "hello"
  end
end

module Extension
  def hello
    # you're also allowed to call super from here
    puts "world"
  end
end

Source.singleton_class.prepend Extension

With include or prepend you can only gain access to a module's instance methods 1 . 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.

class Foo
  singleton_class.prepend ClassMethods

  def self.hello
    puts "hi"
  end 
end
module ClassMethods
  def hello
    puts "ho"
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM