フラットスコープ

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 = "任意のキー"

既存のメソッドをすり替える方法

すでにあるメソッドに処理を追加したい場合。
すでにあるメソッドを踏襲した新しいメソッドを定義して、メソッドの呼び出し側を新しいメソッドに向けることができますが
現実的でないですね。

ではどうやってすり替えるか。
実現手段はいくつかあると思いますが、ここではアラウンドエイリアスを使ってみます。

今回はStringのto _sメソッドを、

文字列(文字列長)

と表示するよう変えてみる。

今日のコードではaliasを使いますが、二度呼ぶとエラーとなってしまうのでirbを都度起動してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
str = "あいう"

str.to_s #=> "あいう"

class String
  alias :old_to_s :to_s

  def to_s
    old_to_s + "(" + length.to_s + ")"
  end
end

str.to_s #=> "あいう(3)"
str.old_to_s #=> "あいう"

この前のrubyのオープンクラス - なんとなく日々徒然と使ってStringクラスにコーディングしていきます。

構文は
alias 新しいメソッド名 古いメソッド名
です。
今回の例ではto sにold to _sという別名をつけます。

1
2
3
4
5
6
7
8
9
10
str = "あいう"

str.to_s #=> "あいう"

class String
  alias :old_to_s :to_s
end

str.to_s #=> "あいう" 
str.old_to_s #=> "あいう"

それでto _sを書き換えちゃいます。

1
2
3
  def to_s
    old_to_s + "(" + length.to_s + ")"
  end

to sが呼ばれた場合は書き換えたメソッドが呼ばれ、old to sが呼ばれた場合は昔のメソッドが呼ばれる状態です。
書き換えられたto
sは昔のto sをold to _sで呼び、lengthをそこにくっつけています。

これでメソッドのすり替えが完了です。


gitで今やってる作業を一時退避する方法

今日は肉の日。

肉関係ないけど.

機能追加中に、バグ報告がはいった場合。
今やってる修正を置いといて、最新を取得しなおしてバグフィックスしたい場合がある。

そんな場合

1
git stash

ってやると、今やっている作業内容(ステージングファイルもアンステージングファイルも)を一時退避できる。

戻した場合は

1
git stash pop

で、戻せる。

今保存しているstash一覧は

1
git stash list

コンフリクトしたりして、stashがどうにもならなくなってstashを消し去りたい場合には

1
git stash clear

ってな感じでどうですか。

これ知るまでは

1
2
git commit -m "一時退避"
git reset --soft HEAD^

ってやっていたマヌケは私です。

ああ、とりあえず肉食いたい。


悲しいお話

昨日降りしきる雨の中灯油をもったまま玄関前でよろけた拍子に水鉢を踏み割ってしまい。

地面に放り出された冬眠中の金魚とメダカを救出して、45センチのグッピー水槽に移したら
ノロノロでかわせずにグッピー♀にいじめられて冬眠からさめたばかりの金魚が死んでしまってとても悲しいのです。


rubyのオープンクラス

rubyのオープンクラス

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Hoge
  def aaa
    p "aaa"
  end
end


hoge = Hoge.new

hoge.methods.grep(/aaa/) # => [:aaa]


class Hoge
  def bbb
    p "bbb"
  end
end

hoge.methods.grep(/aaa|bbb/) # => [:aaa, :bbb]

一度定義済みのクラスHogeに再度Hogeを定義している。
Hogeが定義されていると、二回目の呼び出しでは一回目の呼び出しのHogeが呼ばれ
そこにメソッドbbbが追加される。

当然String等の既存クラスにも対応。

1
2
3
4
5
6
7
8
9
class String
  def aaa
    p "aaa"
  end
end

str = "fuga"
str.methods.grep(/aaa/) # => [:aaa]
p str.aaa # => "aaa"

構造体を使う

rubyで構造体を使いたい時
OpenStructが使える。
属性は動的に作ってくれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'ostruct'
user1    = OpenStruct.new
user2   = OpenStruct.new

user1.id = 1
user1.name = 'hoge'

user2.id = 2
user2.name = 'fuga'

p user1.id #=> 1
p user1.name #=> 'hoge'

p user2.id #=> 2
p user2.name #=> 'fuga'

#Arrayにラップしちゃえば、options_from_collection_for_selectにも渡せる。
require 'action_view'
require 'active_support/all'

include ActionView::Helpers::FormOptionsHelper

users = [user1,user2]
options_from_collection_for_select(users , "id" ,"name") #=> "<option value=  "1  ">hoge</option>  n<option value=  "2  ">fuga</option>" 

すばらしい