Ruby - overwrite def method - ruby

How can I overwrite the def method? But it's strange, cause I don't know from where the def method is defined. It's not Module, not Object, not BasicObject (of Ruby 1.9). And def.class don't say nothing ;)
I would like to use something like:
sub_def hello
puts "Hello!"
super
end
def hello
puts "cruel world."
end
# ...and maybe it could print:
# => "Hello!"
# => "cruel world."
Many thanks, for any ideas.

Who told you def is a method? It's not. It's a keyword, like class, if, end, etc. So you cannot overwrite it, unless you want to write your own ruby interpreter.

You could use alias_method.
alias_method :orig_hello, :hello
def hello
puts "Hello!"
orig_hello
end

You can use blocks to do some similar things like this:
def hello
puts "Hello"
yield if block_given?
end
hello do
puts "cruel world"
end

As others have said, def isn't a method, it's a keyword. You can't "override" it. You can, however, define a method called "def" via Ruby metaprogramming magic:
define_method :def do
puts "this is a bad idea"
end
This still won't override the def keyword, but you can call your new method with method(:def).call.
So, there you (sort of) have it.
Note: I have no idea why you'd ever want to define a method called def. Don't do it.

Related

Make methods dynamic in ruby

Define a class as follows. I want to call one_method dynamically. By default, wow.one_method calls the first one_method. If I want to change the behavior of the method, just call redefine.
I can implement the method as a function type property, but that is not what I want.
If I use the following code directly, it would report errors. Could you modify it slightly.
class Wow
def one_method
puts "hello Ruby"
end
def redefine(what="none")
define_method :one_method do
puts what
end
end
end
wow = Wow.new
wow.redefine("Whatever I want.")
You can achieve that via class_eval or instance_eval:
class Wow
def one_method
puts "hello Ruby"
end
def redefine(what="none")
self.class.class_eval do
define_method :one_method do
puts what
end
end
end
end
wow = Wow.new
wow.one_method #=> hello Ruby
wow.redefine("Whatever I want.")
wow.one_method #=> Whatever I want.
Reason is that define_method defines instance method on the receiver and is a class's instance method so you'll have to call it on the eigen class of the object that you want to redefine a method on.
I would recommend achieving your goal in a more canonical way, just redefine the method on the instance itself:
class Wow
def one
:one
end
end
w = Wow.new
w.one
#=> :one
def w.one
:two
end
w.one
#=> :two
Drawbacks are:
your methods lookup table caches will be dropped
the code is becoming more obscure and hard to debug
Alternatives:
I don't know your real problem, but for your particular question it is better to parameterize your one_method method just to receive an argument for puts. Also, you can pass a block, so you will receive more grained control over the behavior.

creating method on fly as a parameter while calling singleton method

How's it possible in ruby ?
class Test
# Creating singleton method
def self.some_singleton_method(param1)
puts param1
end
end
# calling singleton method by creating method on fly as a parameter to it
Test.some_singleton_method def method_name(some_param)
# do something
end
## method_name
I've tried many places looking around, can't come up with an idea how's it's working.
Thanks!
It is possible, since def is keyword, that creates new method in current scope, which is Object since you're calling it on the "top" level, i.e. not inside any class. Starting from Ruby 2.1, def returns method name as a symbol, so your code is actually equivalent to
name = def method_name(some_param)
// do something
end
Test.some_singleton_method(name) # outputs "method_name"
EDIT: Thanks to Cary Swoveland for clarification that def is actually a keyword and not a method.
Here are two ways to do that.
#1
class Test
def self.doit(m)
send(m) yield
end
end
Test.doit(:hello) do
puts 'hi'
end
#=> :hello
Test.new.hello
#=> "hi"`.
#2
class Test
def self.doit(str)
eval(str)
end
end
Test.doit "def hello; puts 'hi'; end"
#=> :hello
Test.new.hello
#=> "hi"`.

method_missing with unquoted string arguments in Ruby - possible?

I'm learning Ruby and want to be able to do this:
Printer.hi there
and have Ruby output
"hi there"
So far I have the following implementation
class Printer
def method_missing(name, *args)
puts "#{name} #{args.join(',')}"
end
end
But this only lets me do
Printer.hi "there"
If I attempt
Printer.hi there
I get a
NameError: undefined local variable or method `there' for main:Object
which makes sense as I haven't ever defined 'there'. Is there a way to make this work though?
No, this is not possible in the form given (as far as I know).
You aren't looking for method missing, you are looking for the equivalent in the Ruby interpreter to capture when it cannot find a given symbol. So while you cannot intercept it there, you can do it inside of a block:
def hi(&block)
begin
yield
rescue NameError => e
e.message =~ /variable or method `(.+)'/
puts "hi #{$1}"
end
end
hi { there } # => hi there
Please note that I feel like a terrible world citizen for showing you this. Please don't use it anywhere, ever.
Yes, there is a way. When you write there without an explicit receiver, the receiver is the self object of that scope. In this case, it is main. Define methods_missing in the main context.
def method_missing(name, *args)
puts "#{name} was called with arguments: #{args.join(',')}"
end
But if you do so, that would mess up the rest of your code, perhaps. I see not point in doing this.
Since the return value of puts is nil, if you do Printer.hi there, it will evaluate to Printer.hi(nil). So in order for it to output "hi there", you need to define:
class Printer
def self.hi _; puts "hi there" end
end
No because strings need to be quoted, so they are not seen as variables.
Otherwise variables such as there would need some special sort of character to indicate that it is a string. However this still wouldn't work well as spaces would then need to be dealt with.
Use single or double quotes.
It's how the language works. accept this and move on to the next challenge :)
Interestingly you can do this in ruby 1.8.7 with just this code:
def method_missing(*args)
puts args.join ' '
end
I learned about this from Gary Bernhardt's talk, Wat. In 1.9 this gives you a stack level too deep error unless you do it inside a class. Google lead me to this post on Aurthur's tech blog thing, which claims you can do something similar in JRuby 1.9 mode:
def method_missing(*args)
puts [method.to_s, args].flatten.join ' '
end
However when I tried this on MRI 1.9.3 it did not work either. So in 1.9 you can't quite do what you want. Here is the closest I could come:
class Printer
def self.hi(message)
puts "hi #{message}"
end
def self.method_missing(m, *args)
[m.to_s, args].flatten.join ' '
end
def self.bare
hi there
end
end
Printer.bare

Executing code before all method calls [duplicate]

This question already has answers here:
How can I intercept method call in ruby?
(3 answers)
Closed 3 years ago.
I'm still new to Ruby in many ways so am a bit stuck trying to do this (via a Module? or base class?).
I want to do a "puts" for each method call executed on a class. Similar to a very simple form of a cucumber formatter, ie:
class MyClass
def method_a
puts 'doing some stuff'
end
end
So that the output looks like:
MyClass.new.method_a => 'methods_a', 'doing some stuff'
More importantly I want it to apply to any method on any class (dynamically, without littering my code). And I'd like to apply some formatting, ie so 'method_a' => 'Method A'. What's the best way to do this? AOP framework?
class MyClass
def method_a
puts "Doing A..."
end
def method_b
puts "Doing B..."
end
def self.call_before_all_methods
all_instance_methods = instance_methods - Class.instance_methods
all_instance_methods.each do |x|
class_eval <<-END
alias old_#{x} #{x}
def #{x}
print "\'#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}\', "
old_#{x}
end
# remove_method old_#{x}.to_sym
END
end
end
private_class_method :call_before_all_methods
call_before_all_methods
end
a = MyClass.new
a.method_a
a.method_b
So the trick here is make an alias for each method first, and then re-define the method by "print formatted method name" + "original method which is the alias". It's also dynamically processed by Here document (<<-END).
But since re-definition of each method will call its original method, eventually it's not possible to remove_method or undef the alias. I think it's not a big deal to leave all those alias (old_method_a, old_method_b) there.
Here you are (apart from before_filter), even though I don't know why you want to do this:
class MyClass
def puts_all(&blk)
# get all instance_methods, also including default ones, so remove them by - Class.instance_methods
all_other_methods = self.class.instance_methods - Class.instance_methods
# remove the method name itself dynamically by saying __callee__
all_other_methods.delete(__callee__)
# formatting as per your need
all_other_methods.each do |x|
print "#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}, "
send(x)
end
end
def method_a
puts "Doing A..."
end
def method_b
puts "Doing B..."
end
def another_fancy_method
puts "Doing fancy..."
end
end
MyClass.new.puts_all
#=> Method A, Doing A...
#=> Method B, Doing B...
#=> Another Fancy Method, Doing fancy...
So, to achieve this dynamically, you can simply use instance_methods and _callee_.

Problem with accessing superclass methods in method redefinitions

I am having a bit trouble to understand when "super" can be called and when not. In the below example the super method leads to a no superclass error.
class Bacterium
def eats
puts "Nam"
end
end
class Bacterium
def eats
super # -> no superclass error
puts "Yam"
end
end
b = Bacterium.new
b.eats
But this works:
class Fixnum
def times
super # -> works
puts "done"
end
end
5.times { |i| puts i.to_s }
Is 5 not just also an instance of Fixnum. And am I not redefining an existing method like in the Bacterium example above?
No, not really. Fixnum inherits from Integer class, and you are in fact overriding Integer#times, so super works, as it calls implementation from the parent.
In order to achieve something similar when monkeypatching, you should alias method before redefining it, and there call it by alias.
class Bacterium
alias_method :eats_original, :eats
def eats
eats_original # -> "Nam"
puts "Yam"
end
end
Class reopening is not a form of inheritance and super is of no use there.
Just as Mladen said, and you can check that with Class#superclass:
irb> Fixnum.superclass
=> Integer
And does Integer implement #times?:
irb> Integer.instance_methods.grep /times/
=> [:times]
Yes it does.
So, in a simplified way, we can say, that super invokes the method you are in of a superclass. In your case the superclass of a Bacterium is Object, which doesn't implement #eats.
I said this is very simplified, because look at this example:
module One
def hi
" World" << super()
end
end
module Two
def hi
"Hello" << super()
end
end
class SayHi
def hi
"!!!"
end
end
h = SayHi.new
h.extend(One)
h.extend(Two)
puts h.hi
#=> Hello World!!
Don't take to serious what I wrote here, it is actually tip of the iceberg of the Ruby object model, which is important to understand (I am still learning it) - then you will get most, or all of those concepts.
Use some Google-fu for "Ruby object model"...

Resources