Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

method_missingを使用して祖先チェーンをアップ

今日は先祖の鎖をずっと上っていくので、機内持ち込み手荷物を握り締めてください。メソッド呼び出しに従って、それがチェーンをどのように上るのかを確認し、メソッドが欠落している場合に何が起こるかを調べます。そして、私たちは火遊びが大好きなので、そこで止まることなく、火遊びを続けます。 BasicObject#method_missingをオーバーライドする 。注意を払えば、実際の例でも使用できるかもしれません。ただし、保証はありません。行きましょう!

祖先チェーン

Rubyの祖先チェーンの基本的なルールから始めましょう:

  • Rubyは単一の継承のみをサポートします
  • オブジェクトにモジュールのセットを含めることもできます

Rubyでは、祖先チェーンは、特定のクラスのすべての継承されたクラスとモジュールのトラバーサルで構成されます。

Rubyで祖先チェーンがどのように処理されるかを示す例を見てみましょう。

module Auth end
 
module Session end
 
module Iterable end
 
class Collection
  prepend Iterable
end
 
class Users < Collection
  prepend Session
  include Auth
end
 
p Users.ancestors

生成:

[
  Session, Users, Auth,        # Users
  Iterable, Collection,        # Collection
  Object, Kernel, BasicObject  # Ruby Object Model
]

まず、ancestorsと呼びます 特定のクラスの祖先チェーンにアクセスするためのクラスメソッド。

Users.ancestorsへの呼び出しがわかります 以下を含むクラスとモジュールの配列を順番に返します:

  • Usersの追加モジュール
  • Users クラス
  • Users クラスに含まれるモジュール
  • Collection クラスの先頭に追加されたモジュール—ユーザーの直接の親として
  • Collection クラス
  • Collection クラスに含まれるモジュール—なし
  • Object class —任意のクラスのデフォルトの継承
  • Kernel モジュール—Objectに含まれています コアメソッドの保持
  • BasicObject class —Rubyのルートクラス

したがって、特定のトラバースされたクラスまたはモジュールの出現順序は、常に次のとおりです。

  • 追加されたモジュール
  • クラスまたはモジュール
  • 含まれているモジュール

祖先チェーンは、メソッドがオブジェクトまたはクラスで呼び出されたときに、主にRubyによってトラバースされます。

メソッドルックアップパス

メッセージが送信されると、Rubyはメッセージ受信者の祖先チェーンをトラバースし、指定されたメッセージに応答するメッセージがあるかどうかを確認します。

祖先チェーンの特定のクラスまたはモジュールがメッセージに応答すると、このメッセージに関連付けられたメソッドが実行され、祖先チェーンのトラバーサルが停止します。

class Collection < Array
end
 
Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject]
 
collection = Collection.new([:a, :b, :c])
 
collection.each_with_index # => :a

ここでは、collection.each_with_index メッセージはEnumerableモジュールによって受信されます。次に、Enumerable#each_with_index このメッセージに対してメソッドが呼び出されます。

ここで、collection.each_with_index が呼び出されると、Rubyは次のことをチェックします:

  • コレクションはeach_with_index messageに応答します =>いいえ
  • 配列はeach_with_index messageに応答します =>いいえ
  • 列挙型はeach_with_index messageに応答します =>はい

したがって、ここから、Rubyは祖先チェーンの走査を停止し、このメッセージに関連付けられたメソッドを呼び出します。この場合、Enumerable#each_with_index メソッド。

Rubyでは、このメカニズムはメソッドルックアップパスと呼ばれます。 。

では、特定の受信者の祖先チェーンを構成するクラスとモジュールのいずれもメッセージに応答しない場合はどうなりますか?

BasicObject#method_missing

いいプレーで十分!例外をスローすることで、開発者のスタイルを破りましょう。 Collectionを実装します クラスを作成し、そのインスタンスの1つで不明なメソッドを呼び出します。

class Collection
end
 
c = Collection.new
c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>

ここでは、Collection クラスはsearchを実装していません 方法。したがって、NoMethodError 上げられます。しかし、このエラーの発生はどこから来ているのでしょうか?

BasicObject#method_missingでエラーが発生します 方法。このメソッドは、メソッドルックアップパスのときに呼び出されます。 特定のメッセージに対応するメソッドが見つからないことになります。

わかりました...しかし、このメソッドはNoMethodErrorを発生させるだけです 。したがって、Collectionのコンテキストでメソッドをオーバーライドできると便利です。 クラス。

BasicObject#method_missingをオーバーライドする 方法

何だと思う? method_missingをオーバーライドするのはまったく問題ありません このメソッドは、メソッドルックアップパスのメカニズムにも影響されるためです。 。通常のメソッドとの唯一の違いは、このメソッドがメソッドルックアップパスによって少なくとも1回検出されることが確実であることです。 。

確かに、BasicObject class —これはRubyの任意のクラスのルートクラスです—このメソッドの最小バージョンを定義します。クラシックなルビーマジック、ネストパ?

それでは、Collection classでこのメソッドをオーバーライドしましょう。 :

class Collection
  def initialize
    @collection = {}
  end
 
  def method_missing(method_id, *args)
    if method_id[-1] == '='
      key = method_id[0..-2]
      @collection[key.to_sym] = args.first
    else
      @collection[method_id]
    end
  end
end
 
collection = Collection.new
collection.obj1 = 'value1'
collection.obj2 = 'value2'
 
collection.obj1 # => 'value1'
collection.obj2 # => 'value2'

ここでは、Collection#method_missing @collectionへの委任者として機能します インスタンス変数。実際、これはRubyがオブジェクトの委任を大まかに処理する方法です— c.f:delegate ライブラリ。

欠落しているメソッドがセッターメソッドの場合(collection.obj1 = 'value1' )、次にメソッド名(:obj1 )はキーと引数として使用されます('value1'@collectionの値として ハッシュエントリ(@collection[:obj1] = 'value1'

HTMLタグジェネレーター

これで、method_missing メソッドは舞台裏で機能します。再現可能なユースケースを実装しましょう。

ここでの目標は、次のDSLを定義することです。

HTML.p    'hello world'             # => <p>hello world</p>
HTML.div  'hello world'             # => <div>hello world</div>
HTML.h1   'hello world'             # => <h1>hello world</h1>
HTML.h2   'hello world'             # => <h2>hello world</h2>
HTML.span 'hello world'             # => <span>hello world</span>
HTML.p    "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>

そのために、HTML.method_missingを実装します。 各HTMLタグのメソッドの定義を回避するためのメソッド。

まず、HTMLを定義します モジュール。次に、method_missingを定義します このモジュールのクラスメソッド:

module HTML
  def HTML.method_missing(method_id, *args, &block)
    "<#{method_id}>#{args.first}</#{method_id}>"
  end
end

このメソッドは、欠落しているmethod_idを使用してHTMLタグを作成するだけです。 — :div HTML.divの呼び出し 、たとえば。

クラスメソッドもメソッドルックアップパスの対象となることに注意してください。

HTMLタグジェネレータを次のように拡張できます:

  • ブロック引数を使用してネストされたタグを処理する
  • 単一のタグの処理— <br/> たとえば

ただし、数行のコードで、大量のHTMLタグを生成できることに注意してください。

要約すると:

method_missing は、ほとんどのコマンドが識別されたパターンのセットを共有するDSLを作成するための優れたエントリポイントです。

結論

Rubyの祖先チェーンをずっと上って、BasicObject#method_missingに飛び込みました。 。 BasicObject#method_missing ルビーフックメソッドの一部です 。これは、ライフサイクルの正確な瞬間にオブジェクトと対話するために使用されます。他のルビーフックメソッドと同様に 、このフックメソッドは慎重に使用する必要があります。また、慎重に言うと、Rubyオブジェクトモデルの動作を変更してはならないということです。 —それで遊んでいるときやブログ投稿を書いているときを除いて;-)

Voilà!


  1. Ruby転置法を使用して行を列に変換する

    今日は、Ruby転置法を使用してRubyでグリッドを処理する方法を学習します。 多次元配列の形をした完璧なグリッド、たとえば3×3の正方形があると想像してみてください。 そして、行を取得して列に変換する 。 なぜあなたはそれをしたいのですか? 1つの用途は、古典的なゲームであるtic-tac-toeです。 ボードをグリッドとして保存します。次に、勝利の動きを見つけるには、行を確認する必要があります 、列 &対角線 。 問題は、グリッドを配列として格納している場合、行に直接アクセスすることしかできないことです。 コラムズザハードウェイ 「直接アクセス」とは、配列を(eachで)調べ

  2. メタプログラミングの隠れたコスト

    メタプログラミングは非常に派手な言葉のように聞こえますが、それは何か良いことですか? 便利な場合もありますが、メタプログラミングの使用にはいくらかのコストがかかることに多くの人が気づいていません。 同じページにいるので… メタプログラミングとは 正確に? 私はメタプログラミングを次のような方法を使用するものとして定義しています: コードの構造を変更します(define_methodなど) ) 文字列を実際のRubyコードの一部であるかのように実行します(instance_evalなど)。 ) 何らかのイベントへの反応として何かを行います(method_missingなど) ) で