Ruby Language
Refinamientos
Buscar..
Observaciones
Los refinamientos tienen un alcance léxico, lo que significa que están vigentes desde el momento en que se activan (con la palabra clave que using
) hasta que el control cambia. Por lo general, el control se cambia al final de un módulo, clase o archivo.
Parches de mono con alcance limitado
El principal problema de Monkey Patching es que contamina el alcance global. Su código de trabajo está a la merced de todos los módulos que utiliza, no pisando los otros dedos de los pies. La solución de Ruby para esto son los refinamientos, que son básicamente parches de mono en un alcance limitado.
module Patches
refine Fixnum do
def plus_one
self + 1
end
def plus(num)
self + num
end
def concat_one
self.to_s + '1'
end
end
end
class RefinementTest
# has access to our patches
using Patches
def initialize
puts 1.plus_one
puts 3.concat_one
end
end
# Main scope doesn't have changes
1.plus_one
# => undefined method `plus_one' for 1:Fixnum (NoMethodError)
RefinementTest.new
# => 2
# => '31'
Módulos de doble propósito (refinamientos o parches globales)
Es una buena práctica utilizar parches con Refinamientos, pero a veces es bueno cargarlos globalmente (por ejemplo, en desarrollo o pruebas).
Digamos, por ejemplo, que desea iniciar una consola, requerir su biblioteca y luego tener los métodos parcheados disponibles en el ámbito global. No podría hacer esto con los refinamientos porque el using
debe llamarse en una definición de clase / módulo. Pero es posible escribir el código de tal manera que tenga un doble propósito:
module Patch
def patched?; true; end
refine String do
include Patch
end
end
# globally
String.include Patch
"".patched? # => true
# refinement
class LoadPatch
using Patch
"".patched? # => true
end
Refinamientos dinámicos
Los refinamientos tienen limitaciones especiales.
refine
solo se puede utilizar en el alcance de un módulo, pero se puede programar usando send :refine
.
using
es más limitado. Solo se puede llamar en una definición de clase / módulo. Aún así, puede aceptar una variable que apunta a un módulo y puede invocarse en un bucle.
Un ejemplo que muestra estos conceptos:
module Patch
def patched?; true; end
end
Patch.send(:refine, String) { include Patch }
patch_classes = [Patch]
class Patched
patch_classes.each { |klass| using klass }
"".patched? # => true
end
Como el using
es muy estático, se puede emitir con orden de carga si los archivos de refinamiento no se cargan primero. Una forma de abordar esto es envolver la definición de clase / módulo parcheada en un proceso. Por ejemplo:
module Patch
refine String do
def patched; true; end
end
end
class Foo
end
# This is a proc since methods can't contain class definitions
create_patched_class = Proc.new do
Foo.class_exec do
class Bar
using Patch
def self.patched?; ''.patched == true; end
end
end
end
create_patched_class.call
Foo::Bar.patched? # => true
Al llamar al proceso se crea la clase parcheada Foo::Bar
. Esto puede retrasarse hasta que todo el código se haya cargado.