I have recently considered a lot of metaprogramming (metaprogramming) issues and would like to see more examples and explanations of this technology. For better or worse, Metaprogramming has entered the Ruby community and is a standard way to accomplish various tasks and simplify code. Since I can't find this kind of resources, I'm going to start writing some general Ruby technology articles. These may be useful for programmers who have turned to Ruby from other languages or have not yet experienced the joys of Ruby metaprogramming.
1. Use a single case class using the Singleton-class
Many ways to manipulate a single object are based on manipulating its single instance class (Singleton Class), and this makes metaprogramming simpler. The classic way to get a single example class is to execute the following code:
code as follows:
Sclass = (class << self; self; end)
RCR231 suggests defining kernel#singleton_class methods like this:
code as follows:
Module Kernel
def Singleton_class
Class << self; Self End
End
End
I will use this method below.
2. Use class method of DSL to modify subclass Write DSL ' s using class-methods that rewrite subclasses
When you want to create a DSL to define class information, the most common question is how to represent information for use by other parts of the framework. To define a ActiveRecord model object as an example:
Class Product < ActiveRecord::Base
Set_table_name ' Produce '
End
In this case, it is interesting to use the set_table_name. How does that work? Well, here's a little magic. This is an implementation method:
Module ActiveRecord
Class Base
def self.set_table_name Name
Define_attr_method:table_name, Name
End
def self.define_attr_method (name, value)
Singleton_class.send:alias_method, "Original_#{name}", name
Singleton_class.class_eval do
Define_method (name) do
Value
End
End
End
End
End
What's interesting here is Define_attr_method. In this example we need to get a single instance class of the product class, but we don't want to modify the activerecord::base. By using a single example class we have achieved this goal. We have aliased the original method and then defined a new accessor (accessor) to return the value. If ActiveRecord needs table name, it can invoke the accessor directly. This dynamic creation method and accessor techniques are common in a single instance class, especially rails.
3. Dynamically create class and module create classes and modules dynamically
Ruby allows you to dynamically create and modify class and module. You can make any changes to the class or module that are not frozen. can be useful in certain situations. The struct class may be the best example:
Personvo = struct.new (: Name,:p Hone,: email)
P1 = personvo.new (: Name => "Ola Bini")
This creates a new class, assigns it to Personvo, and then creates an instance of the class. It is also simple to create a new class from a draft and define a new method:
c = class.new
C.class_eval do
Define_method:foo do
Puts "Hello World"
End
End
C.new.foo # => "Hello World"
In addition to struct, you can find examples of easily created classes in soap4r and camping. Camping is particularly interesting because it has a special way of creating these classes, which are inherited by your controller and view. Many of the interesting features of camping are implemented in this way:
def R (*urls); Class.new (R) {meta_def (: URLs) {URLs}};
End
This makes it possible to create controller:
Class View < R '/view/(\d+) '
def get post_id
End
End
You can also create a module, and then include a module in the class.
4. Use the method_missing to do interesting things using method_missing to do interesting things
In addition to closures, method_missing may be the most powerful feature of Ruby and the easiest to abuse. With good method_missing, some code can become super simple, even can not be missing. A good example (camping) is an extended hash:
Class Hash
def method_missing (M,*a)
If m.to_s =~/=$/
self[$ '] = a[0]
Elsif a.empty?
SELF[M]
Else
Raise Nomethoderror, "#{m}"
End
End
End
You can use hash like this:
x = {' abc ' => 123}
X.ABC # => 123
X.foo =: Baz
X # => {' abc ' => 123, ' foo ' =>: Baz}
As you can see, if someone calls a method that does not exist in a hash, the internal collection is searched. If the method name ends with =, it is assigned to the key with the same name.
Another good method_missing technique can be found in Markaby. The following referenced code can generate any XHTML tag that contains CSS class:
Body do
H1.header ' Blog '
Div.content do
' Hellu '
End
End
Will generate:
<body>
<div class= "Content" >
Hellu
</div>
</body>
Most of this functionality, especially the CSS class name, is set by method_missing the Self property and then back to self.
5. Method mode scheduling Dispatch on Method-patterns
This can be easily scalable for unpredictable methods. I recently created a small validation framework where the core validation classes find all of their own methods that start with Check_ and invoke them, which makes it easy to add new validations: Simply adding new methods to the class or instance.
Methods.grep/^check_/do |m|
Self.send m
End
It's very simple and incredibly powerful. You can take a look at Test::unit everywhere using this method.
6. Replacement method Replacing methods
Sometimes the implementation of a method is not what you want, or only half of it. The standard object-oriented approach is to inherit and overload, and then invoke the parent class method. It is only useful when you have an object-instantiated control, which is often not the case, and inheritance has no value. To get the same functionality, you can rename (alias) The old method, add a new method definition to invoke the old method, and ensure that the old method's conditions are preserved.
Class String
Alias_method:original_reverse,: Reverse
DEF reverse
Puts "reversing, please wait ..." original_reverse
End
End
An extreme use is to temporarily modify a method and then restore it. For example:
def Trace (*mths)
Add_tracing (*mths) # aliases methods named, adding tracing
Yield
Remove_tracing (*mths) # removes the tracing aliases
End
This example shows a typical way to write add_tracing and remove_tracing. It relies on the single example class of article 1th:
Class Object
def add_tracing (*mths)
Mths.each do |m|
Singleton_class.send:alias_method, "Traced_#{m}", M
Singleton_class.send:d Efine_method, M do |*args|
$stderr. puts "before #{m} (#{args.inspect})"
ret = Self.send ("Traced_#{m}", *args)
$stderr. puts "after #{m}-#{ret.inspect}"
Ret
End
End
End
def remove_tracing (*mths)
Mths.each do |m|
Singleton_class.send:alias_method, M, "traced_#{m}"
End
End
End
"ABC". Add_tracing:reverse
If these methods are added to the module (a little different, see if you can write it out!) , you can also add and remove tracing on a class, not an instance.
7. Use the nil class to introduce the refactoring of empty objects using Nilclass to implement the introduce null object refactoring
In Fowler refactoring, the "introduce empty object" Refactoring is an object that has either a predefined value or a null one. Typical examples are as follows:
Name = X.nil?? ' Default name ': X.name
Currently, java-based Refactoring recommends creating a subclass that is similar to null. For example, Nullperson inherits person, and the overloaded name method always returns "default name." But in Ruby we can open a class, and you can do this:
def Nil.name; "Default name"; End
X # => Nil
name = x.name # => ' default name '
8. Learn different versions of Eval Learn the different versions of Eval
Ruby has several versions of the execution method (evaluation). It is important to understand their differences and use scenarios. There are a few eval, Instance_eval, Module_eval and Class_eval. First, Class_eval is the alias of Module_eval. Second, Eval is somewhat different from others. The most important thing is that eval can only execute a string, others can execute block. This means that eval is the last choice you have to do anything, it has its uses, but in most cases it should be done with Instance_eval and Module_eval.
Eval executes the string in the current environment unless the environment has provided a binding (binding). (see article 11th)
Instance_eval executes a string or block in the context of the recipient (reveiver), without specifying the self as the recipient.
Module_eval executes a string or block in the context of the calling module. This is a better fit to define a new method in a module or a single instance class. The main difference between Instance_eval and Module_eval is where the defined method will be placed. If you use String.instance_eval to define Foo method, you will get String.foo, if you use Module_eval, you will get String.new.foo.
Module_eval is almost always applicable; avoid eval as you do with plagues. It will be good for you to follow these simple rules.
9. Introspective Introspect on instance variables of instance variables
Rails uses a trick to make instance variables in controller also available in view, which is to introspect an instance variable of an object. This can seriously damage the encapsulation, but sometimes it's really handy. Can be easily achieved through instance_variables, Instance_variable_get, and Instance_variable_set. To copy all instance variables from one to another, you can do this:
From.instance_variables.each do |v|
To.instance_variable_set V, from.instance_variable_get (v)
End
10. Create the proc from the block and expose the create procs from blocks and send them around
The practice of storing a proc instantiation in a variable and exposing it makes many APIs easy to use. This is a way for Markaby to manage CSS class definitions. It's easy to convert blocks into proc:
def create_proc (&p); P End
Create_proc do
Puts "Hello"
End # => #<proc ...>
It's also easy to call:
P.call (*args)
If you want to define a method with Proc, you should create it with a lambda, and you can use return and break:
P = lambda {puts "HoHo"; return 1}
Define_method (: A, &p)
If there is block, Method_missing will call block:
def method_missing (name, *args, &block)
Block.call (*args) if Block_given?
End
Thismethoddoesntexist ("abc", "CDE") do |*args|
P args
End # => ["abc", "CDE"]
11. Use binding (binding) to control the eval using binding to controls your evaluations
If you really need to use eval, you can control which variables are valid. This is when you use the kernel method binding to get the object you are binding on. For example:
def Get_b; Binding End
Foo = 13
Eval ("puts Foo", Get_b) # => nameerror:undefined local variable or by ' foo ' for Main:object
Erb and rails Use this technique to set which instance variables are valid. For example:
Class Holder
def Get_b; Binding End
End
H = holder.new
H.instance_variable_set "@foo", 25
Eval ("@foo", H.get_b)
Hopefully these techniques and techniques have clarified metaprogramming for you. I don't claim to be a Ruby or metaprogramming expert, that's just some of my thoughts on the subject.