gpt4 book ai didi

ruby元编程实际使用实例

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章ruby元编程实际使用实例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

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

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

?
1
2
3
4
5
6
7
8
9
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接口注册的功能,实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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代码如下:

  。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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退出 。

  。

实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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元编程实际使用实例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com