In Ruby, is there a way to call a method when any other method of the class is called? For example,
class Car
def repair
puts "Repaired!"
end
def drive
# content
end
def checkup
# content
end
end
In this example, if I call any method on an instance of Car
, I should always call the repair
method. How do I do this in Ruby?
NOTE: I also do want repair
called in built-in methods, too, like Carinstance.class
should call repair
, too.
In Ruby, a method provides functionality to an Object. A class method provides functionality to a class itself, while an instance method provides functionality to one instance of a class. We cannot call an instance method on the class itself, and we cannot directly call a class method on an instance.
In Java, a method can be invoked from another class based on its access modifier. For example, a method created with a public modifier can be called from inside as well as outside of a class/package. The protected method can be invoked from another class using inheritance.
I have assumed that you want Car#repair
to be invoked after each of Car
's other instance methods have returned. I see that you have added a requirement that other methods also invoke repair
. I have added a few remarks at the end about extending this to include built-in instance methods.
The approach I've taken is to make use of BasicObject#method_missing:
class Car
def repair
puts "Repaired!"
end
def drive
puts "Drive!"
end
def checkup
puts "Checkup!"
end
def method_missing(m, *args)
if @@ims.key?(m)
ret = send(@@ims[m], *args)
repair
ret
else
super
end
end
@@ims = instance_methods(false).each_with_object({}) do |m,h|
next if (m == :repair || m == :method_missing)
saved_name = "_#{m}"
alias_method saved_name, m
h[m] = saved_name
remove_method(m)
end
end
car = Car.new
car.repair
Repaired!
car.drive
Drive!
Repaired!
car.checkup #
Checkup!
Repaired!
car.wash # => in `method_missing': undefined method `wash'...
When class Car
is parsed, after all the instance methods have been constructed, the following operations are performed, which I explain with an example:
instance_methods(false) # => [:repair, :drive, :checkup, :method_missing]
each_with_object({})
creates a hash (initially empty), referred to by the block variable h
(more on this later).
next if (m == :repair || m == :method_missing)
causes :repair
and :method_missing
to be skipped.
When m => :drive
, the following three statements effectively rename :drive
to :_drive
and add :drive" => "_drive"
to the hash h
.
each_with_object
returns
@@ims = {:drive=>"_drive", :checkup=>"_checkup"}
and now
instance_methods(false) # => [:repair, :method_missing, :_drive, :_checkup]
Because there is no longer a method :drive
, Car.new.drive
invokes method_missing(:drive)
. The latter finds that @@ims
has a key :drive
, so it uses send
to invoke :_drive
, invokes :repair
and returns the return value of :_drive
. If method_missing
is passed a method that is not a key of @@ims
, super
is invoked and an exception is raised.
In a now-removed edit I suggested that to include built-in instance methods, one need only change instance_methods(false)
to instance_methods
, but warned about possible unintended side-effects. @Kal pointed out that built-in instance methods cannot be removed, so that approach won't work. That's just as well--one should not mess with Ruby in that way. I obviously didn't test my assertion. Shame!
class Car
def self.default_method
instance_methods(true).each do |meth|
alias_method meth, :repair
end
end
def initialize
self.class.default_method
end
def repair
puts "Repaired!"
end
def drive
# content
end
def checkup
# content
end
end
car = Car.new
car.drive # => Repaired!
car.checkup # => Repaired!
car.class # => Repaired!
Note that redefining the built-in methods generates some warnings:
# => untitled 5:6: warning: redefining `object_id' may cause serious problems
# => untitled 5:6: warning: redefining `__send__' may cause serious problems
Edit: Oops, I posted this too quickly and didn't spot the problem. It calls repair
, but not the original methods. I knew it seemed too easy! I think I'm out of my depth with this one. :-) (Note: I thought Cary's approach was really clever, and it does work for your own methods, but it looks like he hit a dead end with the built-in methods, and in any case, it alters methods in a way that you really shouldn't be attempting with built-in methods).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With