Раздел «Язык Ruby».RubyCourseLecture13:
<<Метапрограммирование на 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)

Задачи

try_n_times(4, :rescue => [TimeoutError, ArgumentError]) do
  # ...
end

# Обычный сценарий
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

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

Следующая лекция
Предыдущая лекция