Ruby metaprogramming examples

Source: Internet
Author: User
Tags glob

Ruby metaprogramming examples

I like ruby metaprogramming very much. puppet And chef use many ruby language features to define a new deployment language.
I would like to share some of the scenarios used in actual projects with limited capabilities. If you have a better solution, please leave a message to me :)

Rpc interface templated-use eval, alias, defind_method

require 'rack/rpc'

class Server < Rack::RPC::Server
 def hello_world
  "Hello, world!"
 end

 rpc 'hello_world' => :hello_world
end

The above is an rpc server. Compile a function and call the rpc command for registration.

The define_method, eval, and alias methods can be used to determine the *. rb file under the rpc/directory for loading and rpc interface registration. The implementation code is as follows:

module RPC
  require 'rack / rpc'
  #require rpc / *. rb files
  Dir.glob (File.join (File.dirname (__ FILE__), 'rpc', "* .rb")) do | file |
   require file
  end
  class Runner <Rack :: RPC :: Server
   #include rpc / *. rb and regsiter rpc call
   #eg. rpc / god.rb god.hello
   @@ rpc_list = []
   Dir.glob (File.join (File.dirname (__ FILE__), 'rpc', "* .rb")) do | file |
    rpc_class = File.basename (file) .split ('. rb') [0] .capitalize
    rpc_list = []
    
    #Load the methods under module to the Runner class
    eval "include Frigga :: RPC :: # {rpc_class}"
    #Get declared RPC interface
    eval "rpc_list = Frigga :: RPC :: # {rpc_class} :: RPC_LIST"
    rpc_list.each do | rpc_name |
     #aliasA new rpc method called old_xxxx_xxxx
     eval "alias: old _ # {rpc_class.downcase} _ # {rpc_name}: # {rpc_name}"

     #Redefine rpc method, add a line of log printing function, and then call old_xxxx_xxxx rpc
     define_method "# {rpc_class.downcase} _ # {rpc_name}". to_sym do | * arg |
      Logger.info "[# {request.ip}] called # {rpc_class.downcase}. # {Rpc_name} # {arg.join (',')}"
      eval "old _ # {rpc_class.downcase} _ # {rpc_name} * arg"
     end

     #Register RPC call
     rpc "# {rpc_class.downcase}. # {rpc_name}" => "# {rpc_class.downcase} _ # {rpc_name}". to_sym

     #Add to global variables, summarize all rpc methods
     @@ rpc_list << "# {rpc_class.downcase}. # {rpc_name}"
    end
   end
   
   def help
    rpc_methods = (['help'] + @@ rpc_list.sort) .join ("\ n")
   end
   rpc "help" =>: help

  end
 end #RPC


After completing the above functions, you can easily develop the rpc interface, such as the following code for adding, deleting, and querying ip addresses, registering ip. list, ip. add, and ip. del methods:

module RPC
  module Ip
   #RPC_LIST used for regsiter rpc_call
   RPC_LIST = %w(list add del)

   def list
    $white_lists
   end   

   def add(ip) 
    if ip =~ /^((25[0-5]|2[0-4]\d|[0-1]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[0-1]?\d\d?)$/
     $white_lists << ip
     write_to_file
     return "succ"
    else
     return "fail"
    end
   end

   def del(ip)
    if $white_lists.include?(ip)
     $white_lists.delete ip
     write_to_file
     return "succ"
    else
     return "fail"
    end    
   end

   def write_to_file
     File.open(IP_yml, "w") do |f|
      $white_lists.uniq.each {|i| f << "- #{i}\n"}
     end
   end
  end 
 end


DSL -- use instance_eval

Instance_eval is a Swiss Army knife in ruby, especially in DSL support.
Let's take a look at the API for setting file templates in chef (an open-source automated deployment tool:
Copy codeThe Code is as follows:
Template "/path/to/file. conf" do
Source "file. conf. erb"
Owner "wilbur"
Mode "0744"
End

In the above Code, the source, owner, and mode must be transferred from the external block to the block inside the template. To achieve this purpose, the instance_eval code is used as follows:

 class ChefDSL
   def template(path, &block)
    TemplateDSL.new(path, &block)
   end
  end

  class TemplateDSL
   def initialize(path, &block)
    @path = path
    instance_eval &block
   end

   def source(source); @source = source; end
   def owner(owner);  @owner = owner; end
   def mode(mode);   @mode  = mode;  end
  end


The above tips enable the TemplateDSL object to apply block, just like in its own scope. Block can access and call variables and methods in TemplateDSL.

If instance_eval is not used, ruby will throw a NoMethodError because source, owner, and mode cannot be accessed in the block.
Copy codeThe Code is as follows:
Class TemplateDSL
Def initialize (path, & block)
@ Path = path
Block. call
End
End

You can also use yeild to pass variables, but instance_eval is not concise and flexible.

Command Line interaction-use instance_eval

Command Line interaction. You can use the gem highline.
However, highline cannot meet my needs in some aspects. For example, the chef template function described above has achieved the following results, greatly simplifying repeated code:
Copy codeThe Code is as follows:
# Check frigga fail and ask whether to continue
Tip. ask frigga_fail? Do
Banner "Check some frigga failed, skip failed host and continue deploy? "
On: yes
On: quit do
Raise Odin: TipQuitExcption
End
End
...

# The running result is as follows:
Check some frigga failed, skip failed host and continue deploy? [Yes/quit]
# Enter yes to continue. Enter quit to exit.

The implementation code is as follows:

 require 'colorize'
 class Tip
  def self.ask(stat = true, &block)
   new(&block).ret if stat == true
  end

  attr_reader :ret
  def initialize(&block)
   @opt = []
   @caller = {}
   @banner = ""
   @ret = false
   self.instance_eval(&block)
   print "#{@banner} [#{@opt.join('/')}]: ".light_yellow
   loop do
    x = gets.chomp.strip.to_sym
    if @opt.include?(x)
     @ret = ( @caller[x].call if @caller.key?(x) )
     if @ret == :retry
      print "\n#{@banner} [#{@opt.join('/')}]: ".light_yellow
      next
     else
      return @ret
     end
    else
     print "input error, please enter [#{@opt.join('/')}]: ".light_yellow
    end
   end

  end

  def on(opt, &block)
   @opt << opt
   @caller[opt] = block if block_given?
  end
  def banner(str)
   @banner = str
  end
 end

What's the next project

Welcome! Welcome! Welcome! Welcome! Welcome! Welcome!
 
Error Message

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.