このページは、僕が Ruby を使うためのメモ書きです。特に体系立てて Ruby の解説をしようという大それたことは考えていません。あくまでも僕の私的なメモです。基本的に今の僕は、Ruby を CGI の制作に使いたいと思っているので、CGI 方面の記述が多くなると思います。
また、できる限り記述は正確にしたいと思っていますが、僕の理解不足で、間違った記述がある可能性があります。間違いに気づかれた方は、がらくた箱サポート掲示板かメールでご連絡いただけると、すごく嬉しいです。
| 基本 | 応用 |
|---|---|
|
|
何はともあれ、しきたり(?)は守りましょう。と言うわけで、Ruby で HTML を吐き出す方法です。
Ruby で HTML を書く方法としては、CGI ライブラリを利用せずに書き出す方法と、CGI ライブラリを使って書く方法の2つの方法があります。
#! /usr/local/bin/ruby -Ke
# CGIライブラリを使わずに「Hello World」を表示する方法
print "Content-Type: text/html; charset=EUC-JP¥n¥n"
# "Content-Type: text/html¥n¥n"が最初に必ず必要
print "<HTML><HEAD><TITLE>Hello World! 1</TITLE></HEAD>¥n"
print "<BODY><P>Hello World!</P></BODY></HTML>"
# HTML的に正しくしようとすると、上記のような記述になる
Content-Type: text/html; charset=EUC-JP <HTML><HEAD><TITLE>Hello World! 1</TITLE></HEAD> <BODY><P>Hello World!</P></BODY></HTML>
CGIライブラリを使わない場合、HTML 的に正しく書こうとすると、結構面倒くさい。
#! /usr/local/bin/ruby -Ke
# CGIライブラリを使って「Hello World」を表示する方法
require "cgi"
cgi = CGI.new("html4")
cgi.out('type' => 'text/html', 'charset' => 'EUC-JP') do
cgi.html() do
cgi.head{cgi.title{"Hello World! 2"}} +
cgi.body{cgi.p{"Hello World!"}}
end
end
Content-Type: text/html; charset=EUC-JP Content-Length: 177 Content-Language: ja <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <HTML><HEAD><TITLE>Hello World! 2</TITLE></HEAD> <BODY><P>Hello World!</P></BODY></HTML>
CGIライブラリを使った場合でも、それはそれで面倒なことになりそう。HTML 的には正しく出力されるのだけど、少なくとも、出力結果を凝ったレイアウトにしようと思った場合、HTMLドキュメントを書くような手軽さはなさそう。
と言うわけで、状況が許す限り、出力には eRuby を使おうと思う。
Xrea では 2002.04.06 現在、eruby コマンドがサポートされていないので、出力に eRuby を利用しようとすると、erb ライブラリを使わなければいけない。その時の基本的な記述方法。
require 'erb/erbl' # erb Light を利用 print "Content-Type: text/html; charset=EUC-JP¥n¥n" print ERbLight::new( filename.rhtml ).result(binding)
require 'erb/erbl'
require 'cgi'
cgi = CGI.new('html4')
cgi.out do
ERbLight::new( filename.rhtml ).result(binding)
end
require 'cgi' #CGIライブラリを使用
cgi = CGI.new('html4')
str = cgi['form_name'][0] # 配列として格納
フォームの名前が予め決まっているときは、この方法で大丈夫。もし、インタフェースが決められないときは、こんな感じで書けばいい。
require 'cgi'
cgi = CGI.new('html4')
forms = cgi.params # ハッシュとして格納
いずれの場合も、cgi['form_name'] は実際には、{'form_name' => ['value1', value2'...]} と配列で取得されていること。こうなっている理由はよく分かるのだけど、個人的には使いにくいので、こんなのを作ってみる。
class Article
def initialize( cgi_params ) # cgi.params を引数にとる
@params = Hash.new
cgi_params.each { |key, value|
@params[key] = value.first
}
end
def []( *args )
@params[*args]
end
end
これで、article['form_name'] で、フォームからの値を取り出すことができる。
data = open("filename","r"){|io| io.read}
この方法だと、ファイルのデータを全部読み込んでしまうので注意が必要。
open("filename","r"){|io|
while line = io.gets
end
}
こっちの書き方の方が出番は多そう。
open("filename","w"){|file|
file.write(# 書き込むデータ)
}
open("filename","a"){|file|
file.write(# 書き込むデータ)
}
基本的な考え方としては、
こんな感じ。
File::open("tempFileName", "w") do |temp|
File::open("baseFileName") do |base|
line = base.gets
if line == key # 挿入位置の条件
temp.write( # 挿入データ )
else
temp.write(line)
end
end
end
File::rename("tempFileName", "baseFileName")
str = gets.chomp
「str = gets」だと、改行まで取り込んでしまうので、ちょっと注意が必要。
ruby -c target_script
これで文法チェックが行える。タイプミスやカッコの閉じ忘れなどのケアレスミスは、結構これで見付けられる。っていうか、ケアレスミス多すぎ > 自分
c2t.rb class_name > test_script.rb
require 'runit/testcase'
require 'runit/cui/testrunner'
require 'hogehoge' # テスト対象のスクリプト
class Testclss < RUNIT::TestCase
※ここにテストを記述する。
end
if $0 == __FILE__
if ARGV.size == 0
suite = Testclss.suite
else
suite = RUNIT::TestSuite.new
ARGV.each do |testmethod|
suite.add_test(Testclss.new(testmethod))
end
end
RUNIT::CUI::TestRunner.run(suite)
end
Rubyで作ったCGIがエラーを起こした場合に、どういうエラーが起こっているのかを確認するための方法は、下記の通り。
def error_out()
print "Content-Type:text/html¥n¥n"
$@.each {|x| print CGI.escapeHTML(x), "<BR>¥n"}
end
if __FILE__ == $0
begin
# Main の処理
rescue StandardError
error_out()
rescue ScriptError
error_out
end
end
eRuby を使った場合に、どういうエラーが起こっているのかを確認するための方法は、下記の通り。
erb -x target_file > check_target_file.rb ruby -wc check_target_file.rb
やっていることは、一端、普通の Ruby スクリプトに変換して、チェックするという感じ。
CGIを呼び出した時のディレクトリ位置と、実際に CGI が動いているディレクトリは異なる。当たり前のことだけど、CGI でファイルを操作する際には、ちょっと気をつけた方がいい。
特に、CGIスクリプトで動的にコンテンツを生成する場合、サイト内リンクやイメージファイルの指定関係には気をつけた方がいい。不安な場合は絶対パスで指定した方が吉かも。
こちらも当たり前と言えば当たり前だけど、URL で表示されるディレクトリのパスと、サーバ内の本当のディレクトリのパスは異なる。
CGI ライブラリを使ってフォームの値を取得する場合、フォーム名が重複していない場合でも、配列になっているので注意が必要。
フォームから取得した値に対してを処理を行う場合、cgi["form_name"][0]を使うか、cgi["form_name"].first を使うこと。String メソッドにあって、Array メソッドにないメソッドを使うときに、思わぬエラーが発生する可能性がある。
これについて、とりあえず僕の回避策は、「フォームからデータを取得する」に記載されているとおり。
Ruby での変数の扱いについては、Ruby本で以下のように書かれています。
変数とはオブジェクトを指し示すもので、オブジェクトに付ける名札のようなものです。(中略)Rubyには他の言語で時々見られるようなオブジェクト(またはデータ)そのものを格納する容器としての変数は存在しません。代入は同じオブジェクトへの参照を発生させるだけで、オブジェクトをコピーしないことに注目してください。
このことは、例えば下記のようなスクリプトの場合
a = "foo" b = a a.upcase! p a, b
この変数「a」と「b」は、「FOO」となることを意味します。これ、OKです。
ところで、次のようなスクリプトの場合、
a = "foo" b = a a = "FOO" p a, b
このとき、「a」は「FOO」なのですが、「b」は「foo」のままです。これちょっと注意です。
考え方としては、「b」は「a」を参照しているのではなく、あくまでも「foo」というオブジェクトを参照していると考えればいいのでしょうけれど、僕自身はちょっと混乱してしまいます。
ちなみに、
a = "foo" b = a b.upcase! p a,b
僕の期待としては、「a」には元の「foo」を残して、「b」のみが「FOO」となって欲しいわけですが、これだと変数「a」「b」ともに「FOO」となります。これもかなり注意です。この例だと、
a = "foo" b = a.upcase p a,b
これで、新しいオブジェクトを生成させることで、元のオブジェクトを保持できます。ただし、これがクラスのインスタンスを参照することになるとちょっと面倒そうです。
class Foo
attr_reader :num
def initialize( num )
@num = num
end
def plus( num )
@num = @num + num
end
end
a = Foo.new(1)
b = a
p a.num,b.num # a = 1, b = 1
a.plus(1)
p a.num,b.num # a = 2, b = 2
b.plus(1)
p a.num,b.num # a = 3, b = 3
配列なんかの場合は、さらに注意が必要で、
a = [0, 1, 2] b = a a[0] = nil p a, b
「a」「b」ともに、[nil, 1, 2] になります。[0, 1, 2] の状態を保持しておきたい場合は、「clone」とか「dup」とかを使う必要がありそう。
Ruby では、変数は「オブジェクトを指し示すもの」になっています。このことは、変数のスコープを考える上でも、注意しておく必要があります。例えば、次のような例の場合
class Foo
def initialize( n )
@foo = n
end
def foo
@foo
end
def sample1
@foo +=1
end
end
a = 1
b = Foo.new(a)
b.sample1
p a, b
この場合、a の値は「1」のままで、b.foo の値は「2」になります。これ、OKです。
注意しなければいけないのは、
class Foo
def initialize( str )
@foo = str
end
def foo()
@foo
end
def sample2
@foo.upcase!
end
end
a = "abc"
b = Foo.new(a)
b.sample2
p a, b
この場合、b.fooの値は「ABC」で、aの値も「ABC」になります。
これも、Ruby における変数は、あくまでもオブジェクトを参照する名札だと理解していれば、「a」が参照していた「abc」という文字列オブジェクトそのものを「b」のメソッドで変更してしまった納得できるのですが、何かの値を変数という箱の中に入れているというメタファで理解していると、ちょっと混乱が起きそうです。
「a」の値はそのままで、「b.foo」の値のみを「ABCにしたい場合は、こんな感じに書けばいいのかもしれない。
class Foo
def initialize( str )
@foo = str
end
def foo()
@foo
end
def sample2
@foo = @foo.upcase
end
end
a = "abc"
b = Foo.new(a)
b.sample2
p a, b
# USAGE : tab2space.rb [width] file... space = " " * ARGV.shift.to_i ARGF.each_line do |line| puts line.gsub(/¥t/, space) end
エラー処理は全くないけど、とりあえず個人用としてはこれで。
コマンドラインの引数で指定されたファイルを処理するのは、ARGF.each を使えばいい。このとき、ファイル以外の引数を必要とするときは、ARGF の前に ARGV を使って引数を取得しておく必要があるみたい。例えば、
ARGF.each_line do |line| puts line.gsub(/¥t/, " " * ARGV.shift.to_i) end
こういうコードだと、スペースの数を指定したつもりが、ファイルだと判断されてエラーになるみたい。
Ruby で正規表現を使ったパターンマッチの書き方は、基本下記の通り。
/正規化された文字列/ =‾ 対象文字列
ただし、下記の書き方も認められている。
対象文字列 =‾ /正規化された文字列/
| * | 直前の表現の0回以上の繰り返し。 |
|---|---|
| *? | 直前の表現の0回以上の繰り返し。非欲張り型。 |
| + | 直前の表現の1回以上の繰り返し。 |
| +? | 直前の表現の1回以上の繰り返し。非欲張り型。 |
| {min, max} | 直前の表現の min から max 以下の繰り返し。 |
| {min, max}? | 直前の表現の min から max 以下の繰り返し。非欲張り型。 |
| ? | 直前の表現の0回または1回以上の繰り返し。 |
| ?? | 直前の表現の0回または1回以上の繰り返し。非欲張り型。 |
欲張り型は、できる限り長くマッチしようとする。他方、非欲張り型は、最低限のマッチでよしとする。って言われても、どんな振る舞いになるか、よくわからん。こんな感じになるみたい。
str = "ababc" puts str.gsub(/(ab)+/, "A") # (1) 欲張り型 puts str.gsub(/(ab)+?/, "A") # (2) 非欲張り型
(1)の場合、結果は「Ac」になるのに対して、(2)の場合、「AAc」になる。
と言うことは、欲張り型の場合、最初の「ab」で止まらずに次の「ab」までが1つの範囲になるのに対して、非欲張り型の場合、最初の「ab」でひとまず OK にして、改めて次の「ab」を探しているということ?
正規表現を使うパターンの1つに、ファイルの中から特定の文字列を探す場合、下記のようなスクリプトを書きがち。
File::each_line(file_name) do |line| puts line if line =‾ /a*/ end
でも、これよりも、
keyword = /a*/ File::each_line(file_name) do |line| puts line if keyword =‾ line end
こっちの方が効率がいいみたい。原因は、前者の例が、1行読み込むごとに Regexp クラスを呼び出すのに対して、後者は最初の1回のみだから。対象になるファイルのサイズが大きければ大きいほど、目に見えるほど実行速度に差が出る。
validMail = Regexp.new("^([a-zA-Z0-9_¥.-]+)@(([a-zA-Z0-9_-]+¥.)[a-zA-Z0-9]+$)")
これだけでは、該当メールアカウントが存在するかどうか正確にはわからないけれど、気休め程度にはなるかも。
validURI = Regexp.new("http[s]{0,1}://[¥(¥)%#!/0-9a-zA-Z_$@.&+-,¥'¥"*=;?:‾-]+")
こちらも、必ずしも正確なURIに対応しているわけではないけれど、やっぱり気休め程度にはなるかも。
オブジェクト指向では、振る舞いと状態によって世界をモデル化する。
個人的には、メソッドの役割は、
これに尽きるのかなと思っていますが、どうなんでしょうね?
array.index( array.find{ |item| item == keyword } )
class Array
def every(&block)
arity = block.arity
return self.each(&block) if arity <= 0
i = 0
while i < self.size
yield(*self[i, arity])
i += arity
end
self
end
end
ruby -r exerb/mkexr <targetfile.rb> exerb <targetfile.exr>