before_filterのメソッドを引数付きで呼びだしたいよね

そんなあなたに。

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のメソッド)はクラスのコンテキスト内で評価されるので、クラスメソッドとして追加されます。
これはオープンクラスで追加したのと処理と同じような動きです。


instance_evalを引数つけて実行

ruby1.9からです。

1
2
3
4
5
6
7
8
9
10
11
class Foo
  def initialize
    @v = 2
  end
end

foo = Foo.new
foo.instance_exec(2) do |bar|
  p @v * bar
end
# => 4

sqlの実行。

1
2
ActiveRecord::Base.connection.execute("drop table foo")
Model.connection..execute("drop table foo")


フラットスコープ

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"

まとめ

メソッド名に一定のルールがあり、それを引数等に応じてメソッド名を決定し動的に呼ぶ場合
→動的ディスパッチ

重複している箇所がある場合にメソッドの内容が似通っているなら
→動的メソッド

辺りを使い分けてリファクタリングすると幸せになれるかも?




vimのキーマップとleader

vimで

:map

とすればキーマッピングの確認ができる

を変更したい場合は、.vimrcにこう書くと変更できる。

let mapleader = "任意のキー"