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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow