30 Jun 2010

Rubyシングルトンパターンいろいろ + Railsでの使用例

Rubyでのシングルトンの定義はいろいろあります。カウントアップを実現する例をいくつかの方法で書いてみました。


この記事を書くにあたって以下の記事を参考にしました。
Singleton Classes in Ruby - RUBY EYE FOR JAVA GUY
ここで書かれているRubyのシングルトンクラスを定義するためのいくつかの前提知識は割愛しました。理解が甘いなと感じた場合は読んでみてください。


シングルトンいろいろ



シングルトンメソッドを利用する方法



クラス変数/メソッドを利用する方法




class MyCount
@@count = 0;

def self.up
@@count += 1
end

end

puts MyCount.up #=> 1
puts MyCount.up #=> 2

シンプルにクラスに対してシングルトンメソッドを定義しています。別の言い方をするとClassクラスのインスタンスであるMyCountにシングルトンメソッドを定義しています。


クラスへの仕様追加は以下の方法でもできます。ただし、def MyCount.upからMyCountクラスのクラス変数を参照することができないのでMyCount.up内で定義しています。

class MyCount; end

def MyCount.up
@@count ||= 0
@@count +=1
end




シングルトンクラスを利用する方法



暗示的にシングルトンクラスを生成する




my_count = Object.new
def my_count.up
@@count ||=0
@@count += 1
end

puts my_count.up
puts my_count.up


この例と先に挙げたdef self.upの例との違いは何でしょうか?self.upはクラスにupというシングルトンメソッドを定義しています。それに対してmy_count.upの場合、upがmy_countに対して定義されたとき新しく無名クラスがそのオブジェクトとクラスの間に挿入します。その無名クラスをシングルトンクラスと呼びます。これはあるオブジェクトに対して最初にメソッドが定義された時に起こります。

my_count.upを呼ぶとき、インタープリタは定義を見つけるために最初に無名クラスを検索し、実装のためにクラスハイラーキーを探します。



- 内部クラス
上記の例で起きていることがイメージできましたでしょうか?次の例を見るとシングルトンクラスがどこにいるか分かりやすくなると思います。

こちらの例は機械猫さんのところから拝借しました。
[ruby][デザパタ]Rubyでデザパタ Singleton編 - 機械猫の日記


class Object
class MyCount
@@count=0
def up
@@count+=1
end
end

@@ins = MyCount.new

def count
@@ins
end
end

puts count.up
puts count.up

Objectクラスに内部クラスMyCountが定義されています。@@insはObjectクラスのクラス変数なのでObjectクラス中で一意になります。MyCountが無名である場合を想像してください。厳密には違う場所に定義されます。シングルトンクラスはほぼ内部クラスと似たような振る舞いをしますが、変数のスコープなどに違いがあります。



明示的にシングルトンクラスを生成する




my_count = Object.new

class << my_count #=> シングルトンクラスを定義して、my_countのインスタンスに仕様を追加
@@count = 0
def up
@@count +=1
end
end

puts my_count.up
puts my_count.up

class << my_countはmy_countとObjectの間のシングルトンクラスに明示的にアクセスできます。この場合upはシングルトンクラスに対するインスタンスメソッドとなり、my_countに対するシングルトンメソッドとなります。別の言い方をするとシングルトンクラスはシングルトンメソッドを格納するための入れ物になります。この記法はいくつかのシングルトンメソッドをまとめて定義するのに便利です。


インスタンスではなくクラス定義にシングルトンクラスを定義する場合

class MyCount
class << self
@@count = 0
def up
@@count += 1
end
end
end

puts MyCount.up
puts MyCount.up

class << selfはself=MyCountに対してシングルトンクラスを定義し、仕様を追加しています。インスタンスにシングルトンクラスを定義する先ほどの例との違いはありません。なぜならRubyでは全てのクラスがオブジェクトだからです。Railsでは明示的にシングルトンクラスをクラス定義内に追加するこの方法が好まれているらしいです。



Singletonモジュールを利用する方法



Singletonモジュールを使うとincludeしたクラス自体をシングルトンクラスにしてくれます。シングルトンメソッドで使用するインスタンス変数はinitializeに書けば参照することができます。newはprivateにされているためインスタンスは生成できません。ClassName.instanceでインスタンスを取得します。

require 'singleton'

class MyCount
include Singleton
def up
@count+=1
end
def initialize
@count=0
end
end

a,b = MyCount.instance, MyCount.instance
puts a == b # => true
# a.new # NoMethodError - new is private …


Singletonモジュールが何をしているのかはRuby で Singleton - うっかりプログラミング日誌Singleton - apidock.comを参考にされるとよく理解が進むと思います。



シングルトンクラスを定義したクラスを継承した場合




class MyCount
class << self
@@count = 0
def up
@@count += 1
end
end
end

MyCount.up #=> 1

class Guests < MyCount; end

Guests.up #=> 2

継承したクラスGuestsのupはMyCountで定義されたシングルトンクラスのupを参照するみたいです。シングルトンクラスはそのまま子クラスに渡されるんですね。




Railsでのシングルトンパターンの例


こちらを参考にしました。
Ruby Singleton Pattern Again - Dalibor Nasevic


シングルトンパターンのRubyでの良い例の一つは、RailsでのInflections クラスの実装でしょう。Railsの異なる場所で使用されている全ての屈折規則(inflection rule - 単語の変換を定義する規則)にグローバルアクセスを与えているのはシングルトンインスタンス(Inflections.instance)です。

まずは呼び出し元のinflectルールの定義から見ていきましょう。

$RAILS_ROOT/config/initializers/inflections.rb

ActiveSupport::Inflector.inflections do |inflect|
inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
end

inflectionsというメソッドを屈折規則を定義したブロック付きで呼び出しています。


以下が、簡略化したActiveSupport::Inflectorの定義です。

module ActiveSupport
module Inflector
class Inflections
def self.instance
@__instance__ ||= new
end

attr_reader :plurals, :singulars, :uncountables, :humans

def initialize
@plurals, @singulars, @uncountables, @humans = [], [], [], []
end

def plural(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
@plurals.insert(0, [rule, replacement])
end

def singular(rule, replacement); end
def irregular(singular, plural); end
def uncountable(*words); end
def human(rule, replacement); end
def clear(scope = :all); end
end

# Yields a singleton instance of Inflector::Inflections
# so you can specify additional inflector rules.
def inflections
if block_given?
yield Inflections.instance
else
Inflections.instance
end
end

def pluralize(word)
result = word.to_s.dup

if word.empty? || inflections.uncountables.include?(result.downcase)
result
else
inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
result
end
end

def singularize(word); end
def humanize(lower_case_and_underscored_word); end
def titleize(word); end
def tableize(class_name); end
def classify(table_name); end
end
end

ActiveSupport::Inflector.inflectionsは29行目のinflectionsメソッドを呼び出します。ブロックが定義されている場合は、シングルトンインスタンスであるInflections.instanceを引数にブロックを実行します。先ほどのブロック内をもう一度見てください。inflectを引数に取っています。Inflections.instanceが代入され、inflect.plural /^(ox)$/i, '\1en'が実行されます。

inflectはInflector::Inflectionsのインスタンスです。Inflector::Inflectionsはインスタンス変数に屈折規則を格納した配列を保持しています。14行目のpluralメソッドを参照してみましょう。Inflector::Inflectionsのインスタンス変数@pluralsに新しい屈折規則が追加されます。

実際の変換はActiveSupport::Inflector.pluralizeで行います。43行目の行頭ではinflectionsを引数なしで呼び出しており、33行目のとおりInflector.instanceを返します。gsub!はマッチが無い場合nilを返します。マッチする規則が発見された場合は結果を返しbreakしています。

Inflector.inflectionsメソッドは"単/複数規則を定義するとき"、"Inflector::Inflectionsのインスタンスを取得したいとき"の二通りの使われ方をしています。どこからも単/複数形の規則を追加できる、その定義が全ての呼び出しに反映されるというのがシングルトンを使う利点です。こんな風に適所、シングルトンパターンを使うタイミングを思いつくでしょうか?この”どこからも”というのがシングルトンパターンを使うべき時の"匂い"になるでしょう。


.

25 Jun 2010

Ruby 正規表現入門ドリルの練習問題の解答

前回の練習問題の解答です。

1.
d, m_s, y = m.values_at 1,2,3

2.
>> dirs_files.collect{|v| v unless /(^\.\.)|(^\.)/.match(v) }.compact
=> ["app", "Capfile", "config", "coverage", "db", "doc", "features", "lib", "log", "public", "Rakefile", "README", "rerun.txt", "script", "spec", "tmp", "vendor"]

3.
puts File.read("test.html").match(/<title>/).post_match.match(/<\/title>/).pre_match
puts File.read("test.html").match(/<title>(.+)<\/title>/m)[1]

Ruby 正規表現入門ドリル

Rubyの正規表現の入門編です。これを読めばリファレンスにあまり当たらなくても思い描いている正規表現を大体書けるようにというのと、他の人が書いた正規表現を読めるようになることを目的に書きました。そのため正規表現の論理構造とあまり関係ないものは割愛しました。Rubyのバージョンは得に断りがない限り1.8.7を使用しています。1.9から鬼車が採用されており、グループの扱いに機能拡張があります。そちらについても最後の方で触れています。習うより慣れろということで最後に練習問題を用意しています。

目次




基本


Regexpインスタンスの生成
>> Regexp.new("a")

//はRegexpのインスタンス。
>> /Suzuki Milan Paak/.class
=> Regexp

%記法もRegexpのインスタンス
>> %r|a| == /a/
=> true


- マッチした位置を見つける
Stringの=~もRegexpの=~も返す結果は同じ
>> /i/ =~ "aeiou"
=> 2
>> "aeiou" =~ /i/
=> 2

位置は0からカウントされる
>> "aeiou" =~ /a/
=> 0

一致しない場合はnil
>> "aeiou" =~ /s/
=> nil

$&には最初にマッチした文字列が代入される。$&はマッチが行われる度に上書きされる
>> "aei aeio" =~ /aei./
=> 0
>> $&
=> "aei "
>> "aeio" =~ /aei./
=> 0
>> $&
=> "aeio"

$` 一致した文字列の前の文字列
$& 正規表現と一致した文字列
$' 一致した文字列の後の文字列
$~ 一致情報を記憶したオブジェクト
>> "abc" =~ /b/
=> 1
>> [$`, $&, $', $~]
=> ["a", "b", "c", #<matchdata "b">]

- マッチした文字列を全て取得する
>> /(ba)/.match("foobarbaz").to_a
=> ["ba", "ba"] #=> barとbazのbaが帰っている
>> "foobarbaz".match(/(ba)/).to_a
=> ["ba", "ba"]


マッチがあるかどうかをbooleanで返す
>> /Tokyo/ === "He's from Tokyo."
=> true


リテラル



iを指定すると大文字/小文字を無視する
>> "Ruby" =~ /by/i
=> 2

xを指定すると、正規表現の空白と改行を無視する
>> "foobarbaz" =~ /bar
baz/
=> nil
>> "foobarbaz" =~ /bar
baz/x
=> 3

検索対象の文字列の改行は関係ない。
>> "foobar
baz" =~ /barbaz/
=> nil
>> "foobar
baz" =~ /barbaz/x
=> nil


mを指定すると複数行モードになる。 .が改行を含む任意の一文字と一致する。
>> "foobar
baz" =~ /bar.baz/m
=> 3

| 選択。OR
>> /^--help$|^-h$/ =~ "--help"
=> 0
>> /^--help$|^-h$/ =~ "-help"
=> nil
>> /^--help$|^-h$/ =~ "-h"
=> 0
>> /^--help$|^-h$/ =~ "--h"
=> nil


正規表現記号(メタ文字)



^が行頭
$が行末
>> ["ruby", "by"].collect{|v| v =~ /^by$/}
=> [nil, 0]

. 任意の一文字
>> "abc".scan /.c/
=> ["bc"]


量指定子(quantifiers)



? 直前の表現の0 または 1 回の繰り返し
>> /ユーザー?/ =~ "ユーザログイン"
=> 0

* 直前の表現の0回以上の繰り返し。
>> "aeiou".scan /o*u/
=> ["ou"]
>> "aeiou".scan /i*u/ #=> iの0回の繰り返し + "u" にマッチ
=> ["u"]


+ 直前の表現の1回以上の繰り返し
>> "aeiu".scan /o*u/
=> ["u"]
>> "aeiu".scan /o+u/
=> []


{m}, {m,}, {m, n}
それぞれ直前の正規表現の{m 回}、{m 回以上}、{m 回以上、最大 n 回}
str = "foofoofoo"
p str[/(foo){1}/] # => "foo"
p str[/(foo){2,}/] # => "foofoofoo"
p str[/(foo){1,2}/] # => "foofoo"


quantifire?(??, *?, +?, {m}?, {m,}?, {m, n}?)
quantifireだけの指定はできるだけ長くマッチしようとする。quantifire?はできるだけ短くマッチしようとする。

p str[/(foo){2,}?/] # => "foofoo"

orig_str = "<b>Ruby</b> and <b>Perl</b>"
p orig_str.gsub(/<b>(.*)<\/b>/, '<em>\1</em>')
# => "<em>Ruby</b> and <b>Perl</em>"
p orig_str.gsub(/<b>(.*?)<\/b>/, '<em>\1</em>')
# => "<em>Ruby</em> and <em>Perl</em>"



その他のバックスラッシュ付きのメタ文字 "\x" については割愛します。Rubyリファレンスの正規表現記号のセクションをご参照下さい。


グループ


グループは何文字かの正規表現をまとめる時に使用します。量指定子や選択と一緒に使用します。

u + eの一回以上の繰り返しにマッチ
>> "queue".scan /ue+/
=> ["ue", "ue"]

ueの一回以上の繰り返しにマッチ
>> "queue".scan /(ue)+/
=> [["ue"]]

>> /(ue)+/ =~ "queue"
=> 1
>> $&
=> "ueue"

aまたはbc
>> /a|bc/ =~ "abc"
=> 0
>> $&
=> "a"

acまたはbc
>> /(a|b)c/ =~ "abc"
=> 1
>> $&
=> "bc"


後方参照



グループ化の()にはもう一つ役割があります。それはそのグループが一致した文字列を記憶しておくことです。記憶した文字列はそれ以降の正規表現から参照可能です。これを後方参照と呼びます。後方参照は\nで指定します。nは正規表現中のn番目の()とのマッチの結果を意味します

>> "abc abcd " =~ /(\w+)\s+\1d/
=> 0
>> $&
=> "abc abcd"

(\w+)は英数字が0回以上出現することを意味します。つまり"abc"とマッチします。\1は最初の()と一致した"abc"を指します。\sは空白文字の0回以上の出現を意味します。よって全体で"abc abcd"にマッチします。この場合、/(\w+)\s+\1d/は/(\w+)\s+abcd/と等価です。

$nは\nと同じようにn番目の()とマッチした表現を参照できます。
>> "suzukimilanpaak@example.com " =~ /(\w+)@(\w+).com/
=> 0
>> p $1, $2, $3
"suzukimilanpaak"
"example"
nil

- ()の入れ子
最初に出現した(、つまり外側の()から番号が付く様です。
>> /((foo)bar)/ =~ "foobar"
=> 0
>> p $1, $2
"foobar"
"foo"


文字クラス



[]は[]中のどれか一文字とマッチします。
>> /[abc]/ =~ "b"
=> 0

[]内の^は先頭にある場合、後続の文字以外のもとマッチし、それ以外の場合文字列"^"とマッチします。
p /[^bc]/ =~ "bca" #<= 2 "^"とマッチ >> /[b^c]/ =~ "a^bc"
=> 1
>> $&
=> "^"

>> /[^]/ = "^"
# => invalid regular expression; empty character class: /[^]/

[]内とそうでない場合の違い
[]内の場合後続文字を否定し、そうでない場合行頭にマッチします
p /[^bc]/ =~ "bca" #<= 2 p /^bc/ =~ "abc" #<= nil p /^bc/ =~ "bca" #<= 0 []内の-は文字列として評価されます。 >> /[-bc]/ =~ "a-bc"
=> 1
>> $&
=> "-"



バックトラック



以下の正規表現はマッチします。
>> /(go*)ogle/ === 'google'
=> true
>> $1
=> "go"

これは正規表現のエンジンが以下の手順を踏んでいるからです。

1,
- o* が o 2つにマッチする
- ogleの o がマッチに失敗
2,
- o* を o 1つにマッチさせる (バックトラックする)
- ogleの o が 残りのo にマッチする
- gle が 残りにマッチする

(?>)を使うとバックトラックを抑止できます。"この表現はまだ試験実装中です。将来なくなる可能性もありますので、 そのつもりで使ってください。特に汎用ライブラリなどで使ってはいけません。"と本家マニュアルにありました。
>> /(?>go*)ogle/ === 'google'
=> false


Regexpオブジェクト、MatchDataオブジェクト


>> re = Regexp.new(/(\w+)\s+\1d/)
=> /(\w+)\s+\1d/
>> m = re.match("abc abcd ")
=> #
>> m[0]
=> "abc abcd"
>> m[1]
=> "abc"
>> m[2]
=> nil


\は""中でエスケープシーケンスなので、""で囲んでRegexpのインスタンスを作成する場合二重にエスケープしなければ\wや\sなどの正規表現記号を使えない。
>> re = Regexp.new("\w")
=> w
>> re =~ "aaa"
=> nil

>> re = Regexp.new("\\w")
=> w
>> re =~ "aaa"
=> 0

>> re = Regexp.new('\w')
=> w
>> re =~ "aaa"
=> 0


間違いの元なので、私の場合最初から正規表現のインスタンスを使うことにしてる。
>> /\w+/.match "aaa "
=> #


to_aは一致した文字列を配列にして返す。values_atはその配列に対してインデクスで指定された値を返す。
>> m = /(.)(.)(\d+)(\d)/.match("THX1138: The Movie")
=> #
>>m.to_a
=> ["HX1138", "H", "X", "113", "8"]
>> m.captures
=> ["H", "X", "113", "8"]
>>m.values_at(0, 2, -2)
=> ["HX1138", "X", "113"]

to_aの結果は["正規表現全体にマッチする文字列", "グループ1に一致する文字列", "グループ2..", "グループ3..", "グループ4.."]

MatchData#capturesとString#scanはグループが指定されていた場合、それぞれのグループにマッチする文字列を返す模様
>> "THX1138: The Movie".scan /\d+/
=> ["1138"]
>> "THX1138: The Movie".scan /(.)(.)(\d+)(\d)/
=> [["H", "X", "113", "8"]]



グループ(2)



名前付きグループ


1.9では正規表現エンジンに鬼車が採用されました。そのためグループに名前を付けられるようになりました。(?<group_mane>regexp)という形式で各グループに名前を付けられます。MatchDataのインスタンスからhハッシュキーと同じようにm[:group_name]で参照できます。

ドメイン名から第二レベルドメインを取得してみましょう。
$ rvm 1.9.1
$ irb
ruby-1.9.1-p378 > m = /(?<third_level>w+).(?<second_level>w+).(?<top_level>w+)/.match "engineerfiles.blogspot.com"
=> #<MatchData "engineerfiles.blogspot.com" third_level:"engineerfiles" second_level:"blogspot" top_level:"com">
ruby-1.9.1-p378 > m[:second_level]
=> "blogspot"

\k<group_name>で後方参照できます。
ruby-1.9.1-p378 > /(?<foo>bar)\k<foo>/.match("barbarbar")
=> #<MatchData "barbar" foo:"bar">

名前を指定した場合は数値で後方参照できない。
ruby-1.9.1-p378 > /(?<foo>bar)\1/.match("barbarbar")
SyntaxError: (irb):10: numbered backref/call is not allowed. (use name): /(?<foo>bar)\1/
from /home/suzukimilanpaak/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `<main>'


グループの位置指定、先読み(lookahead)


(?=)はマッチした文字列を記憶しない(幅がない)。
ruby-1.9.1-p378 > /(foo)\s(bar)\s(?=baz)/.match "foo bar baz"
=> #

以下の例はマッチしません。
ruby-1.9.1-p378 > /(foo)\s(?=bar)\s(?=baz)/.match "foo bar baz"
=> nil
(?=)は対象の文字列からマッチングの位置を進めないからだそうです。

(?=bar)の後に(bar)を指定するとそれにマッチする文字列を(?=bar)の位置から再度検索してくれます。
ruby-1.9.1-p378 > /(foo)\s(?=bar)(bar)\s(?=baz)/.match "foo bar baz"
=> #
いまいち使いどころがわかんのだけど...どなたか良い例がありましたらぜひ教えてください。

000 を除く 3 桁の数字
re = /(?!000)\d\d\d/
p re =~ "000" # => nil
p re =~ "012" # => 0
p re =~ "123" # => 0



日本語の取扱い



文字コードの指定



- 組み込み変数で指定
Rubyのコード中に$KCODEを指定する。正規表現以外だけでなく、全てのRubyがマルチバイトコードを認識するためのエンコードを指定します。

最初の1バイトしか意味がなく、しかも大文字小文字を区別しない。そのため以下の指定は全てUTF-8を意味する。
$KCODE = "UTF-8"
$KCODE = "U"
$KCODE = "u"

選択肢は "EUC", "SJIS", "UTF8", "NONE"-ASCII(Default)

影響範囲
インタプリタの字句解析器、Regexp のエンコーディングフラグのデフォルト値、upcase、downcase、swapcase、capitalize、inspect、split、gsub、scan

- Ruby実行時のオプションで指定
$ echo puts $KCODE > /tmp/envvar.rb
$ ruby -Ks environment_variables_sample.rb
SJIS


- 正規表現オブジェクト作成時に指定
正規表現を使用するタイミングだけ文字コードを別のものに指定する場合は以下の様なリテラルで設定が可能です。
/日本語できます/s
この場合/日本語できます/という表現をShift-JISとしてマッチを探します。


Regexp#new, compile(newのSynonym)の引数で指定することもできます。
第一引数:正規表現、第二引数:ケースセンシティブ、第三引数:文字コード
>> re = Regexp.new("cat", true, "s")
=> catis
>> p re
/cat/is


エンコード指定による違い



- . の解釈の違い
以下の内容のファイルをUTF-8で作成して実行する。
$ cat /tmp/re_enc.rb
puts /つのだ.ひろ/u.match "つのだ☆ひろ" #=> つのだ☆ひろ
puts /つのだ.ひろ/n.match "つのだ☆ひろ" #=> nil
puts /つのだ...ひろ/n.match "つのだ☆ひろ" #=> つのだ☆ひろ
エンコードにNONEが指定されている場合、.は1バイトにマッチします。最後の行はUTF-8が3バイトのためマッチします。エンコードにnone以外が指定されている場合、. は全角文字の1文字にマッチします。


- ucase
以下の内容のファイルをShift-JISで作成して実行する。
$ cat /tmp/re_enc.rb
puts "スズキミランパーク".upcase
$ ruby -Ks /tmp/re_enc.rb
スズキミランパーク
スズキミランパーク
$ ruby -Kn /tmp/re_enc.rb
ベペネミランパーハ
スズキミランケーク

- trは日本語に対応していない
以下の内容のファイルをShift-JISで作成して実行する。
$ cat /tmp/re_enc.rb
puts "スズキミランパーク".tr("ミランパーク", " ミラノ パーク")
$ ruby -Kn /tmp/re_enc.rb
々〆´<<<� パー



練習問題


習うより慣れろということで、練習問題をいくつか用意しました。まだ少ないですが、ちょっとずつ足していこうと思います。


1. 以下の与えられたコードと出力結果からSOME_PROCESSに書かれているコードを作成して下さい。
if m = /(\d{2})\s(\w{3})\s(\d{4})/.match("22 Jun 2010")
SOME_PROCESS
end
puts d, m_s, y  #=> ["22", "Jun", "2010"]


2. lsの結果から カレントディレクトリ ".", 直上のディレクトリ "..", 隠し属性ファイル/ディレクトリ ".filename" を取り除く
/var/www/yourfav$ irb
>> dirs_files = (`ls -a`).collect{|v| v.gsub("\n", "")}
=> [".", "..", "app", "Capfile", "config", "coverage", "db", "doc", "features", ".git", "lib", "log", "public", "Rakefile", "README", "rerun.txt", "script", "spec", "tmp", "vendor"]

dirs_filesから".", "..", 隠し属性ファイル/ディレクトリを除いた配列を取得して下さい。

3. HTMLファイルからtitleを抜き出す。
正規表現(1) - Ruby 練習問題集 - Ruby on Rails with OIAX
"カレントディレクトリに HTML ファイル test.html があります。
このファイルから title 要素の内容を抜き出して表示する Ruby プログラムを書きなさい。
test.html は HTML 文法に沿っており、必ず 1 個の title 要素を含むと仮定して構いません。
なお、タグの名前にアルファベットの大文字と小文字が使える点、および要素の内容に改行文字が含まれる可能性がある点に注意すること。"

test.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja"> 
<head> 
<title>
正規表現(1) - Ruby 練習問題集 - Ruby on Rails with OIAX</title> 
</head> 
<body> 
</body> 
</html>   

解答

22 Jun 2010

インスタンスの状態とpublic_methods, private_methods, singleton_methods, public_instance_methods, protected_instance_methods, private_instance_methods

インスタンスの状態とpublic_methods, private_methods, singleton_methods, public_instance_methods, protected_instance_methods, private_instance_methods
インスタンスの状態とメソッド検索メソッドの関係についてまとめました。

まずはどんなメソッド検索メソッドを調べます。

# >> Object.methods.select{|m| m=~ /_methods/}
# => ["public_methods", "instance_methods", "singleton_methods", "public_instance_methods", "protected_methods", "protected_instance_methods", "private_methods", "private_instance_methods"]


こんなクラスで試します。

class MyClass
def initialize; end

def instance_method; end
def self.singleton_method; end

protected
def protected_instance_method; end
def self.protected_singleton_method; end

private
def private_instance_method; end
def self.private_singleton_method; end
end



クラスを検索


まずはクラスそのものでメソッドの検索をしてみます。methodsという配列との論理積を出しているのは、継承しているメソッドを表示させないためです。=beginで始まるコメントには上の4行の出力結果をそれぞれ示しています。

methods = ["instance_method", "singleton_method", "protected_instance_method", "protected_singleton_method", "private_instance_method", "private_singleton_method"]

p methods & MyClass.public_methods.collect{|m| m.to_s}
p methods & MyClass.protected_methods.collect{|m| m.to_s}
p methods & MyClass.private_methods.collect{|m| m.to_s}
p methods & MyClass.singleton_methods.collect{|m| m.to_s}
=begin
["instance_method", "singleton_method", "protected_singleton_method", "private_singleton_method"]
[]
[]
["singleton_method", "protected_singleton_method", "private_singleton_method"]
=end

p methods & MyClass.instance_methods.collect{|m| m.to_s}
p methods & MyClass.public_instance_methods.collect{|m| m.to_s}
p methods & MyClass.protected_instance_methods.collect{|m| m.to_s}
p methods & MyClass.private_instance_methods.collect{|m| m.to_s}
=begin
["instance_method", "protected_instance_method"]
["instance_method"]
["protected_instance_method"]
["private_instance_method"]
=end

注意すべきところはpublic_methodsにprotected_singleton_methodとprivate_singleton_methodが含まれるところです。理由はよく分かりません。それから、protected_methodsとprivate_methodsは何も表示しません。

instance_methodsはpirvate_instance_methodを表示させません。インスタンスメソッドを検索したい場合はスコープごとに分けて検索するか、すべてのスコープの結果を論理和したものから探すのが良いと思います。


インスタンスを検索

mc = MyClass.new
p methods & mc.public_methods.collect{|m| m.to_s}
p methods & mc.protected_methods.collect{|m| m.to_s}
p methods & mc.private_methods.collect{|m| m.to_s}
p methods & mc.singleton_methods.collect{|m| m.to_s}
=begin
["instance_method"]
["protected_instance_method"]
["private_instance_method"]
[]
=end


インスタンスから検索した場合、当然ながらクラスで定義したシングルトンメソッドは表示されません。代わりにインスタンスにシングルトンメソッドを定義すればsingleton_methods()で発見されます。

protected_instance_methodとprivate_instance_methodが発見されます。

インスタンスからinstance_xxxx_methodsと名のつくメソッドを実行すると全てundefined methodになります(ので表記してません)。


継承したクラスを検索


元のクラスと同じ結果

継承したインスタンスを検索


元のクラスのインスタンスと同じ結果

まとめ


- シングルトンメソッドは(当たり前だけど)定義したインスタンス(クラスはClassクラスのインスタンス)からしか参照できない。
- クラスのprotected_instance_methods(), private_instance_methods()は何も見つけないので何のためにあるか分からない。
- instance_methodsはpirvate_instance_methodを含まない

ちなみに、メソッド検索メソッドで参照できるのと、callできるかはまた別の話なのでご注意あれ。

18 Jun 2010

empty? nil? blank?とtrue false

Railsをやってると誰しもがありがたいと思うblank?。
- 他のempty?とnil?との使い分けは?
- ときどき、true, falseの条件とごっちゃになるし整理してみよう


empty? nil? blank?の違い



# blank?はActivesupportのObjectクラスのメソッド
# p "".blank? #=> undefined method `blank?' for "":String

require 'rubygems'
require 'active_support'

p ["", [], {}, nil, 0].collect{|v| v.nil?} #=>[false, false, false, true, false]

p ["", [], {}, "\n", "\t"].collect{|v| v.empty?} #=>[true, true, true, false, false]
# p 0.empty? #=> undefined method `empty?' for 0:Fixnum
# p nil.empty? #=> undefined method `empty?' for nil:NilClassp

["", [], {}, nil, 0, "\n", "\t"].collect{|v| v.blank?} #=>[true, true, true, true, false, true, true]


- nil?
 nilを判定するのにしか使えません

- empty?
 変数の初期値を判定するのに使える様です。"", [], {}はそれぞれString.new, Array.new, Hash.newと同じ値になります。Fixnum, NilClassにはnew methodはありません。

- blank?
 nilかempty?である場合を判定する場合に使えそうです。試してみた感じFixnumは常にfalseの様です。
blank?の定義って

def blank?
self.nil? or self.empty?
end
かと思ってましたが、そういうわけでもないんですね。


実際のところはこんな感じ

# File activesupport/lib/active_support/core_ext/object/blank.rb, line 12
def blank?
respond_to?(:empty?) ? empty? : !self
end
respond_to?はRubyのObjectクラスのメソッドで、引数に取ったメソッドが応答するかどうか調べてくれるらしい。(ってつまりメソッドの存在チェック。)


例えば、

[].instance_eval{ respond_to?(:blank?) } #=> false
[].blank? #=> undefined method `blank?' for []:Array

って余計に説明わかりづらくした。すいません。


話を戻すと、blank?はempty?がある場合はそれを使って判定。そうじゃない場合はRubyの標準の判定の逆を返す。(ここの仕組みが僕はよく分かっていません。例えば if instance_of_objectとしたときどこで判定されるのか見つけられない。きっとライブラリでなく構文解析をやってるところに書いてあるかな)
respond_to?(:empty?) ? empty? : !self




何がTrue, Falseと判定されるか?


さてTrue, Flseの方はと言いますと、

>> ["", [], {}, nil, 0, "\n", "\t"].collect{|v| if v then true; else false; end}
=> [true, true, true, false, true, true, true]

nilだけがfalseと判定されます。

.

17 Jun 2010

Ruby クラス変数、クラスインスタンス変数、インスタンス変数

kabakiyoさんの記事
- クラス変数とクラスインスタンス変数とインスタンス変数の違いについて

を読んで、クラスメソッドとインスタンスのメソッドを組み合わせたときクラス変数、クラスインスタンス変数、インスタンス変数が参照できるのかどうかよく理解してないことに気がついたのでサンプルを書いてみることにしました。


class MyClass
@@class_var = true
@class_instance_var = true

def initialize
@instance_var = true
end

# Class Variableを取得
def self.class_method
return @@class_var, @class_instance_var, @instance_var
end

# Instance Variableを取得
def instance_method
return @@class_var, @class_instance_var, @instance_var
end
end

# 派生クラス
class Derived < MyClass; end


p MyClass.class_method # => [true, true, nil]
p Derived.class_method # => [true, nil, nil]

p MyClass.new.instance_method # => [true, nil, true]
p Derived.new.instance_method # => [true, nil, true]


これを日本語でまとめると..

- クラスインスタンス変数
 - 定義の仕方は@name_of_variable。インスタンス変数とは定義する場所が違う(クラスの中、メソッドの外)。
 - 継承先のクラスから参照できない
 - インスタンスから参照できない

- クラス変数
 - 常に参照できる

- インスタンス変数
 - インスタンスからのみ参照できる



それでなんだか定義の記述が同じで場所が違うだけのクラスインスタンス変数とインスタンス変数の違いがなんだか分からなくなった。


class MyClass
@class_instance_var = 0

def initialize(fnum)
@class_instance_var = fnum
end

# Class Variableを取得
def self.class_method
@class_instance_var
end

# Instance Variableを取得
def instance_method
@class_instance_var
end
end

m = MyClass.new 1
p m.instance_method # => 1

d = Derived.new 2
p d.instance_method # => 2
p m.instance_method # => 1

p MyClass.class_method # => 0
p Derived.class_method # => nil


- クラスインスタンス変数
 - インスタンスから参照した場合、インスタンス変数がそのインスタンスのスコープでクラスインスタンス変数を上書く
 - クラスメソッドから参照した場合、定義したクラスからのみ参照可能


というわけで、クラスインスタンス変数は継承されるときに継承先にコピーされないんだろうと理解したけど、このあたり本当のところご存知の方いらっしゃいます?


.

継続でmy_while

shunsukさんの以下の記事を読んでwhileを継続で実装してみたくなった。
- [Ruby]Rubyの継続でフィボナッチ(継続の初歩)


def my_while(cond, &body)
cond = callcc{|$new_cond| cond}
body.call
cond = $new_cond.call cond if cond.call
end

i = 0
my_while lambda{i < 3} do
p i
i+= 1
end


condにはlambda{i < 3}が渡されます。&bodyには 呼び元の p i; i+=1がProcとして渡されます。
2行目: condの値に初期値として引数で渡されたcond = lambda{i < 3}がそのまま渡されます。
3行目: iをインクリメントしています。
4行目: condを実施してtrueが返る場合、condを引数にして2行目に行きます。4行目の引数のcondはそのまま2行目のcondに代入されるので、単に同じものを引き継いでいるだけです。と言うわけで継続を使う価値の半分は使ってなかったりします。(callccに移動する、値を再代入するのうち、再代入する機能を使う必要がない。)

しかし、継続は1.9.xでは廃止されたっぽいですね。残念。継続は大事ですね。

.

16 Jun 2010

injectを実装してみた

Emunarableのinjectを自分で実装してみた。ほんとそれだけなんだけど、こういうことを何もみないでやってみてって言われるとパッと書けないんで練習。

最初これでエラーになってました。

class Range
def my_inject(sum, &block)
self.collect{|v| sum = block(sum, v)}.last
end
end

p (1..3).inject(1){|sum, n| sum + n}
p (1..3).my_inject(1){|sum, n| sum + n}

3行目のblockでundefined methodが出ます。blockはProc型のインスタンスだからです。正しくは block.call(sum, v)。yieldでの呼び出しと時々ごっちゃになるんですよね。

- yieldでの呼び出し

class Range
def my_inject(sum)
self.collect{|v| sum = yield(sum, v)}.last
end
end

p (1..3).inject(1){|sum, n| sum + n} #=> 7
p (1..3).my_inject(1){|sum, n| sum + n} #=> 7


my_injectにyieldがない場合は単に無視され、my_injectの中が評価され返されます。


class Range
def my_inject(sum)
1
end
end

p (1..3).inject(1){|sum, n| sum + n} #=> 7
p (1..3).my_inject(1){|sum, n| sum + n} #=> 1



- 蛇足
これを書いていたときに(1..3)とするところを[1.3]としていてsum + nでエラーになってしまいました。[1..3]は配列の中にRange型のインスタンスが格納されているだけなので sum + nは 1 + 1..3となっていたんですね。

()でrangeを囲む理由は単に1..3.inject(){}と書けないからだと思います。

rubyでは読めるけど見ないで書けないと言う場面によく遭遇しますね。英語みたいですね。書くことは書くことでしか上達しないです。JavaやC#をやってたときは標準ライブラリのメソッド名が長すぎたりするのでeclipceのオートコンプリートに頼ったりAPIドキュメントを見たりしてあまり覚える必要性を感じませんでしたが、rubyの世界ではできるだけ何も見ないで直感的にコードを書ける能力がより重視されている気がします。

10 Jun 2010

ひさびさにRedmineセットアップしたらエラーに遭遇

久々にRedmineセットアップしたらちょこちょこ変更があったみたいでエラーに遭遇。メモしておきます。

conf/environment.rbにsession keyの設定をしてない


現在の安定版最新ver0.9.4のenvironment.rbに、Rails2.xのrails自動生成されるonfig.action_controller.session={}が記述されてないみたい。以下のエラーが出ていた。tailしてたら長い長いエラーメッセージに埋もれてしばらく気づかなかった。


The backend application (process 13999) did not send a valid HTTP response; instead, it sent nothing at all. It is possible that it has crashed; please check whether there are crashing bugs in this application.
*** Exception ArgumentError in application (A key is required to write a cookie containing the session data. Use config.action_controller.session = { :key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb) (process 13999):


Web屋の人の日記 || WebJourney 開発ログさんの記事を参考に解決。

config/environment.rb

Rails::Initializer.run do |config|
:
characters = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
secret = Array.new(48){characters[rand(characters.size)]}.join

config.action_controller.session = {
:session_key => "_webjourney_session",
:secret => secret
}
:
end


Phusion Passengerを使ってる場合Apacheをreloadしてspown serverに構成を再読み込みさせる必要があります。


No route matches "/index.html"


Phusion Passenger を使ってるため以下のエラーに遭遇した。devillikeaangelの日記さんの記事を参考に修正。(まんまコピーですいません)


ActionController::RoutingError (No route matches "/index.html" with {:method=>:get}):
Rendering /var/rails/redmine/public/404.html (404 Not Found)


どうもmod_rewriteを有効にしてると、index.htmlを探しに行ってしまうみたい。

以下、対処法の抜粋。

passengerで動かすので、Rewriteはいらない。redmine/public/.htaccessにある以下の3行をコメントアウトする


#RewriteRule ^$ index.html [QSA]
#RewriteRule ^([^.]+)$ $1.html [QSA]
#RewriteCond %{REQUEST_FILENAME} !-f


.

9 Jun 2010

Gemへパスが通らない!

レベル低くてすいません。Gemにパスが通らないって怒られる場合の原因と対処です。皆やらないと思うんだけど、僕はやる。

1, rubygemのインストール時にsudo してない。

$ gem install cassandra
WARNING: Installing to ~/.gem since /opt/ruby-enterprise-1.8.7/lib/ruby/gems/1.8 and /opt/ruby-enterprise-1.8.7/bin aren't both writable.

$ gem list -ld cassandra

*** LOCAL GEMS ***

cassandra (0.8.2)
Installed at: /home/suzukimilanpaak/.gem/ruby/1.8

これで、まれにrailsのrequireから参照が通らない場合があります。


- 対処
sudo する。

2, rubygems試しに動かすためのサンプルファイル名に対象のrubygemと同じファイル名を使ってる。
 例、cassandra.rb

require 'rubygems'
require 'cassandra'
include Cassandra::Constants

#=>
$ ruby cassandra.rb
./cassandra.rb:3: uninitialized constant Cassandra (NameError)
from /opt/ruby-enterprise-1.8.7/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /opt/ruby-enterprise-1.8.7/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from cassandra.rb:2


ruby コマンドがローカルのcassandra.rbを最初に見つけてしまっています。


- 対処

ファイル名を変える
cassandra.rb => sample_cassandra.rb



3, これらとは別に、rubygems内のコマンドがありパスが通っておらず実行できない場合があります。

$ spork
No command 'spork' found,
did you mean: Command 'spork' from package 'libspork-perl' (universe)
spork: command not found

$ gem list -ld spork

*** LOCAL GEMS ***

spork (0.8.3)
Installed at (0.8.3): /opt/ruby-enterprise-1.8.7/lib/ruby/gems/1.8

$ echo $PATH
/home/suzukimilanpaak/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:



- 対処
.profileか.bashrcにPATHを追加する。

.profile ログイン時に一度設定すればいいもの
.bashrc bashを起動するたびに設定する必要のあるもの


#.profileを編集してパスを追加する

# if running bash
if [ -n "$BASH_VERSION" ]; then
#この行を追加
PATH=$PATH:/opt/ruby-enterprise-1.8.7/bin:/home/suzukimilanpaak/.gem/ruby/1.8/bin:
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi

.profileを再読み込みして、sporkを実行(すると動くはず)。
$ source ~/.profile

$ spork


.