主页 > 知识库 > ruby元编程实际使用实例

ruby元编程实际使用实例

热门标签:柯城手机地图如何做地图标注 征服者企业地图标注 天津外呼系统怎么收费 中牟外呼系统违法吗 漯河电销 AI电销机器人 线路 外呼线路从哪里出来的 巫师3地图标注魔力之所 淮安自动外呼系统供应商

很喜欢ruby元编程,puppet和chef用到了很多ruby的语言特性,来定义一个新的部署语言。
分享几个在实际项目中用到的场景,能力有限,如果有更优方案,请留言给我:)

rpc接口模板化——使用eval、alias、defind_method

require 'rack/rpc'

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

 rpc 'hello_world' => :hello_world
end

上面是一个rpc server,编写一个函数,调用rpc命令进行注册。

采用define_method、eval、alias方法,可以实现一个判断rpc/目录下的*.rb文件,进行加载和rpc接口注册的功能,实现代码如下:

module RPC
  require 'rack/rpc'
  #require rpc/*.rb文件
  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 = []
    
    #加载module下的方法到Runner这个类下面
    eval "include Frigga::RPC::#{rpc_class}"
    #获取声明的RPC接口
    eval "rpc_list = Frigga::RPC::#{rpc_class}::RPC_LIST"
    rpc_list.each do |rpc_name|
     #alias一个新的rpc方法,叫old_xxxx_xxxx
     eval "alias :old_#{rpc_class.downcase}_#{rpc_name} :#{rpc_name}"

     #重新定义rpc方法,添加一行日志打印功能,然后再调用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 

     #注册RPC调用
     rpc "#{rpc_class.downcase}.#{rpc_name}" => "#{rpc_class.downcase}_#{rpc_name}".to_sym

     #添加到全局变量,汇总所有的rpc方法
     @@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

完成上述功能后,可以非常方便的开发rpc接口,例如下面这个IP地址增、删、查的代码,注册ip.list, ip.add和ip.del方法:

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——使用instance_eval

instance_eval是ruby语言中的瑞士军刀,特别是支持DSL方面。
我们来看一下chef(一个开源的自动化部署工具)中设置文件模板的API:

复制代码 代码如下:

    template "/path/to/file.conf" do
      source "file.conf.erb"
      owner  "wilbur"
      mode   "0744"
    end

上述代码中,source、owner、mode需要从外部block,传递到template内部的block中,为了实现该目的,采用了instance_eval代码如下:

  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

上面这个小技巧使得TemplateDSL对象可以应用block,和在自己的scope一样。block可以访问和调用TemplateDSL中的变量和方法。

如果没有使用instance_eval,如下面的代码,ruby就会抛出一个NoMethodError,因为source、owner、mode无法在block中被访问到。

复制代码 代码如下:

    class TemplateDSL
      def initialize(path, block)
        @path = path
        block.call
      end
    end

当然也可以使用yeild传递变量的方式实现,但没有instance_eval简洁和灵活。

命令行交互——使用instance_eval

命令行交互,可以采用highline这个gem.
但highline在有些方面不能满足我的需求,比如类似上面介绍的chef template功能,达到的效果如下,大大简化了重复代码:

复制代码 代码如下:

        #检查frigga fail,询问是否继续
        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
        ...

        #运行时显示结果如下:
        Check some frigga failed, skip failed host and continue deploy? [yes/quit]
        #输入yes继续,输入quit退出

实现代码如下:

 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
您可能感兴趣的文章:
  • Ruby元编程的一些值得注意的地方
  • ruby元编程之创建自己的动态方法
  • ruby元编程之method_missing的一个使用细节
  • Ruby元编程之梦中情人method_missing方法详解
  • Ruby元编程技术详解(Ruby Metaprogramming techniques)
  • Ruby元编程小结
  • Ruby和元编程之万物皆为对象
  • Ruby元编程基础学习笔记整理

标签:大庆 克拉玛依 河池 西双版纳 南昌 甘孜 内江 枣庄

巨人网络通讯声明:本文标题《ruby元编程实际使用实例》,本文关键词  ruby,元,编程,实际,使用,;如发现本文内容存在版权问题,烦请提供相关信息告之我们,我们将及时沟通与处理。本站内容系统采集于网络,涉及言论、版权与本站无关。
  • 相关文章
  • 下面列出与本文章《ruby元编程实际使用实例》相关的同类信息!
  • 本页收集关于ruby元编程实际使用实例的相关信息资讯供网民参考!
  • 推荐文章