B Seven
B Seven

Reputation: 45943

How to call an instance method defined in a module that is overwritten by another module in Ruby?

class Person
  include Voice
  include Beep
end

module Voice
  def speak
    puts 'speaking from module'
  end

  def original_speak
    # ???
  end
end

module Beep
  def speak
    puts 'beep beep'
  end
end

How to call Voice#speak? For example:

Person.new.voice_speak

I want to add this code to the Voice module, not the Person class or the Beep module.

Use case:
A module that does soft delete:

module Undeletable
  def delete
    # mark document as deleted. This creates a deletion document.
  end

  def restore
    # Delete (for real) the deletion.
  end

  def obliterate
    restore # because we don't want orphaned deletions.

    real_delete # This should call Mongoid#delete
  end
end

def Foo
  include Mongoid::Document
  include Undeletable
end

So, in general, when we call foo.delete, we want to soft delete. However, in rare cases, we want to do a real delete. The module should support both methods.

Upvotes: 0

Views: 84

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Readers: I posted this answer to the original question. The question was subsequently changed. If interested, check the edit history for context.

The steps are as follows.

  • Create a callback in module Voice that is invoked when the module is included in Person. The (class) method for that is Module#included, which has one argument, the class including the module.
  • Included creates an alias original_speakof Person.speak (in Person). This must be done first, while Person#speak is the method speak originally defined on Person.
  • Included then removes the (original) instance method Person#speak, using Module#remove_method (not Module#undef_method), causing Person#speak to become the instance method Voice#speak, which was in effect, right "behind" the original Person#speak prior to the latter's removal.

module Voice
  def self.included(klass)
    klass.send(:alias_method, :original_speak, :speak)
    klass.send(:remove_method, :speak)
  end
  def speak
    puts 'speaking from module'
  end
end

class Person
  def speak
    puts 'speaking from class'
  end
  include Voice
end

Person.instance_method(:speak).owner
  #=> Voice 
Person.instance_method(:original_speak).owner
  #=> Person 
Person.ancestors
  #=> [Person, Voice, Object, Kernel, BasicObject] 

person = Person.new

person.original_speak
speaking from class

person.speak
speaking from module

Upvotes: 1

max pleaner
max pleaner

Reputation: 26768

I see the easiest way as being to add a parameter to the function which overwrites delete. For example:

module Undeletable
  def delete(call_super=false)
   return super if call_super
   return "some custom response"
  end

  def obliterate
    delete(true)
  end
end

The super keyword is important here. If a method is overwritten by another, the original can be called by super. It's also possible to pass arguments to super.

Upvotes: 1

Related Questions