How to know which class raised a NoMethodError? - ruby

I don't want to raise a NoMethodError at a certain place but only for a certain class (ex. NilClass).
Ex.
begin
maybe_nil_maybe_not_nil = nil
maybe_nil_maybe_not_nil.x
rescure NoMethodError => ex
raise unless ex.class_which_raised == NilClass
end
I now do this based on the to_s of the exception but messages are different when you don't expect it for .id, .product and maybe others. It would be better to do this based on metadata/parameters. Is there a way to do this?

Using exceptions for control flow is usually a bad idea. What would happen if the method you called raised a NoMethodError itself? (It would get swallowed by your rescue block)
You're better off using respond_to?
raise 'this is bad' if object.nil?
if object.respond_to?(:x)
object.x
end

Well, depending on what you're doing and how you feel about re-opening classes, another option is to override method_missing on your class, e.g.:
class NilClass
alias method_missing old_method_missing
def method_missing(m_sym, *args, &block)
old_method_missing m_sym, *args, &block
log_that_you_had_a_missing_method_call
end
end
This is kind of dirty, but sometimes you don't have control over all of the callers of the method, if you're dealing with e.g. a library. I would make sure to add a lot of comments about why you're monkey patching your class, since the potential for unintended side effects is huge here.
I would recommend, barring any TOCTOU issues, if you possibly can, going with Matt von Rohr's solution of looking before you leap.

Related

Behavior of `super`

I have this code:
class B
def self.definer(name, *args, &block)
define_method(name) { self.instance_exec(*args, &block) }
end
end
and when I try to use it, I get this error:
B.definer(:tst) { super }
# => :tst
B.new.tst
# => TypeError: self has wrong type to call super in this context: B (expected #<Class:#<Object:0x007fd3008123f8>>)
I understand that super has a special meaning, and works little different from calling a method. Can someone explain why and what is happening? It would also be great if someone suggests a solution for this.
I don't get the same error message as you did, but get an error anyway. super must be used within a method definition. But you are not using it in a method definition. That raises an error.
Regarding the solution, I cannot give you one since it is not clear at all what you are trying to do.
You definitely don't want instance_exec there.
If you didn't have the *args involved, I'd say you just wanted this:
def self.definer(name, &block)
define_method(name, &block)
end
But then your new definer method would do the exact same thing that define_method does in the first place, so there's be no reason to create it, instead of just using define_method in the first place.
What are you actually trying to do? Explain what you want to do, and maybe someone can help you.
But I think the instance_exec in your existing implementation isn't what you want -- it is immediately executing the block upon definer call, when calling define_method -- I think you want the block executed when the method you are defining is being called instead? But I'm not really sure, it depends on what you're trying to do, which is unclear. super doesn't really make any sense within an instance_exec -- super to what method did you think you'd be calling?

Extend IRB to allow for "filtering" not found methods to a method of my choice?

On IRB startup I load a lot of custom files, modules and classes to enhance it.
There is one class I have which would help me a lot but I dont know how to call it automatically. Automatically in this context means: When I start IRB, and when I type something which is not a method or a variable, I would like to call a custom method of my own choosing.
I know loosely that this can be done via either a rescue, or with method_missing, but I am not sure which one to do either. Can someone show me how to invoke a method in IRB (or irbrc) if I type a method that is unknown to IRB, like "foo"?
I'd choose a different approach:
put all methods you need into a Module instead of a Class
have Object include this module
start irb with foo.rb to always automagically load your definitions
foo.rb:
module Foo
def my_method
puts "aye"
end
end
class Object
include Foo
end
Now, whenever you type my_method in irb, it will call your method.
This seems like it would get annoying really quickly, but here's how you do it. Add this to your .irbrc:
def self.method_missing (name, *args, &block)
puts "#{name} is not a method, ya goof"
end
Obviously, change the contents of the method definition to change what happens when missing methods are caught.
Now whenever you call a method on the main object in irb, it catches the missing method.
>> foo
foo is not a method, ya goof
=> nil
That's just for top-level method calls. If you want to catch every missing method call ever, add this instead:
class Object
def method_missing (name, *args, &block)
puts "#{self.class}##{name} is not a method, ya goof"
end
end
Keep in mind, this is going to reveal to you a lot of method calls that are failing that you probably don't even know about.
$ irb
String#to_int is not a method, ya goof
>> foo
NilClass#to_ary is not a method, ya goof
Object#foo is not a method, ya goof
=> nil
>> [].bar
Array#bar is not a method, ya goof
=> nil
I don't think this is going to be something you want to live with in the long run, but this is how to do it. Knock yourself out!

When to use `method_missing`

In the code below, method roar is not defined in class Lion, but still can be called using method_missing.
class Lion
def method_missing(name, *args)
puts "Lion will #{name}: #{args[0]}"
end
end
lion = Lion.new
lion.roar("ROAR!!!") # => Lion will roar: ROAR!!!
In which situations and how should I use this method_missing? And is it safe to use?
It's entirely safe to use provided you use it in expected ways and don't get carried away. Not everything you can do is worth doing, after all.
The advantage of method_missing is you can respond to all kinds of things in unique ways.
The disadvantage is you don't advertise your capabilities. Other objects that expect you to respond_to? something will not get confirmation and might treat your custom object in ways you don't intend.
For building Domain Specific Languages and providing very loose glue between components, this sort of thing is invaluable.
A great example of where this is a good fit is the Ruby OpenStruct class.
Summary: When to use? When it will make your life easier and not complicate others' lives.
Here's one example that pops to mind. It's from redis_failover gem.
# Dispatches redis operations to master/slaves.
def method_missing(method, *args, &block)
if redis_operation?(method)
dispatch(method, *args, &block)
else
super
end
end
Here we check if the method called is actually a command of redis connection. If so, we delegate it to underlying connection(s). If not, relay to super.
Another famous example of method_missing application is ActiveRecord finders.
User.find_by_email_and_age('me#example.com', 20)
There's not, of course, a method find_by_email_and_age. Instead, the method_missing breaks the name, analyzes the parts and invokes find with proper parameters.
Here's a favorite of mine
class Hash
def method_missing(sym,*args)
fetch(sym){fetch(sym.to_s){super}}
end
end
Which lets me access values of a hash as if they were attributes. This is particular handy when working with JSON data.
So for example, rather than having to write tweets.collect{|each|each['text']} I can just write tweets.collect(&:text) which is much shorter. Or also, rather than tweets.first['author'] I can just write tweets.first.author which feels much more natural. Actually, it gives you Javascript-style access to values of a hash.
NB, I'm expecting the monkey patching police at my door any minuteā€¦
First and foremost, stick to Sergio Tulentsev's summary.
Apart from that, I think looking at examples is the best way to get a feeling for right and wrong situations for method_missing; so here is another simple example:
I recently made use of method_missing in a Null Object.
The Null Object was a replacement for a Order model.
The Order stores different prices for different currencies.
Without method_missing it looks like this:
class NullOrder
def price_euro
0.0
end
def price_usd
0.0
end
# ...
# repeat for all other currencies
end
With method_missing, I can shorten it to:
class NullOrder
def method_missing(m, *args, &block)
m.to_s =~ /price_/ ? 0.0 : super
end
end
With the added benefit of not having to (remember to) update the NullOrder when I add new price_xxx attributes to Order.
I also found a blog post from (Paolo Perrotta) where it demonstrated when to use method_missing:
class InformationDesk
def emergency
# Call emergency...
"emergency() called"
end
def flights
# Provide flight information...
"flights() called"
end
# ...even more methods
end
Check if a service has been asked during lunch time.
class DoNotDisturb
def initialize
#desk = InformationDesk.new
end
def method_missing(name, *args)
unless name.to_s == "emergency"
hour = Time.now.hour
raise "Out for lunch" if hour >= 12 && hour < 14
end
#desk.send(name, *args)
end
end
# At 12:30...
DoNotDisturb.new.emergency # => "emergency() called"
DoNotDisturb.new.flights # ~> -:37:in `method_missing': Out for lunch (RuntimeError)

Dangers in overloading method_missing and method_added

Whenever I talk to Rubyists I hear great things about method_added and method_missing. However, I got recently chided for using both in a project. The argument was that another library (such as rspec, for example) could overload the methods too and put the program in a strange state depending on which version of method_missing got called first.
I'm curious to know how often this happens. Is it really that dangerous to overload method_missing? Does anyone have real-world examples of woe arising from method_missing conflicts?
One thing to keep in mind that if you (re)define method_missing on a class, you're replacing any previous implementations of this method (from a Gem for example).
You can avoid this by creating a new class that inherits from the class you want to extend with method_missing.
However, this is normally not an issue as most Gems have their own classes.
The other thing to keep in mind is to always call super at the end of method_missing to not break the method invocation chain.
def method_missing(m, *args, &block)
# your code...
super
end
Maybe this Graphic of the Ruby Method Lookup Flow is also helpful.

Is it possible to access instance values within a methods missing function?

I was trying to write my first method_missing override when I kept running into (edited) stack level too deep errors. The main culprit seemed to be trying to utilize an instance attribute. For instance if 'self' was a instance of the User class then checking for something like:
def method_missing(name)
if self.name
# do stuff
end
end
Would seg fault. I spent a long time on this but ended up giving up. There must be something I'm not understanding about accessing it.
Edit
My apologies, Andrew is correct, I am getting Stack Level too deep errors. With this in mind, what is the appropriate (if any) way to access the instances attribute values?
You can potentially rectify this problem by ensuring that self.name actually exists:
def method_missing(name)
if self.respond_to?(:name) && self.name
# do stuff
end
end
Note this may not work if your class inherits from anything Railsy (e.g. ActiveRecord::Base), since it overrides respond_to?.
If you are in a Railsy class, your method missing should call super, lest you lose a lot of the "magic" ActiveRecord methods (including, probably, self.name itself):
def method_missing(name, *args, &block)
if name_is_something_i_should_handle_here
# do your stuff
else
super(name, *args, block) # call parent's method_missing
end
end
Obviously you should replace name_is_something_i_should_handle_here with the appropriate logic.
You may also wish to consider using dynamic method creation instead of method_missing.

Resources