Using MRI 1.9
When an exception is raised that causes a backtrace to be printed, it would often be immensely easier to debug if the backtrace showed the receiver and values of method parameters as well as the method name. Is there any way of doing this?
Think about situations such as passing a nil deep into library code that wasn't expecting it, or where two strings have incompatible encodings and some routine is trying to concatenate them
you can with 1.8.6 by using the backtracer gem.
1.9 has slightly broken callbacks so isn't compatible yet, though. I might be able to get it to work, if desired.
You could use something like a delegate and see the parameters to a single object:
class A
def go a, b
end
end
class A2
def initialize *args
#delegate = A.new *args
end
def method_missing meth, *args
p "got call to #{meth}", args.join(', ')
#delegate.send(meth,*args)
end
end
which outputs
"in go2"
"got call to go"
"3, 4"
Related
Given a class:
class Foo
def initialize(input1)
#input1 = input1
end
end
is there some way that would throw a more helpful error against a = Foo.new()? How can I build a method that throws an ArgumentError in a more helpful way?
I'd like to build this into the class. The Programming Ruby site lists several error-trapping mechanisms, but all of these seem to depend on wrapping a = Foo.new() in a catch block or the like. I would like to have my error trapping within the class itself however.
Since you're new to Ruby it's understandable this error might seem odd, yet it's also an error that's very specific to passing the wrong arguments in. Remapping it to something else isn't necessarily helpful, it ends up hiding problems in your code. I'd suggest leaving it as-is and expecting errors like that to occur if you're not calling it correctly.
The alternative is, at least in newer versions of Ruby, to declare keyword arguments with no defaults:
def initialize(input1:)
end
That's a required keyword argument, and the error is more specific:
ArgumentError: missing keyword: input1
The downside is you have to call it like this:
Foo.new(input1: 'test')
That might be beneficial in terms of clarity. It's up to you.
you can use a default value and raise whatever error you need within the initialize method for example
Class A
def initialize(a = nil)
raise("give me an A") if a.nil?
#a = a
end
end
You can do this pretty simply by raising that error when the argument is not defined. You can add a message to the ArgumentError exception by specifying it as an argument on the exception:
class Foo
def initialize(input1=nil)
raise ArgumentError, "expected a value for Foo.new('value')" unless input1
#input1 = input1
end
end
After reading Programming Ruby a bit more, I think using alias_method as a hook might serve:
alias_method :initialize_orig, :initialize
def initialize(*args)
begin
result = initialize_orig(*args)
return result
rescue Exception
$stderr.print "Need to use argument 'input1'\n"
raise
end
end
I've been stuck on this for quite a while now. Take a look at this:
class SuperClass
def self.new(*args, **kwargs, &block)
i = allocate()
# Extra instance setup code here
i.send(:initialize, *args, **kwargs, &block)
return i
end
end
class Test < SuperClass
def initialize
puts "No args here"
end
end
The class SuperClass basically "reimplements" the default new method so that some extra initialization can happen before initialize.
Now, the following works just fine:
t = Test.allocate
t.send(:initialize, *[], **{}, &nil)
However, this does not:
t = Test.new
ArgumentError: wrong number of arguments (1 for 0)
from (pry):7:in `initialize'
It fails on this line in SuperClass:
i.send(:initialize, *args, **kwargs, &block)
But apparently it only fails if called within the new method. I have confirmed that args == [], kwargs == {} and block == nil.
Is anybody able to explain this?
Ruby version:
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]
Please refrain from suggesting that I don't overload Class.new. I am aware I can use Class.inherited and Class.append for the same result. This question is only about why the call to initialize fails.
Let's examine a simpler example, especially because the problem isn't as specific as the question and its title make it look like but see for yourself.
def m # takes no arguments
end
m(**{}) # no argument is passed
h = {}
m(**h) # an argument is passed => ArgumentError is raised
This inconsistency was introduced in 2.2.1 by a commit intended to fix a segmentation fault involving **{} (Bug #10719). The commit special-cases **{} to not pass an argument. Other ways like **Hash.new and h={};**h still pass an empty hash as argument.
Previous versions consistently raise ArgumentError (demo). I could be wrong but I believe that's the intended behavior. However it may or may not be the one actually wants. So if you think double-splatting an empty hash shouldn't pass an argument (like **{} at the moment) and therefore work similar to splatting an empty array, there is an open issue about that (Bug #10856). It also mentions this relatively new inconsistency.
A simple *args will capture all arguments including keyword arguments, in case you don't need to reference kwargs separately in the new method:
class SuperClass
def self.new(*args, &block)
i = allocate
# Extra instance setup code here
i.send(:initialize, *args, &block)
i
end
end
I am trying to make a simplistic implementation of AOP in ruby. I was able to implement before and after advices, I got stuck with around advice.
This is the target class that is going to be advised:
class MyClass
def method
puts "running method"
end
end
This is the Aspect class to instantiate objects capable of making advices:
class Aspect
def advise(class_name, method, type, &block)
class_name.send(:alias_method, :proceed, :method)
class_name.send(:define_method, :method) do
case type
when :before
yield
proceed
when :after
proceed
yield
when :around
yield(proceed) # * proceed is the old version of the method
end
end
end
end
(*) Yield should execute the block around MyClass#proceed on the current object when method is invoked.
Creating the target and the aspect:
mc = MyClass.new
a = Aspect.new()
Invoking the method without advising it:
puts mc.method
Advising MyClass#method with around:
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
puts mc.method
(*) I am not being able to pass something to identify the call of proceed, that is the invocation of the old method without the advice.
The output should be:
First
running method
Last
In Ruby, a method call looks like this:
receiver.method(arguments)
Or, you can leave off the receiver if the receiver is self.
So, to call a method named proceed on some receiver, you would write
receiver.proceed
However, in your implementation, you don't keep track of what the receiver should be, so since you don't know the receiver, you simply cannot call the method.
Note that there are lots of other problems with your approach as well. For example, if you advise multiple methods, you will alias them all to the same method, overwriting each other.
I believe there are two things going wrong here.
This section of code
when :around
yield(proceed) # * proceed is the old version of the method
end
Calls the block given to advise providing the output of the proceed method as an argument.
So your output probably looks something like:
running method
First
Last
This block
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
Just evaluates the argument given as proceed. If a method is given it does not call it. So taking problem 1 into consideration in your case the original definition of method (aliased to proceed) returns nil (output of return) which will be passed as the value to the proceed argument in the block when yielded. the block ends up evaluating to something like
puts "First"
nil
puts "Last"
mc.method is called.
To address the second part, you may want to consider using send. Because the inner workings of your aspect may not be known to your code that calls it. It may change over time, so what ever calls Aspect.advise shouldn't make assumptions that the original method will still be accessible. Instead, it should take an argument (the new method name) and send it to the object. Making the block passed to advise:
a.advise(MyClass, :method, :around) do |aliased_method_name|
puts "First"
send(aliased_method_name)
puts "Last"
end
And adjusting the around item added to your class when advise is called to the following:
when :around
yield(:proceed) # * proceed is the old version of the method
end
If you do both of these things, your around section will calls the provided block, using the symbol for the new alias for the overridden method.
N.B.: This approach won't work for methods that require any arguments.
This is what I did. In the definition of Aspect#advise now I use a Proc, like this:
when :around
yield Proc.new { proceed }
end
And when calling the method to advise MyClass#method with :around parameter I use this:
a.advise(MyClass, :method, :around) do |original|
puts "First"
original.call
puts "Last"
end
I got:
First
running method
Last
Here's the fixed version that will work for arguments, and avoid clobbering.
class Aspect
##count = 0
def self.advise(class_name, method, type=nil, &block)
old_method = :"__aspect_#{method}_#{##count += 1}"
class_name.send(:alias_method, old_method, method)
class_name.send(:define_method, method) do |*args, &callblock|
case type
when :before
yield
send(old_method, *args, &callblock)
when :after
send(old_method, *args, &callblock)
yield
when :around, nil
yield lambda {
send(old_method, *args, &callblock)
}
end
end
end
end
class Foo
def foo(what)
puts "Hello, #{what}!"
end
end
Aspect.advise(Foo, :foo) do |y|
puts "before around"
y.yield
puts "after around"
end
Aspect.advise(Foo, :foo, :before) do
puts "before"
end
Aspect.advise(Foo, :foo, :after) do
puts "after"
end
Foo.new.foo("world")
# before
# before around
# Hello, world!
# after around
# after
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
I have recently started a project in Ruby on Rails. I used to do all my projects before in Python but decided to give Ruby a shot.
In the projects I wrote in Python I used a nice little technique explained by the correct answer in this post:
Dictionary or If statements, Jython
I use this technique due to Python not having a native switch function and it also get rid of big if else blocks
I have been trying to do recreate the above method in Ruby but can't seem to quite get it.
Could anyone help me out?
If you only need to call a method by its name stored in a string, standard Ruby way of doing it is using method Object#send:
def extractTitle dom
puts "title from #{dom}"
end
def extractMetaTags dom
puts "metatags from #{dom}"
end
dom = 'foo'
type = 'extractTitle'
send type, dom
#=> title from foo
type = 'extractMetaTags'
send type, dom
#=> metatags from foo
Otherwise, you can use Ruby's case statement, as already suggested.
While nothing prevents you from using the class-based approach, why avoid rubys case statement?
case thing
when 'something'
do_something
when 'nothing'
do_nothing
else
do_fail
end
As others have said, there are alternative ways of doing this in Ruby, but if you are just curious then an equivalent to that Python approach in Ruby (making use of Object#send once you have determined the method name) would be:
class MyHandler
def handle_test(arg)
puts "handle_test called with #{arg}"
end
def handle_other(arg)
puts "handle_other called with #{arg}"
end
def handle(type, *args)
method_name = "handle_#{type}"
if respond_to? method_name
send(method_name, args)
else
raise "No handler method for #{type}"
end
end
end
You can then do:
h = MyHandler.new
h.handle 'test', 'example'
h.handle 'other', 'example'
h.handle 'missing', 'example'
and the output would be:
handle_test called with example
handle_other called with example
handle.rb:15:in `handle': No handler method for missing (RuntimeError)
from handle.rb:23