Ruby 모듈에서 인스턴스 메소드를 포함시키지 않고 호출 할 수 있습니까?


181

배경:

많은 인스턴스 메소드를 선언하는 모듈이 있습니다

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

  def format_text(x); ...
end

그리고 클래스 내에서 이러한 메소드 중 일부를 호출하고 싶습니다. 루비에서 일반적 으로이 작업을 수행하는 방법은 다음과 같습니다.

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 개 이상의 작은 모듈로 분할
    • 이것은 또한 효과가 있으며 아마도 내가 생각할 수있는 가장 좋은 해결책 일 것입니다. 그러나 수십 및 수십 개의 모듈이 확산되면 피하는 것이 좋습니다.

단일 모듈에 관련없는 기능이 많은 이유는 무엇입니까? 그것은의 ApplicationHelperA는 아무 것도 다른 곳 속한하지 특정 정도에 대한 덤핑 지상으로 우리 팀이 사실상 결정적인를 가지고 응용 프로그램을, 레일에서. 거의 모든 곳에서 사용되는 독립형 유틸리티 방법. 나는 그것을 별도의 도우미로 나눌 수는 있지만 30 가지가 있었으며 모두 1 가지 방법이 있습니다 ... 이것은 비생산적 인 것 같습니다.


네 번째 접근 방식 (모듈 분할)을 수행하는 경우 Module#included콜백 을 사용하여 다른 모듈 중 하나 를 트리거 하여 한 모듈이 다른 모듈을 항상 자동으로 포함하도록 할 수 있습니다 include. 이 format_text방법은 자체적으로 유용하기 때문에 자체 모듈로 이동할 수 있습니다. 이것은 관리 부담을 덜 줄입니다.
Batkins

모듈 함수에 대한 답변의 모든 참조에 당황합니다. 수업 module UT; def add1; self+1; end; def add2; self+2; end; end중이 add1아닌 사용하고 싶다고 가정합니다 . 이를 위해 모듈 기능을 갖는 것이 어떻게 도움이됩니까? 뭔가 빠졌습니까? add2Fixnum
캐리 스월 랜드

답변:


135

모듈의 메소드가 모듈 함수로 바뀌면 다음과 같이 선언 된 것처럼 Mods에서 간단히 호출 할 수 있습니다

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

아래의 module_function 접근 방식은 모든 Mod를 포함하는 클래스를 중단하지 않습니다.

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


1
옆으로, module_function다른 코드를 깨뜨릴 수있는 메소드를 개인 메소드로 바꿉니다. 그렇지 않으면 이것이 정답
Orion Edwards

나는 괜찮은 일을하고 코드를 별도의 모듈로 리팩토링했습니다. 내가 생각했던 것만 큼 나쁘지 않았습니다. 귀하의 답변은 여전히 ​​원래의 제약 조건을 WRT로 가장 정확하게 해결할 것이므로 받아 들였습니다!
오리온 에드워즈

@dgtized 관련 함수는 항상 하나의 모듈로 끝날 수 있습니다. 즉, 네임 스페이스를 모든 모듈로 오염시키고 싶지는 않습니다. a Files.truncate와 a가 Strings.truncate있고 동일한 클래스에서 명시 적으로 둘 다 사용하려는 간단한 예 입니다. 루비 개발자는 아니지만 특정 방법이 필요할 때마다 새 클래스 / 인스턴스를 만들거나 원본을 수정하는 것은 좋은 방법이 아닙니다.
TWiStErRob

146

기존 모듈을 변경하거나 새 모듈을 만들지 않고 단일 호출을 버리는 가장 짧은 방법은 다음과 같습니다.

Class.new.extend(UsefulThings).get_file

2
파일 erb에 매우 유용합니다. html.erb 또는 js.erb. 감사 ! 이 시스템이 메모리를 낭비하는지 궁금합니다
Albert Català

5
내 테스트와 stackoverflow.com/a/23645285/54247 에 따르면 @ AlbertCatalà 익명 클래스는 다른 모든 것과 마찬가지로 가비지 수집되므로 메모리를 낭비하지 않아야합니다.
dolzenko

1
익명 클래스를 프록시로 만들고 싶지 않으면 객체를 메소드의 프록시로 사용할 수도 있습니다. Object.new.extend(UsefulThings).get_file
3limin4t0r

83

모듈을 "소유"하는 경우이를 수행하는 다른 방법은을 사용하는 것 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

27
그리고 당신이 그것을 소유하지 않은 경우 : UsefulThings.send : module_function, : b
Dustin

3
module_function 다른 발신자를 :-( 휴식 것이 개인 일 (잘 어쨌든 내 IRB에서와)의 방법으로 변환
오리온 에드워즈

나는 적어도 내 목적을 위해이 접근법을 좋아합니다. 이제 ModuleName.method :method_name메소드 객체를 얻고를 통해 호출 할 수 method_obj.call있습니다. 그렇지 않으면 원래 객체의 인스턴스에 메소드를 바인딩해야합니다. 원래 객체가 모듈이면 불가능합니다. Orion Edwards에 대한 응답으로 module_function원래 인스턴스 방법을 비공개로 만듭니다. ruby-doc.org/core/classes/Module.html#M001642
John

2
오리온-나는 그것이 사실이라고 생각하지 않습니다. 에 따르면ruby-doc.org/docs/ProgrammingRuby/html/… , module_function은 "명명 된 메소드에 대한 모듈 함수를 작성합니다. 이러한 함수는 모듈과 함께 수신자로 호출 될 수 있으며 혼합 된 클래스에 대한 인스턴스 메소드로도 사용 가능해집니다" 모듈 함수는 원본의 복사본이므로 독립적으로 변경 될 수 있습니다. 인스턴스 메소드 버전은 비공개로 설정됩니다. 인수없이 사용하면 이후에 정의 된 메소드가 모듈 함수가됩니다. "
Ryan Crispin Heneise

2
당신은 또한 그것을로 정의 할 수있다def self.a; puts 'aaay'; end
는 Tila를

17

다른 클래스에 모듈을 포함시키지 않고 이러한 메소드를 호출하려면 해당 메소드를 모듈 메소드로 정의해야합니다.

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 스타일이 아닙니다.


17

모듈을 포함하지 않고 (중개 오브젝트를 작성하지 않고) 모듈 인스턴스 메소드를 호출하려면 다음을 수행하십시오.

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

이 접근 방식에주의하십시오 : self에 바인딩하면 메소드가 기대하는 모든 것을 제공하지 않을 수 있습니다. 예를 들어, format_text모듈에 의해 제공되는 다른 방법이 존재한다고 가정 할 수 있습니다 (일반적으로).
Nathan

이것은 모듈 지원 방법을 직접로드 할 수 있는지 여부에 관계없이 모든 모듈을로드 할 수있는 방법입니다. 심지어 모듈 수준에서 변경하는 것이 좋습니다. 그러나 경우에 따라이 줄은 요청자가 원하는 것입니다.
twindai

6

먼저, 모듈을 필요한 유용한 것들로 나누는 것이 좋습니다. 그러나 항상 호출을 위해 클래스를 확장 할 수 있습니다.

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

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 

IMHO module_function모든 방법 보다 깨끗 합니다.


extend self일반적인 관용구입니다.
smathy

4

질문을 이해하면서 모듈의 인스턴스 메소드 중 일부를 클래스에 혼합하려고합니다.

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가 모듈 일 때, 모듈은 클래스의 슈퍼 클래스가됩니다. 따라서 상속이 작동하는 방식으로 인해 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

우리는 지금 희망 Fixnuminclude유일한 방법 add1add3. 그렇게하면 위와 동일한 결과를 얻을 것으로 예상됩니다.

위와 같이 다음을 실행한다고 가정 해보십시오.

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

이것이 우리가 원하는 결과입니다.


2

누군가 10 년 후에도 여전히 그것을 필요로하는지 모르겠지만 고유 클래스를 사용하여 해결했습니다.

module UsefulThings
  def useful_thing_1
    "thing_1"
  end

  class << self
    include UsefulThings
  end
end

class A
  include UsefulThings
end

class B
  extend UsefulThings
end

UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"

0

거의 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).

또는 reflection_utils gem을 통해 사용할 수 있습니다 .

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

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

글쎄, 우리가 볼 수있는 대다수의 응답과 같은 접근 방식은 원래 문제를 해결하고 모든 방법을로드하지 않습니다. 유일한 대답은 @renier가 이미 3 년 전에 응답했기 때문에 원래 모듈에서 메소드를 바인딩 해제하고 대상 클래스에 바인딩하는 것입니다.
Joel AZEMAR

@JoelAZEMAR이 솔루션을 오해하고 있다고 생각합니다. 사용하려는 모듈에 추가됩니다. 결과적으로이 방법을 사용하기 위해 포함 된 방법은 없습니다. 가능한 해결책 중 하나로서 OP에서 제안한 바와 같이 : "1, 어떻게 든 메소드를 어디서나 포함시키지 않고 모듈에서 직접 호출합니다". 이것이 작동하는 방식입니다.
thisismydesign
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.