<<Метапрограммирование на Ruby
Лекция 13: Универсальные обертки функций: postrocess_value, preprocess_arguments, wrap_method, make_trier
postprocess_value
class Module
def alias_method_chain(m, f)
class_eval "alias #{m}_without_#{f} #{m}\n" +
"alias #{m} #{m}_with_#{f}"
end
def postprocess_value(method, &block)
new_method = "#{method}_with_post"
old_method ="#{method}_without_post"
class_eval do
define_method(new_method) do |*args|
block[ self.send(old_method, *args) ]
end
alias_method_chain method, :post
end
end
def preprocess_arguments(method, &block)
f = "pre_#{block.object_id}"
new_method = "#{method}_with_#{f}"
old_method ="#{method}_without_#{f}"
class_eval do
define_method(new_method) do |*args|
self.send(old_method, *block[self, args])
end
alias_method_chain method, f
end
end
def wrap_method(method, &block)
f = "wrap_#{block.object_id}"
new_method = "#{method}_with_#{f}"
old_method ="#{method}_without_#{f}"
class_eval do
define_method(new_method) do |*args|
old_method_obj = self.method(old_method)
block[self, args, old_method_obj]
end
alias_method_chain method, f
end
end
end
class Array
postprocess_value :index do |value|
value.nil? ? 'undefined' : value
end
preprocess_arguments :[] do |obj, args|
[args[0] % obj.size]
end
preprocess_arguments :at do |obj, args|
arg = args.first
if Float === arg
[arg.round]
else
args
end
end
wrap_method(:at) do |obj, args, method|
if obj.size <= args[0]
raise ArgumentError, "Wrong index #{args[0]}"
end
value = method[*args]
if value.nil?
'undefined'
else
value
end
end
end
puts ['a','b'].index('b') == 1
a = [nil,1,2,3,4,5]
puts a.at(0)
preprocess_arguments
class Module
def preprocess_arguments(method, &block)
f = "pre_#{block.object_id}"
new_method = "#{method}_with_#{f}"
old_method ="#{method}_without_#{f}"
class_eval do
define_method(new_method) do |*args|
self.send(old_method, *block[self, args])
end
alias_method_chain method, f
end
end
end
class Array
preprocess_arguments :[] do |obj, args|
[args[0] % obj.size]
end
preprocess_arguments :at do |obj, args|
arg = args.first
if Float === arg
[arg.round]
else
args
end
end
end
wrap_method
class Module
def alias_method_chain(m, f)
class_eval "alias #{m}_without_#{f} #{m}\n" +
"alias #{m} #{m}_with_#{f}"
end
def wrap_method(method, &block)
f = "wrap_#{block.object_id}"
new_method = "#{method}_with_#{f}"
old_method ="#{method}_without_#{f}"
class_eval do
define_method(new_method) do |*args|
old_method_obj = self.method(old_method)
block[self, args, old_method_obj]
end
alias_method_chain method, f
end
end
end
class Array
wrap_method(:at) do |obj, args, method|
if obj.size <= args[0]
raise ArgumentError, "Wrong index #{args[0]}"
end
value = method[*args]
if value.nil?
'undefined'
else
value
end
end
end
puts a.at(0)
Задачи
- Убедитесь, что несколько wrappers одного и того же метода не конфликтуют.
- Изучите синтаксис begin-rescue-retry-else-ensure-end (см. RubyCourseLecture05). Напишите метод try_n_times:
try_n_times(4, :rescue => [TimeoutError, ArgumentError]) do
# ...
end
- Напишите метод make_trier:
# Обычный сценарий
require 'net/http'
require 'uri'
url = URI.parse('http://acm.mipt.ru')
res = Net::HTTP.start(url.host, url.port) do |http|
http.get('/')
end
puts res.body
# Сценарий с патчем
require 'net/http'
require 'uri'
url = URI.parse('http://acm.mipt.ru')
Net::HTTP.start(url.host, url.port) do |http|
http.singleton_class.class_eval do
make_trier :get,
:attempts => 3,
:timeout => 10.seconds,
:before_retry => lambda{|obj| ... }
end
# код, где много раз встречается http.get
print http.get('/robots.txt')
print http.get('/')
end
puts res.body
- Напишите метод make_rescued:
module A
def foo
end
make_rescued :foo,
:rescue => [ArgumentError, ...],
:default_value => .. # if expected exception raised
make_rescued :foo,
:rescue => lambda{|ex| ex.to_s =~ /This is not critical/ },
:default_value => .., # if expected exception raised
:each_exception => lambda{|ex| },
:retry_if => lambda{|ex| }
end