忘れたときに備えた記録

トップ 最新 追記
2005|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|11|12|
2009|01|02|03|04|05|06|10|12|
2010|06|07|08|12|
2011|07|09|
2012|09|11|
2013|02|03|09|
2015|10|11|
2016|01|08|11|
2017|02|08|10|
2018|11|

2007-09-11(Tuesday)

WEBrick の簡単なサンプル

#!/usr/bin/ruby
require "webrick"

s = WEBrick::HTTPServer.new(:Port=>10080, :BindAddress=>"127.0.0.1")

s.mount_proc("/") do |req, res|
   res.content_type="text/html"
   res.body = <<EOT
<html>
<head><title>Test</title></head>
<body>Hello, WRBrick</body>
</html>
EOT
   s.shutdown
end

trap(:INT) do
   s.shutdown
end

s.start

こんなスクリプトを実行しておいて、同じホストで

hiraku:~$ telnet localhost 10080
(略)
GET / HTTP/1.1      <- ここを入力
                    <- 改行だけの行で入力の終了
HTTP/1.1 200 OK     <- 以降、サーバからのレスポンス
Connection: Keep-Alive
Date: Tue, 11 Sep 2007 12:04:50 GMT
Content-Type: text/html
Server: WEBrick/1.3.1 (Ruby/1.8.5/2006-08-25)
Content-Length: 76

<html>
<head><title>Test</title></head>
<body>Hello, WRBrick</body>
</html>
Connection closed by foreign host.

こんな感じで使える。

Webサーバからデータを取得する処理のテストをするときに、ローカルで臨時のサーバを用意するのに使えそう。

Tags: Ruby

open-uri でデータを取得できないURL

ホスト名に'_'を含むURLをopen-uriで開こうとすると、エラーになった。

hiraku:~/t2marks$ ruby -ropen-uri -e 'open("http://test_example.org/")'
/usr/lib/ruby/1.8/uri/generic.rb:194:in `initialize': the scheme http does not accept registry part: test_example.org (or bad hostname?) (URI::InvalidURIError)
        from /usr/lib/ruby/1.8/uri/http.rb:46:in `initialize'
        from /usr/lib/ruby/1.8/uri/common.rb:484:in `new'
        from /usr/lib/ruby/1.8/uri/common.rb:484:in `parse'
        from /usr/lib/ruby/1.8/open-uri.rb:29:in `open'
        from -e:1

どうも、ホスト名に'_'が含まれているのはRFC違反らしい。とはいえ、実際にそういうホストがあるので、なんとかそこからデータをダウンロードできるようにしたい。

それで、代替のメソッドを作ってみた(もしかしたら、最新版では動くようになってるかもしれないけど、ちょっと興味もあったので自作してみた)。

  module Misc
     RE_SCHEME = /(https?)/
     RE_USER = /(.*?)/
     RE_PASS = /:(.*?)/
     RE_USER_PASS = /#{RE_USER}#{RE_PASS}?@/o
     RE_HOST = /(.*?)/
     RE_PORT = /:(\d+)/
     RE_PATH = /(\/.*)$/
     RE_URL = %r[#{RE_SCHEME}://#{RE_USER_PASS}?#{RE_HOST}#{RE_PORT}?#{RE_PATH}?$]o
     def self.split_url(url)
        return [] unless RE_URL =~ url
        $~[1..-1]
     end

     def self.download(url)
        scheme, user, pass, host, port, path = split_url(url)
        return unless scheme
        path ||= "/"
        default_port = scheme=="https" ? 443 : 80
        port = port ? port.to_i : default_port
        c = ""
        http = Net::HTTP.new(host, port)
        http.open_timeout = 5
        http.read_timeout = 5
        if scheme=="https"
           http.use_ssl = true
           http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end

        http.start do |http|
           opt = {}
           opt["Authorization"] = "Basic "+["#{user}:#{pass}"].pack("m").chomp if user && pass
           opt["User-Agent"] = "Test agent"
           http.get(path, opt)
        end
     end
  end

使うときには、

r = Misc.download("http://user:pass@host_name.org/path/file")
puts r.body

とかする。

Tags: Ruby

2007-09-12(Wednesday)

DebianでNet::HTTPS

libopenssl-rubyパッケージを入れていないと、net/https.rbはインストールされなかった。

Tags: Ruby

SQLiteでRails

Railsで使うデータベースは、デフォルトではMySQLなので、今まではちょっとRailsの動きを試したいとか思ってテスト用のプロジェクトを作っても、そのたびにテスト用のMySQLのデータベースを作っていた。hoge_development, hoge_test, hoge_productionなどなど。

そんな次第で毎回毎回データベースの初期操作をするのもあずましくないなと思っていたわけだけど、最近、SQLiteを使うと手軽にテスト環境を作れることがわかった。具体的には、

~$ rails -d sqlite3 test

とすると、SQLite用のconfig/database.ymlを使った空プロジェクトを作ってくれるし、特にデータベースの準備をしなくても

~/test$ rake db:migration

だけでデータベースの作成からやってくれる。素晴らしい!

Tags: Rails

Debianのlibsqlite3-rubyのバグ?

データベースの用意をするときに、

t.column :name, :string

とすると、

Hoge.create.name # => "NULL"

となってしまう。

また、デフォルトで空文字列を指定して

t.column :name, :string, :default=>""

とすると、

Hoge.create.name # => "''"

となってしまう。

どうもDebianのlibsqlite3-rubyパッケージのバージョンが古いせいらしく、こっちをアンインストールした上で、gemでsqlite3-rubyをインストールすると直った。

その際、

extconf.rb:1:in `require': no such file to load -- mkmf (LoadError)

というエラーが出たが、これはDebianの方の ruby1.8-dev パッケージをインストールすることで解決した。

また、

make: *** `sqlite3_api_wrap.o' に必要なターゲット `ruby.h' を make するルールがありません.  中止.

というエラーも出て、こちらは libsqlite3-dev パッケージのインストールで解決できた。

Tags: メモ

2007-09-13(Thursday)

指定した複数の子オブジェクトを持つオブジェクトの抽出

はてブやdel.icio.usのようにタグづけされたブックマークで、指定した複数のタグすべてを付けられたブックマークを、データベースからどうやってまとめて取り出すか、というのを考えてみたですよ。

まず、前提。モデルは以下の3つ。

  1. Bookmark
  2. Tag
  3. BookmarkTagAssign
    • bookmark_id
    • tag_id

habtmはRails2.0で無くなるらしいという噂を目にしたので、has_many :throughを使って関連付けてる。BookmarkTagAssignモデルがタグとブックマークを結びつける結合テーブルのモデル。

1つだけ指定したタグが付けられた(他のタグも付けられていて良い)ブックマークをデータベースから取り出すためには、

Bookmark.find(:all, :include=>"bookmark_tag_assigns", :conditions=>[bookmark_tag_assigns.tag_id = ?", tag.id])

とかで出来る。

問題は2つ以上のタグが全て付けられたブックマークの検索。真っ先に思いついて失敗したのが

Bookmark.find(:all, :include=>"bookmark_tag_assigns", :conditions=>["bookmark_tag_assigns.tag_id IN (?)", tags.map(&:id)])

これだと、たとえば2つのタグ hogeとfugaを指定したときに、hogeが付いていてfugaが付いていないブックマークとかが全部引っかかってきてしまう。

それならと

Bookmark.find(:all, :include=>"bookmark_tag_assigns", :conditions=>["bookmark_tag_assigns.tag_id = 1 AND bookmark_tag_assigns.tag_id = 2"])

というのを試してみたけど、これも期待通りには動かない。

Rails本とにらめっこして調べてみたけど、どうもActiveRecordの機能を使ってサクッと処理させる方法がわからず、結局SQLのリクエストを書くことにした。

SQLの入門サイトで少し調べて(SQL講座 [Smart]SQL -TECHSCORE-がとても役に立ちました)、どうもINTERSECTを使うのが良さそうだと分かった。

まず、

SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id = 1 INTERSECT SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id = 2 INTERSECT ...

と、必要な分だけ繰り返す。これで、指定したタグ全てが付けられたBookmarkモデルのid一覧が手に入る。これを使って、

Bookmark.find(:all, :conditions=>["id IN SELECT ....."]

とすると、指定したタグが全部付いたブックマークだけが取り出せる(SQLの作成は、Rubyの文字列処理を使って作る)。

ついでに、はてブにあるような「指定したタグ全部付いたブックマークが付けられている、残りのタグ全部」を取り出す方法も。まず、

@rest_tags = Tag.find(:all, :include=>["bookmark_tag_assigns", "bookmarks"], :conditions=>["bookmark_tag_assigns.bookmark_id in (#{sql})"])

で、取り出したブックマークが付けられているタグ全部が手に入るので、ここから、指定したタグを引けば良い。

これを、実際にSQLite3で動かしてみた。すると、残りのタグを取り出す部分がやけに重い。

じゃあ他のデータベースでも試してみるべかと思って、MySQLで動かすように準備をして、とりあえずテストしてみると、エラーが出てしまう。なんだ?と思って調べてみると、なんとMySQLではINTERSECTが使えない。が〜(゜д゜;)〜ん

それで、INTERSECTを使わずに何とかできないかと考えてみた。で、思いついたのが次の手順。

  1. BookmarkTagAssigns(以下BTA)のテーブルから、1つ目のタグのidとtag_idが一致するものを集めて、そのbookmark_idをまとめる
  2. BTAから、1で集めたbookmark_idのいずれかとbookmark_idが一致し、かつ、2つ目のタグのidとtag_idが一致するものを集めて、そのbookmark_tdをまとめる
  3. BTAから、2で集めたbookmark_idのいずれかとbookmark_idが一致し、かつ、2つ目のタグのidとtag_idが一致するものを集めて、そのbookmark_tdをまとめる
  4. (以下同様)

といったことをSQLでサブクエリを使って書けば良いんじゃないかな?と作ったのが次の処理。

base = "SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id"
sql = ""
@tags.each do |i|
    if sql.empty?
       sql = "#{base} = #{i.id}"
    else
       sql = "#{base} = #{i.id} AND bookmark_id IN (#{sql})"
    end
end
@bookmarks = Bookmark.find(:all, :conditions=>"id in (#{sql})")
@rest_tags = Tag.find(:all, :conditions=>["id in (SELECT tag_id FROM bookmark_tag_assigns WHERE bookmark_id IN (#{sql})) AND NOT id IN (?)", @tags.map(&:id)])

指定されたタグの数だけ入れ子にしたサブクエリを作って、それをBookmark_findに渡している。SQLは次のようになる。

SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id = 3 AND bookmark_id IN ( SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id = 2 AND bookmark_id IN (SELECT bookmark_id FROM bookmark_tag_assigns WHERE tag_id = 1) )

これで、MySQLでも動くようになった上、なんとSQLiteで動かしても十分早く動くようになった。

感想 : SQLうまくかくことがとてもじゅうようだというのがよくわかりました。

Tags: Rails

タグにタグを付けたい

膨大なタグの一部

ふと気づいたらタグの数が621とかになっていて、思ったこと。たとえば、'RubyOnRails'や'gems'というタグに、'Ruby'というタグを付けてタグそのものの検索に使えたら便利じゃないかなと思った。普段はタグの付いていないタグだけがトップページに出るようになっていて、必要に応じてタグを辿って細かなタグを見つけ出せるとか。

でも本当にそんなんで便利かな?もやもやもや。

そんなこんなを込めて気分転換にこの2日ほどt2marksと名付けたモノを作ってたりして。

今のところ、はてブのatom.xmlからブックマークをインポートしたり、タグにタグをつけて、でもまだ検索できなかったり、そんな感じであります。


2007-09-14(Friday)

指定した複数の子オブジェクトを持つオブジェクトの抽出 (2)

INTERSECTを使わない方のSQL、MySQLでも動くには動くけど、タグ数600超、ブックマーク数5000超のデータで試したら、タグ1つ検索するのに1時間とかかかってしまう。SQLiteだと数秒で終わるんだけどなぁ。

ま、そのうち時間ができたら、もうちょっとSQLの勉強するか


2007-09-26(Wednesday)

Structは効率的

Matzにっき(2007-09-15)経由Rubyのダイエット(pdf)より。後で役に立ちそうなのでメモ。

クラス定義するよりStructを使うほうがメモリ効率がよくて、特にRuby1.9以降では変数3つ以下の場合、更に効率がよくなるとのこと。

Tags: メモ Ruby