I recently read some articles (such as this one) to promote the use of method_missing in Ruby.
Many people are steamy with method_missing, but are not careful to deal with each other's relationship. So, I want to explore this question:
* * What should I do with method_missing *
When should you resist the temptation of method_missing
First of all, never give in to Method_missing's charms until you've taken the time to think about how well you've done it. You know, in everyday life, there's very little to make you think that you need method_missing:
Daily: Method Agent
Case: I need a way for this class to be able to use another class
This is the most common use of method_missing I have ever seen. This is particularly prevalent in gems and Rails plug-ins. Its model is similar to this:
Class A
def hi
Puts "Hi from #{self.class}"
End
End
Class B
DEF initialize
@b = A.new
End
def method_missing (Method_name, *args, &block)
@b.send (Method_name, *args, &block)
End
End
A.new.hi #=> Hi from A
B.new.hi #=> Hi from A
So, B has all the instance methods of a. But let's think about what happened when we called @b.hi. Your ruby environment goes along the inheritance chain to find hi this way, in the end, just before throwing a nomethoderror, it tuned method_missing this method.
In the example above, the situation is not bad, after all, there are two trivial classes to look for. But usually, we are programming in the context of Rails or some other framework. And your Rails model inherits from ActiveRecord, and it's integrated into a bunch of other classes, so now you have a high stack to crawl ... every time you call @b.hi!
Your good base friend: Define_method
Guess now you're complaining, "but Steve, I need method_missing." I tell you, don't forget in fact besides Mistress, you also have a loyal good base friend, call Define_method.
It allows you to dynamically define a method (as the name suggests). The great thing about it is that after it has been executed (usually after your classes are loaded), these methods exist in your class, simple and straightforward. When you create these methods, there is no inheritance chain to crawl.
Define_method is very loving and reliable, and can satisfy your daily life. Don't believe me? Next look ...
Class B
Define_method (: Hi) do
@b.hi
End
End
"But I have a big pile of methods to define!" "You complain
"No problem!" "I sell Meng Winks
Class B
[: Hi,: Bye,: Achoo,: Gesundheit].each do |name|
Define_method (name) do
@b.send (name)
End
End
End
But I am too lazy to write them all!
You're a little hard to get.
Class A
# ... lots of methods in
End
Class B
A.instance_methods.each do |name|
Define_method (name) do
@b.send (name)
End
End
End
What if I want to define a method that is not the same as the original?
Easy
Class A
def hi
Puts "Hi."
End
End
Class B
A.instance_methods.each do |name|
Define_method ("What_is_#{name}") do
If @b.respond_to? (name)
@b.send (name)
Else
False
End
End
End
End
B.new.what_is_hi #=> "Hi."
B.NEW.WHAT_IS_WTF #=> False
Well, the code doesn't look elegant.
There's no way to do that. If you want the code to be easier to read, check out our Ruby delegation Library and Rails ActiveRecord delegation.
Well, let's summarize and see the real power of Define_method.
Modify the example from the ruby-doc.org
Class A
def Fred
Puts "in Fred"
End
def create_method (name, &block)
Self.class.send (:d efine_method, name, &block)
End
Define_method (: Wilma) {puts "Charge it!"}
End
Class B < A
Define_method (: Barney, Instance_method (: Fred))
End
A = B.new
A.barney #=> in Fred
A.wilma #=> Charge It!
A.create_method (: Betty) {P self.to_s}
A.betty #=> B
when do you use method_missing?
Now you think that there is always a time to use it, or what else do you want it? That's right.
Dynamically named method (also named, meta method)
Case: I'm going to provide a set of methods based on a pattern. These methods do things as the name suggests. I may have never called these possible methods, but they must be available when I want to use them.
Now is the words! This is exactly what ActiveRecord is doing, providing you with search methods for dynamic builds based on attributes, such as Find_by_login_and_email (User_login, User_email).
def method_missing (method_id, *arguments, &block)
If match = Dynamicfindermatch.match (method_id)
Attribute_names = Match.attribute_names
Super unless all_attributes_exists? (Attribute_names)
If Match.finder?
#. The Point
End # I OCD makes me unable to omit this
# ...
Else
Super # This is important, I'll tell your why in a second
End
End
Weigh
Method_missing is a perfect tradeoff when you have a whole bunch of meta methods to define and not necessarily use.
Think about the attribute-based lookup method in ActiveRecord. To define these methods with Define_method from head to toe, ActiveRecord needs to examine all the fields in each model's table and define methods for each possible field combination.
Find_by_email
Find_by_login
Find_by_name
find_by_id
Find_by_email_and_login
Find_by_email_and_login_and_name
Find_by_email_and_name
# ...
If your model has 10 fields, that's 10! (362880) A lookup method needs to be defined. Imagine that when your Rails project runs, there are so many methods that need to be defined once, and the Ruby environment has to put them all in memory.
Tiger Woods can't do anything.
* * Correct way to use method_missing
(The translator's wretched note: To go home, the following brief selected passage)
1, first check
Not every call has to be processed, you should first check to see if the call matches the mode of the Meta method you need to add:
def method_missing (method_id, *arguments, &block)
If method_id.to_s =~/^what_is_[\w]+/
# do your thing
End
End
2, pack up
Check well, really want to deal with, please remember to wrap the function body in your good base friend, Define_method inside. So, the next time you don't need to find a mistress:
def method_missing (method_id, *arguments, &block)
If method_id.to_s =~/^what_is_[\w]+/
Self.class.send:d Efine_method, method_id do
# do your thing
End
Self.send (method_id)
End
End
3, wipe the buttocks
I can not handle the method, perhaps the parent has a way, so super:
def method_missing (method_id, *arguments, &block)
If method_id.to_s =~/^what_is_[\w]+/
Self.class.send:d Efine_method, method_id do
# do your thing
End
Self.send (method_id)
Else
Super
End
End
4. Telling the world
def respond_to? (method_id, include_private = False)
If method_id.to_s =~/^what_is_[\w]+/
True
Else
Super
End
End
To tell others, your class does not have this method for the time being, but it can actually respond to this method.
* * Summary * *
In every Ruby Programmer's life, these three methods play an important role. Define_method is your good base friend, Method_missing is an inseparable but also need xiangjingrubin mistress, and respond_to? It is your beloved son who is so free from danger.