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à!
-
Ruby転置法を使用して行を列に変換する
今日は、Ruby転置法を使用してRubyでグリッドを処理する方法を学習します。 多次元配列の形をした完璧なグリッド、たとえば3×3の正方形があると想像してみてください。 そして、行を取得して列に変換する 。 なぜあなたはそれをしたいのですか? 1つの用途は、古典的なゲームであるtic-tac-toeです。 ボードをグリッドとして保存します。次に、勝利の動きを見つけるには、行を確認する必要があります 、列 &対角線 。 問題は、グリッドを配列として格納している場合、行に直接アクセスすることしかできないことです。 コラムズザハードウェイ 「直接アクセス」とは、配列を(eachで)調べ
-
メタプログラミングの隠れたコスト
メタプログラミングは非常に派手な言葉のように聞こえますが、それは何か良いことですか? 便利な場合もありますが、メタプログラミングの使用にはいくらかのコストがかかることに多くの人が気づいていません。 同じページにいるので… メタプログラミングとは 正確に? 私はメタプログラミングを次のような方法を使用するものとして定義しています: コードの構造を変更します(define_methodなど) ) 文字列を実際のRubyコードの一部であるかのように実行します(instance_evalなど)。 ) 何らかのイベントへの反応として何かを行います(method_missingなど) ) で