Могу ли я вызвать метод экземпляра в модуле Ruby, не включая его? - PullRequest
165 голосов
/ 27 ноября 2008

Справочная информация:

У меня есть модуль, который объявляет несколько методов экземпляра

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

И я хочу вызвать некоторые из этих методов из класса. То, как вы обычно делаете это в ruby, выглядит так:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Проблема

include UsefulThings вводит всех методов из UsefulThings. В этом случае я хочу только format_text и явно не хочу get_file и delete_file.

Я вижу несколько возможных решений этого:

  1. Каким-то образом вызывать метод непосредственно в модуле, нигде не включая его
    • Я не знаю, как / если это можно сделать. (Отсюда и этот вопрос)
  2. Каким-то образом включить Usefulthings и ввести только некоторые из его методов
    • Я также не знаю, как / если это можно сделать
  3. Создайте прокси-класс, включите в него UsefulThings, затем делегируйте format_text этому экземпляру прокси
    • Это бы сработало, но анонимные прокси-классы - это хак. Тьфу.
  4. Разделите модуль на 2 или более меньших модуля
    • Это также сработало бы и, вероятно, является наилучшим решением, которое я могу придумать, но я бы предпочел избежать этого, поскольку в конечном итоге я получу распространение десятков и десятков модулей - управление этим будет обременительным

Почему в одном модуле много несвязанных функций? Это ApplicationHelper из приложения rails, которое наша команда де-факто определила как полигон для чего-то, что недостаточно специфично, чтобы принадлежать где-либо еще. В основном автономные служебные методы, которые используются везде. Я мог бы разбить его на отдельных помощников, но их было бы 30, все с одним методом каждый ... это кажется непродуктивным

Ответы [ 9 ]

130 голосов
/ 11 сентября 2009

Я думаю, что самый короткий способ сделать простой одноразовый вызов (без изменения существующих модулей или создания новых) будет следующим:

Class.new.extend(UsefulThings).get_file
128 голосов
/ 27 ноября 2008

Если метод модуля превращается в функцию модуля, вы можете просто вызвать его из Mods, как если бы он был объявлен как

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

Приведенный ниже метод module_function позволит избежать взлома любых классов, включающих все моды.

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  

Однако мне любопытно, почему набор не связанных между собой функций в первую очередь содержится в одном модуле?

Отредактировано , чтобы показать, что оно все еще работает, если public :foo вызывается после module_function :foo

77 голосов
/ 27 ноября 2008

Другой способ сделать это, если вы «владеете» модулем, это использовать 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
17 голосов
/ 02 октября 2014

Чтобы вызвать метод экземпляра модуля без включения модуля (и без создания промежуточных объектов):

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end
16 голосов
/ 27 ноября 2008

Если вы хотите вызывать эти методы без включения модуля в другой класс, вам нужно определить их как методы модуля:

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...
end

и затем вы можете позвонить им с

UsefulThings.format_text("xxx")

или

UsefulThings::format_text("xxx")

Но в любом случае я бы порекомендовал поместить только связанные методы в один модуль или в один класс. Если у вас есть проблема с тем, что вы хотите включить только один метод из модуля, то это звучит как неприятный запах кода, и это не очень хороший стиль Ruby для соединения не связанных между собой методов.

5 голосов
/ 27 ноября 2008

Во-первых, я бы рекомендовал разбить модуль на полезные вещи, которые вам нужны. Но вы всегда можете создать класс, расширяющий его для вашего вызова:

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
4 голосов
/ 13 августа 2015

Насколько я понимаю, вы хотите смешать некоторые методы экземпляра модуля в классе.

Давайте начнем с рассмотрения того, как работает Module # include . Предположим, у нас есть модуль UsefulThings, который содержит два метода экземпляра:

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

UsefulThings.instance_methods
  #=> [:add1, :add3]

и Fixnum include с этим модулем:

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

Мы видим, что:

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

Вы ожидали, что UsefulThings#add3 переопределит Fixnum#add3, чтобы 1.add3 вернул 4? Учтите это:

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

Когда класс include s является модулем, модуль становится суперклассом класса. Таким образом, из-за того, как работает наследование, отправка add3 экземпляру Fixnum приведет к вызову Fixnum#add3, возвращающему dog.

Теперь давайте добавим метод :add2 к UsefulThings:

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

Теперь мы желаем от Fixnum до include только методов add1 и add3. При этом мы ожидаем получить те же результаты, что и выше.

Предположим, как указано выше, мы выполняем:

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

Каков результат? Нежелательный метод :add2 добавляется к Fixnum, :add1 добавляется и, по причинам, которые я объяснил выше, :add3 не добавляется. Так что все, что нам нужно сделать, это undef :add2. Мы можем сделать это с помощью простого вспомогательного метода:

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

который мы вызываем так:

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

Тогда:

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

что является желаемым результатом.

4 голосов
/ 07 февраля 2014

A. Если вы всегда хотите вызывать их «квалифицированным», автономным способом (UsefulThings.get_file), то просто сделайте их статическими, как указали другие,

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. Если вы все еще хотите сохранить миксин-подход в тех же случаях, а также однократный автономный вызов, вы можете иметь однострочный модуль, который сам расширяет с помощью миксина:

module UsefulThingsMixin
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

Так что оба работают тогда:

  UsefulThings.get_file()       # one off

   class MyUser
      include UsefulThingsMixin  
      def f
         format_text             # all useful things available directly
      end
   end 

ИМХО, это чище, чем module_function для каждого метода - на случай, если все они понадобятся.

0 голосов
/ 14 ноября 2017

Спустя почти 9 лет вот общее решение:

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

Неудачный трюк, который вам нужно применить, - это включить модуль после определения методов. В качестве альтернативы вы также можете включить его после того, как контекст определен как ModuleIncluded.send(:include, CreateModuleFunctions).

Или вы можете использовать его через refle_utils самоцвет.

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...