Rubyのmethod_missingを使って、呼び出したメソッドが存在しないときに一番似ているメソッド名を提示するコードを書きました。
少しだけ用語解説
method_missing(method, *args)
呼び出したメソッドが存在しない場合に、代わりに呼ばれるメソッドです。
呼び出したメソッド名、そのときのメソッド引数を全て受け取ることができます。
ActiveRecordのfind_by_[column]はこれを使って実装されてるみたいです。
詳しい説明は下記のページが分かりやすかったです。
Rubyのmethod_missingを使って黒魔術を実装する
似ているメソッド名を提案するコード
短いので全部書きます。
# -*- coding: utf-8 -*-
class MethodSuggestion < Array
# 存在しないメソッドを呼び出した場合、一番名前の近いメソッドを見つける
# 雑なので真似しては駄目な書き方です…。
def method_missing(method, *args)
distance_hash = Hash.new
self.methods.each { |m|
distance_hash[m] = ld(method, m)
}
near_methods = distance_hash.sort_by { |key, value| value }
print(method.to_s, near_methods[0][0])
end
# 似ているメソッド名を提示する
def print(call_method, similar_method)
puts(%Q[ruby: '#{call_method}' is not a valid method.])
puts ''
puts 'Did you mean this?'
puts " #{similar_method}"
end
# 編集距離(レーベンシュタイン距離)を求める
# 下記のURLのコードそのまま
#
# ruby でレーベンシュタイン距離(編集距離)の計算
# http://d.hatena.ne.jp/kenkitii/20090204/ruby_levenshtein_distance
def levenshtein_distance(str1, str2)
col, row = str1.size + 1, str2.size + 1
d = row.times.inject([]) { |a, i| a << [0] * col }
col.times { |i| d[0][i] = i }
row.times { |i| d[i][0] = i }
str1.size.times do |i1|
str2.size.times do |i2|
cost = str1[i1] == str2[i2] ? 0 : 1
x, y = i1 + 1, i2 + 1
d[y][x] = [d[y][x-1]+1, d[y-1][x]+1, d[y-1][x-1]+cost].min
end
end
d[str2.size][str1.size]
end
def ld(str1, str2)
levenshtein_distance(str1, str2)
end
end
# ここから実行開始
app = MethodSuggestion.new
puts app.includd?('aaa')
実行結果は下記の通り。
ruby: 'includd?' is not a valid method.
Did you mean this?
include?
最近テストも練習中なので、簡単なテストも書いてみました。
# -*- coding: utf-8 -*-
require "rspec"
require_relative "../lib/method_suggestion.rb"
describe MethodSuggestion do
before(:each) do
@ms = MethodSuggestion.new
end
after(:each) do
@ms = nil
end
describe "編集距離を計算するメソッド" do
it "同じ文字列の場合は0が返ること" do
@ms.ld('aaa', 'aaa') == 0
end
it "一文字違う文字列の場合は1が返ること" do
@ms.ld('aaa', 'ada') == 1
end
it "Wikipediaと同じサンプル文字列の場合は3が返ること" do
@ms.ld('kitten', 'sitting') == 3
end
end
end
まとめ
Rubyってほんとお手軽ですね。ちょっとしたコードを書くだけならとても便利です。
