Explanation of the method_missing method in the Ruby metaprogramming dream, rubymethod_missing
I recently read some articles (such as this article) to promote the use of method_missing in Ruby.
Many people are in a fire with method_missing, but they are not careful about the relationship between them. So I want to discuss this question:
** How can I use method_missing **
When should I resist the temptation of method_missing?
First, never give in to the charm of method_missing before you take the time to think about your use. As you know, in daily life, it is rare for you to think that method_missing is urgently needed:
Daily: Method proxy
Case: I need to enable this class to use the method of another class.
This is the most common use of method_missing. This is particularly popular in gems and Rails plug-ins. Its model is similar to the following:
Copy codeThe Code is as follows:
Class
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
B. new. hi # => Hi from
In this way, B has all the instance methods of. But let's think about what happened when @ B. hi was called. In your ruby environment, you can find the hi method along the inheritance chain. In the end, it calls the method_missing method just before throwing a NoMethodError.
In the above example, the situation is not bad. After all, there are two negligible classes to be checked. But we usually program in the context of Rails or other frameworks. While your Rails model inherits from ActiveRecord, And it integrates with other large classes, so now you have a pile of high stacks that need to be crawled. Every time you call @ B. hi!
Your good friend: define_method
It is estimated that you are complaining now. "But Steve, I need method_missing." I tell you, don't forget that in addition to mistresses, you have a loyal friend named define_method.
It allows you to dynamically define a method (as the name suggests ). Its greatness is that after it has been executed (usually after your classes are loaded), these methods will exist in your class, which is simple and straightforward. When you create these methods, there is no need to crawl the inheritance chain.
Define_method is very reliable and can satisfy your daily life. Don't believe me? Next, let's see zookeeper.
Copy codeThe Code is as follows:
Class B
Define_method (: hi) do
@ B. hi
End
End
"But I have a big way to define it !" You complained
"No problem !" I'm selling cute eyes
Copy codeThe Code is as follows:
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 one by one!
You're a little hard to do.
Copy codeThe Code is as follows:
Class
#... Lots of methods in here
End
Class B
A. instance_methods.each do | name |
Define_method (name) do
@ B. send (name)
End
End
End
What if the method I want to define is a little different from the original one?
Easy
Copy codeThe Code is as follows:
Class
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
Er, the code looks not elegant.
Then there's no way to deal with it. If you want code to be easier to read, you can look at our ruby delegation library and Rails ActiveRecord delegation.
Well, let's take a look at the real power of define_method.
Example of modifying from ruby-doc.org
Copy codeThe Code is as follows:
Class
Def fred
Puts "In Fred"
End
Def create_method (name, & block)
Self. class. send (: define_method, name, & block)
End
Define_method (: wilma) {puts "Charge it! "}
End
Class B <
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 should I use method_missing?
Now you're probably thinking, maybe it's always time to use it, or what else will it do? Yes.
Dynamic naming (also known as metadata)
Case: I need to provide a set of methods based on a certain pattern. What these methods do is as the name suggests. I may have never called these methods, but they must be available when I use them.
Now is the voice! This is actually the way ActiveRecord uses. It provides you with search methods that are dynamically built based on attributes, such as find_by_login_and_email (user_login, user_email ).
Copy codeThe Code is as follows:
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?
#... You get the point
End # my OCD makes me unable to omit this
#...
Else
Super # this is important, I'll tell you why in a second
End
End
Weigh advantages and disadvantages
Method_missing is a perfect compromise when you have a lot of metadata methods to define and don't necessarily get them.
Think about the attribute-based search method in ActiveRecord. Use define_method to define these methods from the beginning to the end. ActiveRecord needs to check all fields in each model table and define the method for each possible field combination method.
Copy codeThe Code is as follows:
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
#...
Assume that your model has 10 fields, that is, 10! (362880) search methods need to be defined. Imagine that when your Rails project runs, there are so many methods that need to be defined at a time, and the ruby environment has to put them in the memory.
Tiger Woods cannot do anything.
** Correct method_missing usage
(Translator's note: If you want to go home, brief excerpt below)
1. Check first
Not every call needs to be processed. You should first check whether this call meets the metadata mode you need to add:
Copy codeThe Code is as follows:
Def method_missing (method_id, * arguments, & block)
If method_id.to_s = ~ /^ What_is _ [\ w] +/
# Do your thing
End
End
2. Pack
Check whether the function is correct. Remember to include the function body package in your friends and define_method. In this way, you don't need to look for a mistress next time:
Copy codeThe Code is as follows:
Def method_missing (method_id, * arguments, & block)
If method_id.to_s = ~ /^ What_is _ [\ w] +/
Self. class. send: define_method, method_id do
# Do your thing
End
Self. send (method_id)
End
End
3. Wipe your ass
The parent class may have a way to handle a method that cannot be handled by itself. So, super:
Copy codeThe Code is as follows:
Def method_missing (method_id, * arguments, & block)
If method_id.to_s = ~ /^ What_is _ [\ w] +/
Self. class. send: define_method, method_id do
# Do your thing
End
Self. send (method_id)
Else
Super
End
End
4. Let the world know
Copy codeThe Code is as follows:
Def respond_to? (Method_id, include_private = false)
If method_id.to_s = ~ /^ What_is _ [\ w] +/
True
Else
Super
End
End
Tell others that your class does not have this method yet, but it can actually respond to this method.
** Conclusion **
In every Ruby programmer's life, this half method plays an important role. Define_method is your best friend. method_missing is a sealed but respectful mistress, while respond_to? It's your son of love.