require ' net/http ' <br>
module Net <br>
class HTTP <br>
def get (*args)<br>
# <br>
end <br>
end <br>
end <br>
<br>
require
and classes are created dynamically created / patched. How to deal with require
is a separate complex question ( 1 , 2 ). It is not possible to lock the require
(that is, not to give control to other threads until the require
execution ends), since a dead lock may occur.class Fixnum <br>
def * (x)<br>
42 <br>
end <br>
end <br>
puts 5 * 5 <br>
puts 5 * 14 <br>
<br>
class Fixnum <br>
alias orig_div / <br>
def / (x)<br>
puts " -: - #{ self } #{ x } " <br>
self .orig_div(x) <br>
end <br>
end <br>
puts 54 / 12 <br>
puts 13 / 0
alias
language construct, which stores the function body under a different name. It is more correct to think about the alias
operation as about the operation of copying the method body and assigning a new name to it. Use alias
before redefining the method in order to gain access to the previous unpatched version of the method by the selected new name.require ' net/http ' <br>
class HTTP <br>
alias get_orig get <br>
def restore_connection <br>
begin <br>
do_start<br>
true <br>
rescue <br>
false <br>
end <br>
end <br>
def get (*args)<br>
attempts = 0 <br>
begin <br>
get_orig(*args, &block)<br>
rescue Errno :: ECONNABORTED => e<br>
if (attempts += 1 ) < 3 <br>
restore_connection<br>
retry <br>
end <br>
raise e<br>
end <br>
end <br>
end
<br>
require ' net/http ' <br>
class HTTP <br>
make_rescued :get ,<br>
:rescue => [ Errno :: ECONNABORTED , Errno :: ECONNRESET , EOFError , Timeout :: Error ],<br>
:retry_attempts => 3 ,<br>
:on_success => lambda {| obj , args , res | puts " We did it!: #{ args.inspect } " },<br>
:sleep_before_retry => 1 ,<br>
:ensure => lambda {| obj , args | puts " Finishing : #{ args.inspect } " },<br>
:timeout => 3 ,<br>
:retry_if => lambda do | obj , args , e , attempt |<br>
obj.instance_eval do <br>
case e<br>
when Errno :: ECONNABORTED , Errno :: ECONNRESET <br>
# ! ! <br>
restore_connection<br>
when EOFError , Timeout :: Error <br>
# ? <br>
true <br>
end <br>
end <br>
end <br>
end <br>
<br>
make_rescued
, which takes into account various options and solves the problem of salvation in full? It's time to make a new pattern!method_missing
method. All this we will discuss in the following topics.module MakeRescued <br>
def extract_options (args)<br>
args.pop if args.last.is_a?( Hash )<br>
end <br>
def alias_method (a, b)<br>
class_eval " alias #{ a } #{ b } " <br>
end <br>
def make_rescued (*methods)<br>
options = extract_options(methods)<br>
exceptions = options[ :rescue ] || [ Exception ]<br>
methods.each do | method |<br>
method_without_rescue = " #{ method } _without_rescue " <br>
alias_method method_without_rescue, method<br>
define_method(method) do |* args |<br>
retry_attempts = 0 <br>
begin <br>
send(method_without_rescue, *args)<br>
rescue Exception => e<br>
retry_attempts += 1 <br>
unless options[ :retry_attempts ] && retry_attempts > options[ :retry_attempts ]<br>
if exceptions.any?{| klass | klass===e}<br>
retry <br>
end <br>
end <br>
raise e<br>
end <br>
end <br>
end <br>
end <br>
end <br>
<br>
require ' timeout ' <br>
<br>
module MakeRescued <br>
def extract_options (args)<br>
args.last.is_a?( Hash ) ? args.pop : {}<br>
end <br>
def alias_method (a, b)<br>
class_eval " alias #{ a } #{ b } " <br>
end <br>
def make_rescued (*methods)<br>
options = extract_options(methods)<br>
exceptions = options[ :rescue ] || [ Exception ]<br>
methods.each do | method |<br>
method_without_rescue = " #{ method } _without_rescue " <br>
alias_method method_without_rescue, method<br>
define_method(method) do |* args |<br>
retry_attempts = 0 <br>
begin <br>
res = nil <br>
res = if options[ :timeout ]<br>
Timeout ::timeout( options[ :timeout ] ) do <br>
send(method_without_rescue, *args)<br>
end <br>
else <br>
send(method_without_rescue, *args)<br>
end <br>
options[ :on_success ][ self ,args,res] if options[ :on_success ]<br>
res<br>
rescue Exception => e<br>
retry_attempts += 1 <br>
unless options[ :retry_attempts ] && retry_attempts > options[ :retry_attempts ]<br>
if exceptions.any?{| klass | klass===e}<br>
if options[ :retry_if ] && options[ :retry_if ][ self ,args,e,retry_attempts]<br>
sleep options[ :sleep_before_retry ] if options[ :sleep_before_retry ]<br>
retry <br>
end <br>
end <br>
end <br>
options[ :on_fail ][ self ,args,e] if options[ :on_fail ]<br>
raise e<br>
ensure <br>
options[ :ensure ][ self ,args,res] if options[ :ensure ]<br>
res<br>
end <br>
end <br>
end <br>
end <br>
end <br>
<br>
Module .module_eval { include MakeRescued }
:default
option, which specifies the default value of the method if Exception
dropped. If this option is equal to a block (there is an object of class Proc
), then it means you need to call this block with parameters (self, args)
and return the result of the calculation as a result of the method.make_rescued
method make_rescued
welcome.alias_method_chain
alias_method_chain
method. Now we only note that it would be possible to write this:... def get_with_rescue (* args) ... get_without_rescue (* args) ... end alais_method_chain: get,: rescue
def alias_method_chain (target, feature)<br>
alias_method " #{ target } _without_ #{ feature } " , target<br>
alias_method target, " #{ target } _with_ #{ feature } " <br>
end <br>
method_with_feature
and method_without_feature
naming notation allows programmers to understand by the call stack that there is a deepening in the methods patched by the partisans. When Exception
dropped, we see significant method names. In addition, we have for each feature two methods - with this feature and without it, and sometimes there is a need to call them directly.class Module <br>
def alias_method (a, b)<br>
class_eval " alias #{ a } #{ b } " <br>
end <br>
def alias_method_chain (target, feature)<br>
alias_method " #{ target } _without_ #{ feature } " , target<br>
alias_method target, " #{ target } _with_ #{ feature } " <br>
end <br>
end <br>
<br>
# : method_without_feature method_with_feature <br>
class Abc <br>
def hello <br>
puts " hello " <br>
raise ' Bang! ' <br>
end <br>
<br>
def hello_with_attention <br>
puts " attention, " <br>
hello_without_attention<br>
end <br>
alias_method_chain :hello , :attention <br>
<br>
def hello_with_name (name)<br>
puts " my darling #{ name } , " <br>
hello_without_name<br>
end <br>
alias_method_chain :hello , :name <br>
end <br>
<br>
Abc .new.hello( ' Liza ' )<br>
greck $ ruby ​​method_chain_sample_backtrace.rb method_chain.rb: 14: in `hello_without_attention ': Bang! (RuntimeError) from method_chain.rb: 19: in `hello_without_name ' from method_chain.rb: 25: in `hello ' from method_chain.rb: 30 my darling Liza, attention, hello greck $
method_without_feature
methods are method_without_feature
, and all definitions define the same method:<br>
# , method_without_feature <br>
class Abc <br>
def hello <br>
puts " hello " <br>
raise ' Bang! ' <br>
end <br>
<br>
alias hello_without_attention hello <br>
def hello <br>
puts " attention, " <br>
hello_without_attention<br>
end <br>
<br>
alias hello_without_name hello <br>
def hello (name)<br>
puts " my darling #{ name } , " <br>
hello_without_name<br>
end <br>
end <br>
<br>
Source: https://habr.com/ru/post/50819/
All Articles