rubyメタプログラミングの実際の使用例
6575 ワード
rubyメタプログラミングが大好きで、puppetとchefは多くのrubyの言語特性を使って、新しい配置言語を定義します.実際のプロジェクトで使われているシーンをいくつか共有します.能力が限られています.もっと優れた案があれば、私にメッセージを残してください.)
rpcインタフェースのテンプレート化――eval、alias、defind_の使用method
上にはrpc serverがあり、関数を作成し、rpcコマンドを呼び出して登録します.
define_を採用method、eval、aliasメソッドは、rpc/ディレクトリの下を判断する*を実現することができる.rbファイルは、ロードとrpcインタフェースの登録を行う機能で、実現コードは以下の通りです.
上記の機能が完了すると、rpcインタフェースの開発が非常に便利になり、例えば以下のIPアドレスの増加、削除、検索のコード、ipの登録が可能になる.list, ip.addとip.delメソッド:
DSL――instance_の使用eval
instance_evalはruby言語のスイスの軍刀であり、特にDSL側をサポートしている.chef(オープンソースの自動化導入ツール)でファイルテンプレートを設定するAPIを見てみましょう.
上記コードでは、source、owner、modeは外部blockからtemplate内部のblockに渡す必要があり、その目的を達成するためにinstance_を採用しているevalコードは次のとおりです.
上の小さなテクニックは、自分のscopeと同じように、TemplateDSLオブジェクトにblockを適用することができます.blockはTemplateDSLの変数とメソッドにアクセスして呼び出すことができます.
instanceを使用していない場合evalは、次のコードのようにrubyがNoMethodErrorを投げ出す.source、owner、modeはblockにアクセスできないからだ.
もちろんyeildで変数を渡す方式で実現することもできますがinstance_はありませんevalは簡潔で柔軟です.
コマンドラインインタラクション――instance_の使用eval
コマンドラインのインタラクションはhighlineというgemを採用することができる.しかし、highlineはいくつかの面で私のニーズを満たすことができません.例えば、上記のchef template機能のように、以下のような効果を達成し、重複コードを大幅に簡略化しました.
実装コードは次のとおりです.
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("
")
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}
"}
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
上の小さなテクニックは、自分のscopeと同じように、TemplateDSLオブジェクトにblockを適用することができます.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 "
#{@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