web-dev-qa-db-ja.com

Rubyモジュールのインスタンスメソッドを含めることなく呼び出すことはできますか?

バックグラウンド:

私はいくつかのインスタンスメソッドを宣言するモジュールを持っています

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のメソッドのallを取り込みます。この場合、format_textのみが必要であり、get_filedelete_fileは明示的には必要ありません。

これに対するいくつかの可能な解決策を見ることができます:

  1. どこかにそれを含めずに、モジュールで何らかの方法でメソッドを直接呼び出します
    • どうやって/これができるのかわかりません。 (したがって、この質問)
  2. どういうわけかUsefulthingsをインクルードし、そのメソッドの一部のみを取り込みます
    • また、これがどのように/できるかわからない
  3. プロキシクラスを作成し、その中にUsefulThingsを含め、format_textをそのプロキシインスタンスに委任します
    • これは機能しますが、匿名プロキシクラスはハックです。うん.
  4. モジュールを2つ以上の小さなモジュールに分割します
    • これも機能しますが、おそらく私が考えることができる最良のソリューションですが、何十ものモジュールが急増するため、避けたいと思います-これを管理するのは面倒です

単一のモジュールに多くの無関係な関数があるのはなぜですか? RailsアプリのApplicationHelperであり、私たちのチームが事実上、他の場所に属するほど具体的でないもののゴミ捨て場として決定しました。ほとんどすべての場所で使用されるスタンドアロンのユーティリティメソッド。私はそれを別々のヘルパーに分割することができましたが、30個あり、それぞれ1つのメソッドがあります...これは非生産的です

165
Orion Edwards

モジュールのメソッドがモジュール関数に変換される場合、次のように宣言されているかのように、単に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が呼び出された場合にインクルードがまだ機能することを示す

128
dgtized

(既存のモジュールを変更したり、新しいモジュールを作成したりせずに)使い捨ての呼び出しを行う最短の方法は次のようになると思います。

Class.new.extend(UsefulThings).get_file
134
dolzenko

モジュールを「所有」している場合の別の方法は、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
78
Dustin

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

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

モジュールを別のクラスに含めずにこれらのメソッドを呼び出す場合は、モジュールメソッドとして定義する必要があります。

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

まず、モジュールを必要な便利なものに分割することをお勧めします。ただし、呼び出し用にそれを拡張するクラスをいつでも作成できます。

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
6
Dustin

私は質問を理解しているので、モジュールのインスタンスメソッドのいくつかをクラスにミックスしたいと思います。

Module#include の仕組みを検討することから始めましょう。 2つのインスタンスメソッドを含むモジュールUsefulThingsがあるとします。

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

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

およびFixnumincludesそのモジュール:

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#add3Fixnum#add3を返すので、1.add34を返すと期待していましたか?このことを考慮:

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

クラスがincludesモジュールである場合、モジュールはクラスのスーパークラスになります。したがって、継承の仕組みにより、add3Fixnumのインスタンスに送信すると、Fixnum#add3が呼び出され、dogが返されます。

次に、:add2メソッドをUsefulThingsに追加します。

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

Fixnumincludeにしたいのは、メソッドadd1およびadd3のみです。そうすることで、上記と同じ結果が得られると期待しています。

上記のように実行するとします:

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

結果は何ですか?不要なメソッド:add2Fixnumに追加され、: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
Cary Swoveland

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回限りのスタンドアロン呼び出しを維持する場合は、mixinでextendsのワンライナーモジュールを使用できます。

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よりもきれいです-すべてのメソッドが必要な場合に備えて。

4
inger

ほぼ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
0
thisismydesign