Rubyモジュールのインスタンスメソッドを、それを含めずに呼び出すことはできますか?


180

バックグラウンド:

いくつかのインスタンスメソッドを宣言するモジュールがあります

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があり、すべて1つのメソッドで...これは非生産的なようです


4番目のアプローチ(モジュールの分割)を行う場合、Module#includedコールバックを使用してもう一方のモジュールをトリガーすることincludeにより、一方のモジュールが常に他方を自動的に含めるようにすることができます。このformat_textメソッドは、それ自体で有用であると思われるため、独自のモジュールに移動できます。これにより、管理の負担が少し軽減されます。
Batkins 2013年

モジュール関数に対する回答のすべての参照に戸惑っています。あなたが持っていて、クラスではなくmodule UT; def add1; self+1; end; def add2; self+2; end; end使用したいとしますadd1add2Fixnum。そのためのモジュール関数があるとどのように役立ちますか?何か不足していますか?
Cary Swoveland、2015

回答:


135

モジュールのメソッドがモジュール関数に変換されている場合は、次のように宣言されているかのように、Modから簡単に呼び出すことができます。

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関連関数は、常に1つのモジュールで終了する可能性があります。これは、すべての名前空間をそれらで汚染したいという意味ではありません。a Files.truncateとaがStrings.truncateあり、明示的に同じクラスで両方を使用したい場合の簡単な例。Rubyの開発者ではありませんが、特定のメソッドが必要になるたびに新しいクラス/インスタンスを作成したり、元のクラスを変更したりするのは良い方法ではありません。
TWiStErRob

146

(既存のモジュールを変更したり、新しいモジュールを作成したりせずに)単一の呼び出しを破棄する最も簡単な方法は、次のようになると思います。

Class.new.extend(UsefulThings).get_file

2
ファイルerbに非常に役立ちます。html.erbまたはjs.erb。ありがとう!このシステムはメモリを浪費するのではないかと思います
AlbertCatalà

5
@AlbertCatalàによると、私のテストとstackoverflow.com/a/23645285/54247によると、匿名クラスは他のすべてと同じようにガベージコレクションされるため、メモリを浪費してはなりません。
ドルツェンコ

1
匿名クラスをプロキシとして作成したくない場合は、メソッドのプロキシとしてオブジェクトを使用することもできます。Object.new.extend(UsefulThings).get_file
3limin4t0r 2018

82

モジュールを「所有」している場合に行う別の方法は、を使用すること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

26
そして、あなたがそれを所有していない場合のために: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
Tilo

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")

とにかく、関連するメソッドだけを1つのモジュールまたは1つのクラスに配置することをお勧めします。モジュールからメソッドを1つだけ含めたいという問題がある場合、コードのにおいが悪いように聞こえ、無関係なメソッドをまとめるのは良いRubyスタイルではありません。


17

モジュールを含めずに(および中間オブジェクトを作成せずに)モジュールインスタンスメソッドを呼び出すには:

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

このアプローチには注意してください。自己にバインドしても、期待するすべての機能がメソッドに提供されない場合があります。たとえばformat_text、モジュールによって提供される別のメソッドが存在することを想定します。このメソッドは(通常)存在しません。
ネイサン

これは、モジュールサポートメソッドを直接ロードできるかどうかに関係なく、任意のモジュールをロードできる方法です。モジュールレベルで変更することをお勧めします。しかし、場合によっては、この行は質問者が取得したいものです。
twindai 2017

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.同じケースでミックスインアプローチを維持したい場合、および1回限りのスタンドアロンの呼び出しと同様に、ミックスインで拡張するワンライナーモジュールを使用できます。

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はすべての方法が必要な場合に備えて、すべての方法よりもきれいです。


extend self一般的なイディオムです。
smathy

4

質問を理解しているので、モジュールのインスタンスメソッドのいくつかをクラスに混在させたいと考えています。

Module#includeがどのように機能するかを考えることから始めましょう。UsefulThings2つのインスタンスメソッドを含むモジュールがあるとします。

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年経ってもまだそれが必要かどうかはわかりませんが、私はeigenclassを使用して解決しました。

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年前にすでに応答しているので、唯一の適切な答えは、元のモジュールからメソッドをアンバインドし、それをターゲットクラスにバインドすることです。
ジョエル・アゼマー

@JoelAZEMAR私はあなたがこの解決策を誤解していると思います。使用したいモジュールに追加します。その結果、それらを使用するためにそのメソッドを含める必要はありません。可能な解決策の1つとしてOPによって提案されているように:「1、どこかにそれをどこにも含めずにモジュールで直接メソッドを呼び出す」。これがどのように機能するかです。
thisismydesign 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.