5

まず、短いバージョンの場合:

メソッド定義は単なるブロックではありませんか?なぜ私は次のようなことができないのですか?

obj.instance_exec(&other_obj.method(:my_method))

別のクラスのインスタンスのコンテキストでモジュールメソッドを実行することを目的としていますか?このメソッドは呼び出されますが、「instance_exec」呼び出しにもかかわらず、「obj」のコンテキストでは実行されていないようです。

これを実現する方法を理解する唯一の方法は、「my_method」のすべてのコードをprocでラップし、代わりに次の方法で呼び出すことです。

obj.instance_eval(&other_obj.my_method)

ただし、すべてのモジュールメソッドをprocsにカプセル化することは避けたいと思います。


さて、長いバージョンの場合:

モジュール化された外部プロバイダーシステムを作成しようとしています。ここで、特定のクラス/メソッド(通常はコントローラーメソッド)に対して、特定のプロバイダー(Facebookなど)の対応するメソッドを呼び出すことができます。

複数のプロバイダーが存在する可能性があるため、プロバイダーメソッドには名前空間を付ける必要がありますが、たとえば「facebook_invitation_create」のような一連のメソッドを単に含めるのではなく、InvitationsControllerインスタンスにcreateメソッドを含むfacebookメンバーを持たせたいです-例えば

class InvitationsController < ApplicationController
  def create
    ...
    # e.g. self.facebook.create
    self.send(params[:provider]).create
    ...
  end
end

さらに、プロバイダーメソッドは、コントローラー自体の一部であるかのように機能するだけでなく、コントローラーインスタンス変数、パラメーター、セッションなどにアクセスできる必要があります。また、(ほとんどの場合)記述される必要があります。それらがコントローラー自体の一部であるかのように-モジュール化された結果として複雑な追加コードがないことを意味します。

以下に簡単な例を作成しました。この例では、MyClassにgreetメソッドがあり、有効なプロバイダー名(この場合は:facebook)で呼び出されると、代わりにそのプロバイダーのgreetメソッドが呼び出されます。次に、プロバイダーgreetメソッドは、クラス自体の一部であるかのように、インクルードクラスのメッセージメソッドにアクセスします。

module Providers
  def facebook
    @facebook ||= FacebookProvider
  end

  module FacebookProvider
    class << self
      def greet
        proc {
          "#{message} from facebook!"
        }
      end
    end
  end
end

class MyClass
  include Providers
  attr_accessor :message

  def initialize(message="hello")
    self.message = message
  end

  def greet(provider=nil)
    (provider.nil? or !self.respond_to?(provider)) ? message : instance_exec(&self.send(provider).greet)
  end
end

これにより、以前に述べたほとんどすべてが実際に実行されますが、プロバイダー関数をprocにカプセル化する必要があるという事実に悩まされています。代わりに、メソッドでinstance_execを呼び出すだけでよいと思いました(procカプセル化を削除した後)。

instance_exec(&self.send(provider).method(:greet))

...しかし、エラーが発生したため、instance_execは無視されているようです。

NameError: undefined local variable or method `message' for Providers::FacebookProvider:Module

定義されたメソッドでinstance_execを呼び出す方法はありますか?

(これをより適切に実装する方法についての提案も受け付けています...)

4

2 に答える 2

9

これはあなたが思っているよりも簡単だと思います(そして私の答えはあなたが尋ねてから2年後だと思います)

モジュールのインスタンスメソッドを使用して、それらを任意のオブジェクトにバインドできます。

module Providers
  def facebook
    @facebook ||= FacebookProvider
  end

  module FacebookProvider
    def greet
      "#{message} from facebook!"
    end
  end
end

class MyClass
  include Providers
  attr_accessor :message

  def initialize(message="hello")
    self.message = message
  end

  def greet(provider=nil)
    if provider
      provider.instance_method(:greet).bind(self).call
    else
      message
    end
  end
end

providerがモジュールの場合、ユーザーinstance_methodがを作成UnboundMethodして現在のにバインドできますself

これは委任です。これは、次のように機能する鋳造宝石の基礎です。

delegate(:greet, provider)

または、キャストからの使用をオプトインした場合method_missing、コードは次のようになります。

greet

ただし、最初にデリゲートを設定する必要があります。

class MyClass
  include Providers
  include Casting::Client
  delegate_missing_methods
  attr_accessor :message

  def initialize(message="hello", provider=facebook)
    cast_as(provider)
    self.message = message
  end
end

MyClass.new.greet # => "hello from facebook!"

DCIの理解に関連する委任とは何か、ブログにはないこと、およびCleanRubyで書いたことについて書きました。

于 2015-04-15T21:12:12.483 に答える
0

たぶん私はフォローしていませんが、あなたはこれを必要以上に難しくしているようです。

クラスに「ディスパッチ」パターンを実装してみませんか。プロバイダー名とプロバイダーメソッドのハッシュ{:facebook => "facebook_greet"}があり、着信呼び出しを「Object#send」を介して正しいハンドラーに「送信」します。 "(http://ruby-doc.org/core-1.9.3/Object.html#method-i-send)?Sendはメソッドのディスパッチに対して非常に高速であるため、evalとは異なり、優れたパフォーマンスが得られるはずです。

これが私がそれを解決する方法を示すためのいくつかのコードです(私があなたが達成しようとしていることと一緒にフォローしていると仮定します):

module TwitterProvider
  def providerInit(providers)
    @providers[:twitter]="twitter_greet"
    super(providers) if defined?(super)
  end
  def twitter_greet
    "Hello Twitter User"
  end
end

module FacebookProvider
  def providerInit(providers)
    providers[:facebook]="facebook_greet"
    super(providers) if defined?(super)
  end
  def facebook_greet
    "Hello Facebook User"
  end
end


class MyClass
  include FacebookProvider
  include TwitterProvider
  attr_accessor :message

  def providerInit(providers)
    super(providers) if defined?(super)
  end

  def initialize(message="hello")
    @providers = {}
    self.message = message
    providerInit(@providers)
  end

  def greet(provider=nil)
    if provider.nil? or !self.respond_to?(@providers[provider])
      self.message
    else
      self.send(@providers[provider])
    end
  end
end

my_class = MyClass.new
puts my_class.greet
puts my_class.greet(:twitter)
puts my_class.greet(:facebook)

# Output:
# hello
# Hello Twitter User
# Hello Facebook User
于 2013-07-05T23:27:14.873 に答える