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.
Related
I don't know how to correctly phrase the title, I think the best way to explain this issue is just with code samples.
My goal
I want to define a meta method like this (in Rails 5):
class Post < ApplicationRecord
override_this_attribute_writer :some_attribute
end
The override_this_attribute_writer follows a common pattern, it overrides the original writer by doing some filtering on top of it. I find this way of overriding very convenient and clear.
First approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
alias_method :"#{attribute_name}_old=", :"#{attribute_name}="
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:"#{attribute_name}_old=", a_value)
end
end
end
When doing this, I was getting an exception at the call of alias_method, because, apparently, the method I was trying to copy didn't exist (yet).
Second approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:write_attribute, attribute_name, a_value)
end
end
end
I was expecting this not to work: if, when running the meta method, ActiveRecord hasn't created the attribute writer yet, this means that it will do it later and override the method that I just defined.
But surprisingly it worked! So I put my hands inside ActiveRecord (5.1.5) to find out more.
Dig into ActiveRecord 5.1.5
I wanted to ensure that what I did was safe and it wasn't just working by accident: I looked into the definition of method writer, and put binding.pry around the method.
This is the result of the experiment:
For attributes that I did not override,
This line is called
Then the method is defined inside this module eval call
Finally, the newly created writer method is correctly called when performing object.attribute=
For attributes that I DID override,
My own method is defined before anything else (when the ActiveRecord writers aren't there yet
Then ActiveRecord calls the same line that handles writer creation, as in the previous example
The method gets (apparently) correctly created by ActiveRecord, since it passes again by this point
But now, surprisingly, when calling object.attribute= my own method is still called in place of the ActiveRecord one
So, this is what I don't understand: if ActiveRecord seems to be overriding my method but it doesn't, what prevents it from doing it?
My questions
What in the end I need to know is whether the fix I have done is actually a good practice (and robust) or it's at risk and it might break if in the future we do upgrades.
If you think that my fix is dangerous, would you be able to suggest a different way to achieve the same goal?
Calling super is even more idiomatic:
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |value|
# do some stuff
super value
end
end
end
end
I came across this solution for a Proxy class in the Ruby koans:
class Proxy
attr_accessor :messages
def initialize(target_object)
#object = target_object
#messages = []
end
def method_missing(method_name, *args, &block)
#messages << method_name
#object.send(method_name, *args, &block)
end
end
I can create an object from this proxy class by passing another class as an argument. For instance, the following code will result in "Do something", without having to type thing.method_missing(:do_thing):
class Thing
def do_thing
puts "Doing something."
end
end
thing = Proxy.new(Thing.new)
thing.do_thing
Why is the code in method_missing executed even without having to call said method?
There are methods that are called implicitly (i.e., called even when you don't write it in the code) when a certain event happens or a certain method is called. I call these methods hooks, borrowing the terminology of e-lisp. As far as I know, Ruby has the following hooks:
Ruby hooks
at_exit
set_trace_func
initialize
method_missing
singleton_method_added
singleton_method_removed
singleton_method_undefined
respond_to_missing?
extended
included
method_added
method_removed
method_undefined
const_missing
inherited
initialize_copy
initialize_clone
initialize_dup
prepend
append_features
extend_features
prepend_features
And method_missing is one of them. For this particular one, it is automatically called when Ruby cannot find a defined method. Or in other words, method_missing is the most default method that is called with the least priority, for any method call.
method_missing is one of the amazing aspects of metaprogramming in ruby. With proper use of this method, you can gracefully handle exceptions and whatnot. In your case it is called because the method you are calling on the object doesn't exist obviously.
But one should be careful of its use too. While you are at it do look at responds_to method too.
An example regarding ActiveRecord will make you understand better. When we write:
User.find_by_email_and_age('me#example.com', 20)
There isn't actually a method by that name. This call goes to the method_missing and then this fancyfindmethod is broken down into pieces and you are served what you asked for. I hope that helps.
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?
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)
I have an object like this
class SomeObject
def initialize &block
# do something
end
end
class AnotherObject < SomeObject
def initalize &block
super
# do something with block
end
end
When super is called in AnotherObject, the block seems to be passed to SomeObject. Is this the right behaviour and is there away round it?
According to rubyspec this is the correct behaviour, even if you pass explicit arguments to super (i.e. super('foo'))
If you don't want to pass that block, you could just pass a block that does nothing, although this isn't quite the same thing (e.g. if the method changes its behaviour based on block_given?)
It appears that
super(&nil)
is a way to pass no block at all to super, although I couldn't find this in ruby spec.