そんなあなたに。
ruby 1.9.2
rails 3.0.5
で確認しました。
ここを参考にしました。
1
2
3
4
5
6
7
| before_filter :display_list, :only => ['top'] do |controller|
controller.display_list('index')
end
def display_list(list)
p list #=> 'index'
end
|
ちなみにこれでも動きました。でもなんかこの書き方怖い。
1
| before_filter "display_list('index')".to_sym
|
前に書いたオープンクラスでは、クラスを再オープンしてメソッドを追加した場合に。
同じクラスから生成されたオブジェクト全てにメソッドが追加されました。
もう一回やってみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class Hoge
def aaa
p 'aaa'
end
end
hoge1 = Hoge.new
hoge2 = Hoge.new
hoge1.methods.grep(/aaa|bbb/) # => [:aaa]
hoge2.methods.grep(/aaa|bbb/) # => [:aaa]
class Hoge
def bbb
p 'bbb'
end
end
hoge1.methods.grep(/aaa|bbb/) # => [:aaa, :bbb]
hoge2.methods.grep(/aaa|bbb/) # => [:aaa, :bbb]
|
生成されたオブジェクトがメソッドを呼ぶとき。
オブジェクトはクラスにメソッドを探しにいき、クラスに定義したメソッドがあった場合にそれを実行します。
つまりクラスのメソッドに対する変更はそのクラスから生成された全オブジェクトに影響があるということです。
でも実際には特定のオブジェクトにのみ処理を追加したい場合があります。
特異なんとかを使います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class Hoge
def aaa
p 'aaa'
end
end
hoge1 = Hoge.new
hoge2 = Hoge.new
hoge1.methods.grep(/aaa|bbb/) # => [:aaa]
hoge2.methods.grep(/aaa|bbb/) # => [:aaa]
def hoge1.bbb
p "bbb"
end
hoge1.methods.grep(/aaa|bbb/) # => [:bbb,:aaa]
hoge2.methods.grep(/aaa|bbb/) # => [:aaa]
|
hoge1のみにメソッドbbbが追加されました。
他にinstance _evalも使えます。
1
2
3
| hoge1.instance_eval { def ccc; p "ccc";end}
hoge1.methods.grep(/aaa|bbb|ccc/) # => [:bbb, :ccc, :aaa]
hoge2.methods.grep(/aaa|bbb|ccc/) # => [:aaa]
|
蛇足ですが。class _eval
1
2
3
| Hoge.class_eval { def ddd; p "ddd";end}
hoge1.methods.grep(/aaa|bbb|ccc|ddd/) # => [:bbb, :ccc, :aaa, :ddd]
hoge2.methods.grep(/aaa|bbb|ccc|ddd/) # => [:aaa, :ddd]
|
class evalはclassのメソッドなのでHoge.class evalで呼びます。
全オブジェクトに影響しています。
class _eval(classのメソッド)はクラスのコンテキスト内で評価されるので、クラスメソッドとして追加されます。
これはオープンクラスで追加したのと処理と同じような動きです。
rubyにはスコープゲートがある。
スコープゲートを越えて変数は参照できない。
スコープゲートには
class
module
def
があり、それぞれのブロックの中ではスコープが切り替わる。
1
2
3
4
5
6
7
8
9
10
| class Foo
str = "foo"
def print
str
end
end
foo = Foo.new
foo.print # => NameError: undefined local variable or method `str'が発生する
|
def printでスコープが変わるので、外側のstrが参照できない。
解決するには例えばフラットスコープ
1
2
3
4
5
6
7
8
9
10
| class Bar
str = "bar"
define_method :print do
str
end
end
bar = Bar.new
bar.print
|
スコープゲート(class/module/def)を使わなければいいという話。
今回はdefをdefine _methodに変更した
classはClass.new
moduleはModule.new
でブロック内部に中身を記述する。
一応クラスの場合の例。
1
2
3
4
5
6
7
8
| str = "bar"
Bar = Class.new do
define_method :print do
str
end
end
Bar.new.print
|
動的ディスパッチ
メソッド実行時にメソッドを動的に呼び出す。
日づけごとに天気を返すメソッドがあって、それを動的ディスパッチでメソッドを呼んでみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| class Weather
def weather_of_day(day)
send("weather_of_day_#{day}")
end
def weather_of_day_1
"快晴"
end
def weather_of_day_2
"多分雪"
end
def weather_of_day_3
"ほどよく寒い曇り空"
end
def weather_of_day_4
"寒いよ。雨"
end
def weather_of_day_5
"晴れ時々曇り"
end
def method_missing(method_id,*args)
return "#{$1}日は指定できません" if method_id =~ /^weather_of_day_(.*)/
super
end
end
Weather.new.weather_of_day(1) #=> "快晴"
Weather.new.weather_of_day(3) #=> "ほどよく寒い曇り空"
|
動的メソッド
動的にメソッドを作成する
instance variable getでインスタンス変数の値は簡単にとれますが、それがない体で。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class Me
def initialize
@first_name = "gogo"
@last_name = "sakura"
end
def self.generate_name_accessor(prefix)
prefix = prefix.to_s
define_method "#{prefix}_name" do
prefix != "full" ? instance_eval("@#{prefix}_name") : [@first_name,@last_name].join("_")
end
end
generate_name_accessor :full
generate_name_accessor :first
generate_name_accessor :last
end
Me.new.first_name #=> "gogo"
Me.new.last_name #=> "saura"
Me.new.full_name #=> "gogo_sakura"
|
まとめ
メソッド名に一定のルールがあり、それを引数等に応じてメソッド名を決定し動的に呼ぶ場合
→動的ディスパッチ
重複している箇所がある場合にメソッドの内容が似通っているなら
→動的メソッド
辺りを使い分けてリファクタリングすると幸せになれるかも?
以下を入れれば良いっぽい
ruby-matchit - ’Matchit’ for Ruby. : vim
online
ページ下部のClick on the package to
download.からruby-matchit.vimをダウンロードして
~/.vim/plugin/
におきませう。
それだけで%で対応するdef endとか飛べちゃう。
vimで
:map
とすればキーマッピングの確認ができる
を変更したい場合は、.vimrcにこう書くと変更できる。
let mapleader = "任意のキー"