很喜歡ruby元編程,puppet和chef用到了很多ruby的語言特性,來定義一個新的部署語言。
分享幾個在實際項目中用到的場景,能力有限,如果有更優(yōu)方案,請留言給我:)
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,編寫一個函數(shù),調(diào)用rpc命令進行注冊。
采用define_method、eval、alias方法,可以實現(xiàn)一個判斷rpc/目錄下的*.rb文件,進行加載和rpc接口注冊的功能,實現(xiàn)代碼如下:
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方法,添加一行日志打印功能,然后再調(diào)用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調(diào)用 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
完成上述功能后,可以非常方便的開發(fā)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(一個開源的自動化部署工具)中設(shè)置文件模板的API:
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對象可以應(yīng)用block,和在自己的scope一樣。block可以訪問和調(diào)用TemplateDSL中的變量和方法。
如果沒有使用instance_eval,如下面的代碼,ruby就會拋出一個NoMethodError,因為source、owner、mode無法在block中被訪問到。
當(dāng)然也可以使用yeild傳遞變量的方式實現(xiàn),但沒有instance_eval簡潔和靈活。
命令行交互——使用instance_eval
命令行交互,可以采用highline這個gem.
但highline在有些方面不能滿足我的需求,比如類似上面介紹的chef template功能,達到的效果如下,大大簡化了重復(fù)代碼:
#運行時顯示結(jié)果如下:
Check some frigga failed, skip failed host and continue deploy? [yes/quit]
#輸入yes繼續(xù),輸入quit退出
實現(xiàn)代碼如下:
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
標(biāo)簽:大慶 克拉瑪依 河池 西雙版納 南昌 甘孜 內(nèi)江 棗莊
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《ruby元編程實際使用實例》,本文關(guān)鍵詞 ruby,元,編程,實際,使用,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。