ruby元编程实际使用实例

很喜欢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
(0)

相关推荐

  • Ruby元编程的一些值得注意的地方

    避免无限循环的元编程. 写一个函数库时不要使核心类混乱(不要使用 monkey patch). 代码块形式最好用于字符串插值形式.         当你使用字符串插值形式,总是提供 __FILE__ 和 __LINE__,使得你的回溯有意义. class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__ define_method 最好用 class_eval{ def ... } 当使用 class_eva

  • ruby元编程之创建自己的动态方法

    method_missing是Ruby元编程(metaprogramming)常用的手法.基本思想是通过实现调用不存在的方法,以便进行回调.典型的例子是:ActiveRecord的动态查找(dynamic finder).例如:我们有email属性那么就可以调用User.find_by_email('joe@example.com'),虽然, ActiveRecord::Base并没有一个叫做find_by_email的方法. respond_to? 并不如method_missing出名,常用

  • Ruby元编程小结

    今天被问到此类问题,以前总是觉得这个是比较宽泛的一个概念,自己即使是用过这些特性,但却一直不知道这叫"元编程" 直到今天被人问起的时候,方才顿悟一些,随后便在网上和自己的平实用的一些元编程做个小总结. 原来所谓的Ruby中的元编程,是可以在运行时动态的操作语言结构(如类.模块.实例变量等)的技术.你甚至于可以在不用重启的情况下,在运行时直接键入一段新的Ruby代码,并执行他. Ruby的元编程,也具有"利用代码来编写代码"的作用.例如,常见的attr_accesso

  • Ruby和元编程之万物皆为对象

    开篇 空即是色,色即是空. 空空色色,色色空空,在Ruby语言中,万物皆为对象. Ruby是一个面向对象的语言(Object Oriented Language),面向对象的概念比其他语言要贯彻的坚定很多. Ruby中不存在Java中原始类型数据和对象类型数据之分.大部分Ruby中的的东东都是对象. 所以,想要掌握Ruby和Ruby的元编程,对象就是第一门必修功课.本回就着重研究一下Ruby中的对象. Ruby中的对象 如果你从其他面向对象的语言转来,一提到得到一个对象你可能会想到建立一个类,然

  • ruby元编程之method_missing的一个使用细节

    我们知道顶级域,定义域的self是啥? 复制代码 代码如下: puts self    #main puts self.class #Object 我们知道当一个方法被调用的时候,如果没有对象接受,默认就是self,如: 复制代码 代码如下: def tell_me_who     puts self end tell_me_who  #main 方法调用是这样的步骤,先查找当前对象的所在类的实例方法存在方法与否,如果存在,调用方法,如果不存在则查看superclass,直到 BasicObje

  • Ruby元编程技术详解(Ruby Metaprogramming techniques)

    我最近考虑了很多元编程(Metaprogramming)的问题,并希望看到更多这方面技术的例子和讲解.无论好坏,元编程已经进入Ruby社区,并成为完成各种任务和简化代码的标准方式.既然找不到这类资源,我准备抛砖引玉写一些通用Ruby技术的文章.这些内容可能对从其它语言转向Ruby或者还没有体验到Ruby元编程乐趣的程序员非常有用. 1. 使用单例类 Use the singleton-class 许多操作单个对象的方法是基于操作其单例类(singleton class),并且这样可以使元编程更简

  • Ruby元编程之梦中情人method_missing方法详解

    我最近读了些文章(比如这篇),宣传在 Ruby 里使用 method_missing 的. 很多人都与 method_missing 干柴烈火,但在并没有小心处理彼此之间的关系.所以,我想来探讨一下这个问题: ** 我该怎么用 method_missing ** 什么时候该抵挡 method_missing 的诱惑 首先,永远不要在还没花时间考虑你用得够不够好之前,就向 method_missing 的魅力屈服.你知道,在日常生活中,很少会让你以为的那样亟需 method_missing: 日常

  • Ruby元编程基础学习笔记整理

    笔记一: 代码中包含变量,类和方法,统称为语言构建(language construct). # test.rb class Greeting def initialize(text) @text = text end def welcome @text end end my_obj = Greeting.new("hello") puts my_obj.class puts my_obj.class.instance_methods(false) #false means not i

  • ruby元编程实际使用实例

    很喜欢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

  • Python中使用装饰器和元编程实现结构体类实例

    Ruby中有一个很方便的Struct类,用来实现结构体.这样就不用费力的去定义一个完整的类来仅仅用作访问属性. 复制代码 代码如下: class Dog < Struct.new(:name, :age) end fred = Dog.new("fred", 5) printf "name:%s age:%d", fred.name, fred.age ##name:fred age:5 Python3.4中也可以这么干,但写法很累赘.其中包含self.nam

  • 浅谈C++模板元编程

    所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文).模板元编程的根在模板.模板的使命很简单:为自动代码生成提供方便.提高程序员生产率的一个非常有效的方法就是"代码复用",而面向对象很重要的一个贡献就是通过内部紧耦合和外部松耦合将"思想"转化成一个一个容易复用的"概念".但是面向对象提供的工具箱里面所

随机推荐