operador modulos herencia clases ruby module methods

modulos - operador modulo en ruby



¿Puedo invocar un método de instancia en un módulo de Ruby sin incluirlo? (9)

A. En caso de que usted, siempre quiera llamarlos de una manera independiente y "calificada" (UsefulThings.get_file), simplemente conviértalos estáticos como otros señalaron,

module UsefulThings def self.get_file; ... def self.delete_file; ... def self.format_text(x); ... # Or.. make all of the "static" class << self def write_file; ... def commit_file; ... end end

B. Si aún desea mantener el enfoque de mixin en los mismos casos, así como la invocación independiente de una sola vez, puede tener un módulo de una sola línea que se amplíe con la mezcla:

module UsefulThingsMixin def get_file; ... def delete_file; ... def format_text(x); ... end module UsefulThings extend UsefulThingsMixin end

Entonces ambos trabajos entonces:

UsefulThings.get_file() # one off class MyUser include UsefulThingsMixin def f format_text # all useful things available directly end end

En mi humilde opinión, es más limpio que module_function para cada método, en caso de que quiera todos.

Fondo:

Tengo un módulo que declara una cantidad de métodos de instancia

module UsefulThings def get_file; ... def delete_file; ... def format_text(x); ... end

Y quiero llamar a algunos de estos métodos desde dentro de una clase. Cómo lo haces normalmente en ruby ​​es así:

class UsefulWorker include UsefulThings def do_work format_text("abc") ... end end

Problema

include UsefulThings trae todos los métodos de UsefulThings . En este caso, solo quiero format_text y explícitamente no quiero get_file y delete_file .

Puedo ver varias soluciones posibles para esto:

  1. De alguna manera invoque el método directamente en el módulo sin incluirlo en ningún lado
    • No sé cómo / si esto puede hacerse. (De ahí esta pregunta)
  2. De alguna manera, incluye Usefulthings y solo trae algunos de sus métodos
    • Tampoco sé cómo / si esto puede hacerse
  3. Cree una clase proxy, incluya UsefulThings en eso y luego format_text a esa instancia proxy
    • Esto funcionaría, pero las clases proxy anónimas son un truco. Yuck.
  4. Divida el módulo en 2 o más módulos más pequeños
    • Esto también funcionaría, y es probablemente la mejor solución que se me ocurre, pero preferiría evitarlo, ya que terminaría con una proliferación de docenas y docenas de módulos. Gestionar esto sería engorroso.

¿Por qué hay muchas funciones no relacionadas en un solo módulo? Es ApplicationHelper de una aplicación de rieles, que nuestro equipo ha decidido defacto como el basurero de cualquier cosa que no sea lo suficientemente específica para pertenecer a otro lado. La mayoría de los métodos de utilidad independientes que se utilizan en todas partes. Podría dividirlo en ayudantes separados, pero serían 30, todos con 1 método cada uno ... esto parece improductivo


Creo que la forma más sencilla de hacer una única llamada descartable (sin alterar los módulos existentes ni crear nuevas) sería la siguiente:

Class.new.extend(UsefulThings).get_file


Después de casi 9 años, aquí hay una solución genérica:

module CreateModuleFunctions def self.included(base) base.instance_methods.each do |method| base.module_eval do module_function(method) public(method) end end end end RSpec.describe CreateModuleFunctions do context "when included into a Module" do it "makes the Module''s methods invokable via the Module" do module ModuleIncluded def instance_method_1;end def instance_method_2;end include CreateModuleFunctions end expect { ModuleIncluded.instance_method_1 }.to_not raise_error end end end

El desafortunado truco que debe aplicar es incluir el módulo después de que se hayan definido los métodos. Alternativamente, también puede incluirlo después de que el contexto se define como ModuleIncluded.send(:include, CreateModuleFunctions) .

O puede usarlo a través de la gema reflection_utils .

spec.add_dependency "reflection_utils", ">= 0.3.0" require ''reflection_utils'' include ReflectionUtils::CreateModuleFunctions


En primer lugar, recomendaría dividir el módulo en las cosas útiles que necesita. Pero siempre puedes crear una clase que amplíe eso para tu invocación:

module UsefulThings def a puts "aaay" end def b puts "beee" end end def test ob = Class.new.send(:include, UsefulThings).new ob.a end test


Otra forma de hacerlo si "posee" el módulo es usar module_function .

module UsefulThings def a puts "aaay" end module_function :a def b puts "beee" end end def test UsefulThings.a UsefulThings.b # Fails! Not a module method end test


Para invocar un método de instancia de módulo sin incluir el módulo (y sin crear objetos intermedios):

class UsefulWorker def do_work UsefulThings.instance_method(:format_text).bind(self).call("abc") ... end end


Según entiendo la pregunta, quiere mezclar algunos de los métodos de instancia de un módulo en una clase.

Comencemos por considerar cómo funciona el Module#include . Supongamos que tenemos un módulo UsefulThings que contiene dos métodos de instancia:

module UsefulThings def add1 self + 1 end def add3 self + 3 end end UsefulThings.instance_methods #=> [:add1, :add3]

y Fixnum include s ese módulo:

class Fixnum def add2 puts "cat" end def add3 puts "dog" end include UsefulThings end

Vemos eso:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" } #=> [:add2, :add3, :add1] 1.add1 2 1.add2 cat 1.add3 dog

¿Esperaba que UsefulThings#add3 anulara Fixnum#add3 , para que 1.add3 devolviera 4 ? Considera esto:

Fixnum.ancestors #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable, # Object, Kernel, BasicObject]

Cuando la clase include s el módulo, el módulo se convierte en la clase ''superclase''. Entonces, debido a cómo funciona la herencia, el envío de add3 a una instancia de Fixnum hará que se Fixnum#add3 , devolviendo el dog .

Ahora agreguemos un método :add2 a UsefulThings :

module UsefulThings def add1 self + 1 end def add2 self + 2 end def add3 self + 3 end end

Ahora deseamos que Fixnum include solo los métodos add1 y add3 . Si lo hace, esperamos obtener los mismos resultados que arriba.

Supongamos que, como arriba, ejecutamos:

class Fixnum def add2 puts "cat" end def add3 puts "dog" end include UsefulThings end

Cual es el resultado? El método no deseado :add2 se agrega a Fixnum , se agrega :add1 y, por las razones que expliqué anteriormente, no se agrega :add3 . Entonces todo lo que tenemos que hacer es undef :add2 . Podemos hacer eso con un método de ayuda simple:

module Helpers def self.include_some(mod, klass, *args) klass.send(:include, mod) (mod.instance_methods - args - klass.instance_methods).each do |m| klass.send(:undef_method, m) end end end

que invocamos así:

class Fixnum def add2 puts "cat" end def add3 puts "dog" end Helpers.include_some(UsefulThings, self, :add1, :add3) end

Entonces:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" } #=> [:add2, :add3, :add1] 1.add1 2 1.add2 cat 1.add3 dog

cual es el resultado que queremos


Si desea llamar a estos métodos sin incluir el módulo en otra clase, debe definirlos como métodos de módulo:

module UsefulThings def self.get_file; ... def self.delete_file; ... def self.format_text(x); ... end

y luego puedes llamarlos con

UsefulThings.format_text("xxx")

o

UsefulThings::format_text("xxx")

Pero, de todos modos, recomendaría que coloque solo los métodos relacionados en un módulo o en una clase. Si tiene un problema con que desea incluir solo un método del módulo, entonces suena como un mal olor de código y no es bueno el estilo de Ruby para unir métodos no relacionados.


Si un método en un módulo se convierte en una función de módulo, simplemente puede cancelarlo de Mods como si hubiera sido declarado como

module Mods def self.foo puts "Mods.foo(self)" end end

El método module_function a continuación evitará romper cualquier clase que incluya todos los Mods.

module Mods def foo puts "Mods.foo" end end class Includer include Mods end Includer.new.foo Mods.module_eval do module_function(:foo) public :foo end Includer.new.foo # this would break without public :foo above class Thing def bar Mods.foo end end Thing.new.bar

Sin embargo, tengo curiosidad por saber por qué un conjunto de funciones no relacionadas están todas contenidas dentro del mismo módulo en primer lugar.

Editado para mostrar que incluye todavía funciona si public :foo se llama a public :foo después de la función module_function :foo