Home>

1. Use the singleton-class

Many methods of manipulating a single object are based on manipulating its singleton class, and this makes metaprogramming easier.The classic way to get a singleton class is to execute the following code:

sclass=(class<<self;self;end)

Rcr231 recommends defining the kernel #singleton_class method like this:

module kernel
 def singleton_class
 class<<self;self;end
 end
end

I will use this method below.

2. dsl use class methods to modify subclasses write dsl "s using class-methods that rewrite subclasses

When i want to create a dsl to define class information,The most common problem is how to represent information for use by other parts of the framework.Take the definition of an activerecord model object as an example:

class product<activerecord ::base
 set_table_name "produce"
end

In this example,Of interest is the use of set_table_name. How does this work?Okay, here comes 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

Make people Interesting here is define_attr_method. In this example we need to get the singleton class of the product class,But you don't want to modify activerecord ::base. We achieve this by using singleton classes.We aliased the original method,Define a new accessor to return the value.If ActiveRecord requires the table name, it can call the accessor directly.This technique of dynamically creating methods and accessors is common in singleton classes,Especially rails.

3. Create classes and modules dynamically

Ruby allows you to dynamically create and modify classes and modules. You can make any changes to classes or modules that are not frozen.It can be useful in certain situations.The struct class is probably the best example:

personvo=struct.new (:name,:phone,:email)
p1=personvo.new (:name =>"ola bini")

This creates a new class,Assign it to personvo, and then create an instance of the class.It's also easy to create new classes and define new methods from the draft:

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 also find examples of easily creating classes in soap4r and camping.camping is particularly interesting,Because it has special methods to create these classes,Inherited by your controller and view. Many interesting features of camping are implemented this way:

def r (* urls);class.new (r) {meta_def (:urls) {urls}};
end
</p>
<p>
This makes it possible to create a controller like this:
class view<r "/view/(\ d +)"
 def get post_id
 end
end

You can also create a module like this, and then include the module in the class.

4. use method_missing to do interesting things

Apart from blocks, method_missing is probably the most powerful feature of Ruby.It is also the easiest to abuse.With good method_missing, some code becomes super simple,It is even indispensable.A good example (camping) is extended hashing:

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,The internal collection is searched.If the method name ends with =, it will be assigned to the key with the same name.

Markaby can find another good method_missing trick. The code referenced below can generate any xhtml tag containing a css class:

body do
 h1.header "blog"
 div.content do
 "hellu"
 end
end

Will generate:

<body>
 <h1>blog</h1>
 <div>
 hellu
 </div>
</body>

The vast majority of this feature,In particular, the css class name is set to the self property via method_missing and returns self.

5. Dispatch on method-patterns

This is easily scalable for unpredictable methods.I recently created a small validation framework,The core validation class will find all the methods that start with check_ and call them.This makes it easy to add new validations:just add new methods to the class or instance.

methods.grep/^ check_/do | m |

self.send m

end

This is very simple,And incredibly powerful.Take a look at test ::unit using this method everywhere.

6. Replacement methods

Sometimes the implementation of a method is not what i want,Or just do it halfway.The standard object-oriented approach is inheritance and overloading,Then call the parent method.Only useful if you have control over object instantiation,This is often not the case,There is no value in inheritance.To get the same functionality,You can rename the old method and add a new method definition to call the old method,And ensure that the pre- and post-conditions of the old method are preserved.

class string
 alias_method:original_reverse,:reverse
 def reverse
 puts "reversing, please wait ..." original_reverse
 end
end

An extreme usage is to temporarily modify a method,Then restore again.E.g:

def trace (* mths)
 add_tracing (* mths) #aliases the methods named, adding tracing
 yield
 remove_tracing (* mths) #removes the tracing aliases
end

example This example shows a typical way to write add_tracing and remove_tracing.It relies on the singleton class of Article 1:

class object
 def add_tracing (* mths)
 mths.each do | m |
 singleton_class.send:alias_method, "traced _ #{m}", m
 singleton_class.send:define_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 bit different,See if you can write it! ), You can also add and remove tracing on classes instead of instances.

7. Use nil class to introduce the null object refactoring

In the reconstruction of fowler,The refactoring of "introducing empty objects" is that an object either exists,Either has a predefined value when empty.Typical examples are as follows:

name=x.nil??"default name":x.name

Currently Java-based refactoring would recommend creating a null-like subclass. For example, nullperson inherits person, and the overloaded name method always returns "default name". But in ruby ​​we can open classes,This can be done:

def nil.name;"default name";end
x #=>nil
name=x.name #=>"default name"

8. Learn the different versions of eval

Ruby has several versions of evaluation. It is important to understand their differences and use cases.There are eval, instance_eval, module_eval, and class_eval. First, class_eval is an alias for module_eval. Second, eval is a bit different from others.The most important thing is that eval can only execute one string,Others can execute blocks. This means eval is your last choice for anythingIt has its uses,But in most cases, you should use instance_eval and module_eval to execute the block.

Eval will execute the string in the current environment,Unless the environment already provides bindings. (See Article 11)

Instance_eval will execute a string or block in the context of the receiver (reveiver). If not specified, self will be the receiver.

Module_eval executes a string or block in the context of the calling module. This is more suitable for defining new methods in module or singleton classes.The main difference between instance_eval and module_eval is where the defined methods are placed.If you define the foo method with string.instance_eval you will get string.foo, if you use module_eval you will get string.new.foo

Module_eval is almost always applicable;Avoid using eval as you would a plague. It's good for you to follow these simple rules.

9. Introspect on instance variables

Rails uses a trick to make the instance variables in the controller also available in the view, which is to introspect the instance variables of an object.This will severely damage the package,However, sometimes it is very smooth.It can be easily implemented with instance_variables, instance_variable_get, and instance_variable_set. To copy all instance variables from one to another,Can be like this:

from.instance_variables.each do | v |
 to.instance_variable_set v, from.instance_variable_get (v)
end

10. create procs from blocks and send them around

of The practice of storing a proc in a variable and making it public makes many APIs easy to use.This is a method used by markaby to manage css class definitions.It's easy to convert a block into a proc:

def create_proc (&p);p;end

create_proc do

puts "hello"

end #=>#<proc ...>

Calling is also easy:

p.call (* args)

If i want to use proc to define the method,Should be created with lambda, you can use return and break:

p=lambda {puts "hoho";return 1}

define_method (:a,&p)

If there is a block, method_missing will call the 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 to control eval use binding to control your evaluations

If you really need to use eval, you can control which variables are valid.At this time, you need to use the kernel method binding to obtain the bound object.E.g:

def get_b;binding;end
foo=13
eval ("puts foo", get_b) #=>nameerror:undefined local variable or method `foo" for main:object

Erb and rails use this technique to set which instance variables are valid.E.g:

class holder
 def get_b;binding;end
end
h=holder.new
h.instance_variable_set "@foo", 25
eval ("@ foo", h.get_b)

Hope these tips and techniques have clarified metaprogramming for you.I don't claim to be an expert in ruby ​​or metaprogramming,These are just some of my thoughts on this issue.

lua
  • Previous Database access performance optimization
  • Next Introducing a MySQL access library soci for C ++ programs