見出し画像

ポンコツ・キャンプ -MiniTestの簡単なツールを作るまでの道程-

何とかキャンプってのが流行っているようなので、ポンコツながら、少しでもお役に立てるかもしれないと真面目に取り組んでみようかと思います。

  • ゴール:
    メソッドの存在を確認するテスト用ツールの作成
    public, private, protectedの属性別でメソッドの確認を出来るようにする
    要は、assert_respond_toを便利にしたツール

  • 手にすることが出来る知識:
    配列の積集合、差集合といった配列を集合として操作する知識
    実験的方法から始め、ツールを入手するまでのドロ臭い手法
    普段使わないRubyのメソッドの知識(public_instance_methods,
    private_instance_methods, send)

では、さっそくはじめます。
操作をするのに必要な予備知識からです。

  • Rubyの集合演算:
    Rubyは、配列を集合として演算する機能を持っています。
    "|", "&", "-"を使って、次の演習で確認してみてください。


スーパーマーケットとフルーツショップの品揃えを次の配列のように表現しました。

supermarket = ['apple', 'banana', 'cherry']
fruits_shop = ['apple', 'cherry', 'durian', 'elder berry', 'fig']

問い1:いま、買い求めることの出来る果物の種類は?
問い2:フルーツショップにしか買い求めることが出来ない果物は?
問い3:スーパーマーケットにしか買い求めることが出来ない果物はあるのか?
問い4:どちらの店でも買い求めることが出来る果物は?


問い1の答え:supermarket | fruits_shop
問い2の答え:fruits_shop - supermarket
問い3の答え:supermarket - fruits_shop
問い4の答え:supermarket & fruit_shop

解説:問い1では、2つの購入先で重複する要素'apple','cherry'が演算後に
重複せず結果が出ていることを確認してください。"|"を"+"に変えて演算したらどうなるか確認してみるのも良いでしょう。問い2は、普通に理解できると思いますが、問い3は、少し趣きが違ってきます。supermarketからfruits_shopの差集合を取っていますが、fruits_shopの多くの要素がsupermarketとの差集合に何の影響も与えていません。
実は、問い2のsupermarketの'banana'も同じなのですが、このことには気が付き難かったのではないでしょうか。問い4では、共通要素を持つ積集合を求めています。

ベン図 参考

この程度のこと、屁でもないと大半の方が思われるはずですが、この投稿では、この知識を基に進めていきます。侮ることなかれです。

  • public, private, protectedに関するメソッドを列挙するメソッドの調査
    オブジェクトに送ることの出来るメッセージを取得するメソッドがいくつか用意されています。
      Object.methods.select { |ele| /methods/ =~ ele }
    を使えば、methodsを含むメソッドが取得できます。methodsは、よく知られたメソッドです。事実、こうして使っています。これらのメソッド使うことで、夫々の応答メッセージを取得できます。ただ表示される内容が、びろ~んと沢山でてきて何が何やら。。。

正直どのメソッドをどう使ったらいいのか、影響範囲も微妙に違いがあり、難しいです。まず、引数のinherited_tooを理解するといいです。デフォルトのtrueは、遡れるだけ遡るように探索します。falseは、レシーバのオブジェクトだけしか、探索しません
試行錯誤の結果、public_instance_methods, private_instance_methods, protected_instance_methodsの3つが今回のゴールに有効なようです。

  • まずは、適応対象のモデルを作っておきましょう。:
    対象のモデルは、親クラスParentを持つMyClassです。MyClassは、モジュールMyModuleを内部にインクルードしています。また、MyClassは、シングルトン・メソッドを持っています。夫々のクラス/モジュールは、全てprivate, protected, publicの属性のメソッドを持っています。
    一般的なモデルだと思いますが、ご自身で思いつくモデルを作って試すのも良いかと思います。モデルのコード次のようになります。

class Parent
  private;   def private_parent;   end
  protected; def protected_parent; end
  public;    def public_parent;    end
end

module MyModule
  private;   def private_my_module;   end
  protected; def protected_my_module; end
  public;    def public_my_module;    end
end

class MyClass < Parent
  include MyModule

  private;   def private_my_class;   end
  protected; def protected_my_class; end
  public;    def public_my_class;    end
end

class << MyClass
  private;   def private_singleton_my_class;    end
  protected; def protected_singleton_my_class;  end
  public;    def public_singleton_my_class;     end
end

obj = MyClass.new

このMyClassのインスタンス・オブジェクトに次の夫々のメソッドを適用すれば、求める対象のメソッドのリストが得られます。
モデルには、各属性に1つずつのメッセージしかないですが、複数あればその分抽出されます。

print "\nインスタンスobjのpublic/private/protected各々のメッセージ\n\n"
p obj.class.public_instance_methods(false)
p obj.class.private_instance_methods(false)
p obj.class.protected_instance_methods(false)

print "\nインスタンスobjからParentクラスまでのpublic/private/protected各々の応答できるメッセージ\n\n"
p obj.class.public_instance_methods(true) - Object.public_instance_methods(true)
p obj.class.private_instance_methods(true) - Object.private_instance_methods(true)
p obj.class.protected_instance_methods(true) - Object.protected_instance_methods(true)

print "\n#{obj.class}クラスのpublic/private/protected各々のシングルトン・メソッド\n\n"
p obj.class.public_methods(false) - Object.public_methods(false)
p obj.class.private_methods(false) - Object.private_methods(false)
p obj.class.protected_methods(false) - Object.protected_methods(false)

実行結果:

インスタンスobjのpublic/private/protected各々のメッセージ

[:public_my_class]
[:private_my_class]
[:protected_my_class]

インスタンスobjからParentクラスまでのpublic/private/protected各々の応答できるメッセージ

[:public_my_class, :public_my_module, :public_parent]
[:private_my_class, :private_my_module, :private_parent]
[:protected_my_class, :protected_my_module, :protected_parent]

MyClassクラスのpublic/private/protected各々のシングルトン・メソッド

[:public_singleton_my_class]
[:private_singleton_my_class]
[:protected_singleton_my_class]

インスタンス・オブジェクトobjをobj.classとして、クラス名を呼び出すのがキモになります。

もう、ほとんど説明の必要がないでしょうが、
obj.class.public_instance_methods(true) -
     Object.public_instance_methods(true)
と式の中で、引数trueを指定すると、上流のメッセージの全てがリストアップされます。obj.classとObjectクラスで差集合を取るのは、Objectクラスの上流にも親がいるので、応答するメッセージが存在するからです。
classで宣言したクラスは、Objectクラスの直接の配下になりますので、Objectクラスを起点に差集合を取れば、親クラスからインスタンスまでの
応答メッセージが入手できることになります。

ここまでの情報を基に、この後ツールの作成に移ります。
随分と小奇麗にまとめていますが、各種のmethodsたちをいじり倒して、試行錯誤を繰り返したり、Rubyのリファレンスマニュアルとの格闘でした。
一度、ご自身でも格闘されることをお勧め致します。脇道に逸れて、冒険するための仕掛けは作っておいたつもりです。是非、お試しあれ。

【続】ポンコツ・キャンプに続きますので、是非読んでくださいね。


この記事が気に入ったらサポートをしてみませんか?