株式会社クリアコード > ククログ

ククログ

クリアコードはプログラミングが好きなソフトウェア開発者を2名募集しています。

クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)Rubyに関連した開発を得意としています。

Ohloh profile for kou RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|
タグ:

日本Ruby会議2010発表資料: るりまサーチの作り方 - Ruby 1.9でgroonga使って全文検索

注: 長いです。

日本Ruby会議2010るりまサーチの作り方について発表しました。

るりまサーチの作り方

[rk10][29S06] るりまサーチの作り方 - Ruby 1.9でgroonga使って全文検索

2010-08-30
再生: 45
コメント: 1
マイリスト: 2

[rk10][29S06] るりまサーチの作り方 - Ruby 1.9でgroonga使って全文検索 (32:59)
Kouhei Sutou (ClearCode Inc. / COZMIXNG)このトークではるりまサーチについてとるりまサーチの作り方について話します。るりまサーチはRubyリファレンスマニュアル刷新計画の成果物であるRubyのリファレンスマニュアルを高速に検索するWebアプリケーションです。るりまサーチはRubyインタプリタとしてRuby 1.9.1(MRI)、全文検索エンジンとデータストアとしてgroonga、Rubyとgroongaのインターフェイスとしてrroongaを使っています。作り方の説明では、特にこれらの技術の使い方について詳しく説明します。るりまサーチ: http://rurema.clear-code.com/

cache!!

ステージから見た感じだと立ち見の人もいたようでした。セッションに参加してくれたみなさん、会場を担当してくれたりレポートしてくれたスタッフのみなさん、ありがとうございました。

時間の関係で省略したことも含めてまとめておきます。

話すこと

話すこと

資料の中では、まず、るりまサーチについて説明し、その後、全文検索システムとしてのるりまサーチをどう作るのかを説明しています。

るりまサーチとは

るりまサーチとは

るりまサーチはRubyリファレンスマニュアル刷新計画 (通称るりま)の成果物であるRuby本体のリファレンスマニュアルを全文検索するためのWebアプリケーションです。るりまサーチが必要とされていた理由は、既存のリファレンスマニュアル閲覧Webアプリケーションに組み込まれていた検索機能の速度が遅かった*1からです。せっかく有益なリファレンスマニュアルがあっても、目的のエントリにたどりつくのが難しければ、有効に活用することができません。検索機能の面からリファレンスマニュアルの有効活用を支援する全文検索システムがるりまサーチです。

ポイント: ドリルダウン

ポイント: ドリルダウン

るりまサーチはRubyのリファレンスマニュアルに特化した小さな全文検索システムですが、最近の全文検索システムにとって重要なエッセンスが含まれています。全文検索システムを開発する場合はこれらのエッセンスを含めることを検討してみてください。

まず1つ目はドリルダウンと呼ばれる機能です。Solrなど他の全文検索システムによってはファセットと呼ぶこともあります。ドリルダウンとは、通常の検索結果に加えて、別のパラメータでの絞り込み結果も同時に提供する機能です。スライド中では「Rubyのバージョンで絞り込んだ結果、何件ヒットするか」という情報も表示しています。

この機能で嬉しいことは以下の2点です。

  • 検索キーワードを入力しなくてもクリックだけで結果を絞り込んでいける。
  • 絞り込み結果が0件になる条件を除外するので、「絞り込んだ後に0件ヒットになる」無駄な条件を指定せずに済む。

どちらもユーザの使い勝手を向上させるインターフェイスにつながります。ショッピングサイトなどでも使われているインターフェイスですね。

ポイント: URL

ポイント: URL

2つ目はURLのパスに絞り込み条件を含めることです。これは、内部ネットワーク用の全文検索システムではなく、インターネット上に公開する全文検索システム向けです。

最近ではURLにUTF-8でエンコードされたページ情報を含めることは一般的になってきました。WikipediaやAmazonでも行っています。Web検索エンジンはURLからも検索用の情報を抽出しているようなので、SEOになると考えられます。

ポイント: キャッシュ

ポイント: キャッシュ

3つ目はキャッシュです。より快適に検索・絞り込みを行うにはできるだけ速いレスポンスが求められます。レスポンスを高速化するためには、以下のような方法があります。

  • アルゴリズムを改良し、少ない計算量で結果を計算できるようにする。
  • 同じ結果を返す処理の処理結果を保存して、2回目以降の処理で結果を再利用する。

手軽に高速化する場合は後者のキャッシュ機能が便利です。キャッシュをする場合はキャッシュを無効化するタイミングを慎重に検討する必要があります。このタイミングを誤ると、期待した結果が返ってこないという問題が発生します。

キャッシュを無効にするタイミングはアプリケーションに依存します。一般的に、データが変更されるまでは同じキャッシュを利用できます。るりまサーチの場合は1日1回バッチ処理で元データを更新しています。そのため、同じキャッシュを1日使いまわすことができます。これにより高速にレスポンスを返すことができます。

また、キャッシュの効果を高めるためには、処理の内部よりもクライアントに近いところでキャッシュする必要があります。その方がより多くの計算を省略することができるからです。るりまサーチはログインせずに使えるシステムなので、同じ検索リクエストの結果はクライアントに関わらず同一になります。そのため、レスポンスをまるごとキャッシュすることができ、とても高い効果があります。

ログインが必要なシステムの場合は、クライアント毎に変更される部分のみJavaScriptで動的に生成したり、iframeを用いて別HTMLにすることにより、ログインによって変更されない部分ではキャッシュを利用することができます。それが難しい場合はもっと処理の内部でキャッシュをすることになります。この場合はキャッシュの効果が薄くなります。

キャッシュを用いることにより劇的にレスポンス速度を改善することができますが、キャッシュの有効期限とキャッシュする場所についてはよく検討する必要があります。

ポイント

ポイント

るりまサーチに含まれている最近の全文検索システムに重要なエッセンスは以下の3つです。

  • ドリルダウン
  • URLに検索条件を含める
  • キャッシュ

それでは、このようなエッセンスを含む全文検索システムるりまサーチの作り方について説明します。

全文検索システム

全文検索システム

全文検索システムは以下の5つの要素からなります。

  • 検索対象
  • クローラー
  • インデクサー
  • 全文検索エンジン
  • 検索インターフェイス

まず、検索対象からクローラーが検索対象とする文書を収集します。次に、それらからインデクサーがテキストやメタ情報を抽出して全文検索エンジンに登録します。全文検索エンジンに登録したデータからユーザが求めるデータを検索して提示するのが検索インターフェイスです。

るりまサーチの場合

るりまサーチの場合

るりまサーチの場合は以下のようになります。

検索対象
リファレンスマニュアル。
クローラー
リファレンスマニュアルはリポジトリからチェックアウトするので必要なし。
インデクサー
BitClustに含まれる機能を使ってリファレンスマニュアルの情報を全文検索エンジンに登録する。新規開発。
全文検索エンジン
groonga
検索インターフェイス
Ruby 1.9とRackを用いたWebインターフェイス。新規開発。

この中で、るりまサーチの重要な部分である全文検索エンジンgroongaについて説明します。

groonga: 特徴

groonga: 特徴

発表当日に初のメジャーバージョン1.0.0がリリースされたgroongaは、MySQLとの組み合わせで広く利用されているSennaの後継プロジェクトです。Sennaでのよいところを維持しつつ、さらに改良が加えられています。

Sennaは妥協しない転置索引実装と参照ロックしない更新アルゴリズムによるリアルタイム検索の実現が大きな特徴でした。Senna自体はデータストア機能を持たず、MySQLなど外部のデータストアと連携します。MySQLとSennaを連携させるソフトウェアはTritonnと呼ばれ、SQLで高速な全文検索機能を利用できることから広く使われています。しかし、MySQL側のロックモデルのため常に検索可能な状態で更新処理を行うことができません。そのため、せっかくのSennaの参照ロックフリーな更新アルゴリズムの特徴を活かしきれませんでした。

そこで、groongaでは独自のデータストア機能を提供し、外部のシステムによる制限を回避してgroongaの性能を発揮できるようにしました。データストアはドリルダウンを高速に実現できるカラム指向を採用しています。

また、HTTP/memcached/独自プロトコルなどのネットワークプロトコルも実装し、Solrのように検索サーバとして利用することもできるようになっています。

その他にも、より大規模な文書に対してもスケールするような性能改善や、モバイル端末の普及により重要性が増している位置情報データに対応するなど新規機能が含まれています。ただし、これらの改善のためにSennaとの互換性がなくなっています。Sennaの後継としてgroongaと名前を変更した理由はこのためです。

定義例: るりまサーチ

定義例: るりまサーチ

それでは、るりまサーチのケースを例にしてgroongaの使い方を説明します。手順は以下の通りです。

  1. スキーマ定義
  2. データ登録
  3. 検索

RDBと同じようにgroognaでも、まず、スキーマを定義します。

スキーマはRDBと同じように以下の3つの要素から構成されます。

  • テーブル
  • カラム

RDBではさらに索引もでてきますが、groongaでは↑の3つの要素を使って索引を作成するので、RDBより特別な存在ではありません。

スキーマを定義するときは、まず、検索対象がなにかを考えます。そして、その対象がどのくらいの粒度で1エントリになるかを考えます。るりまサーチではリファレンスマニュアルが検索対象で、メソッドやクラスそれぞれが1つのエントリになります。検索対象全体をテーブルとし、エントリをテーブルの各レコードにします。るりまサーチでは検索対象全体を扱う「Entries」テーブルを定義しています。

テーブルには検索結果に表示したい内容と検索時に利用する内容をカラムとして定義します。るりまサーチの場合にはメソッド名やクラス名を格納する「name」カラムやドキュメントを格納する「description」カラムなどを定義しています。

検索対象用のテーブルを定義したら索引を定義します。ここがRDBと異なる部分です。全文検索用の索引では単語と文書を対応させる語彙表が必要になりますが、同じトークナイザー*2を利用している場合は同じ語彙表を共有して省スペース化したり、同じテキストに複数のトークナイザーを適用して検索精度や検索漏れのトレードオフを調整したり、といったRDBよりも細かい制御ができます。

単にヒットしたかどうかではなく、検索結果の重み付けも重要です。有用な検索結果を提供するためには、クエリに適していると思われる結果ほど上位に提示する必要があります。しかし、どのように重み付けをするのが適切かは全文検索システムに大きく依存します。そのため、groongaでは索引毎に重み付けをカスタマイズする機能を提供しています。

るりまサーチではメソッド名やクラス名に完全一致した場合はよりマッチしていると判断するように*3、名前と完全一致だけする語彙表「Names」テーブル*4を定義し、そこに「name」カラムの索引を定義します。検索時にはこの索引にマッチした場合は重み付けを大きくします。

ドキュメント部分(「summary」カラムと「description」カラム)はトークナイザーを設定した全文検索用の語彙表「Terms」テーブルを共有しています。こっちの索引にマッチした場合は重み付けを小さくします。

スキーマはgroongaが提供している組み込みのDDLで定義する方法と、groongaのRubyバインディングであるrroongaが提供するDSLで定義する方法があります。

groongaのDDL:

# 検索対象のテーブル
table_create Entries TABLE_HASH_KEY ShortText
# 全文検索用の語彙表。トークナイザーとしてN-gramを使用。
table_create Terms TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram
# 完全一致検索用の語彙表。トークナイザーはなし。
table_create Names TABLE_HASH_KEY ShortText

# 検索対象のデータ格納場所
column_create Entries name COLUMN_SCALAR Names
column_create Entries summary COLUMN_SCALAR Text
column_create Entries description COLUMN_SCALAR Text

# 全文検索用の索引
column_create Terms Entries_summary COLUMN_INDEX Entries summary
column_create Terms Entries_description COLUMN_INDEX Entries description

# 完全一致検索用の索引
column_create Names Entries_name COLUMN_INDEX Entries name

rroongaのDDL:

Groonga::Schema.define do |schema|
  # 完全一致検索用の語彙表。トークナイザーはなし。
  schema.create_table("Names",
                      :type => :hash,
                      :key_type => "ShortText") do |table|
  end

  # 検索対象のテーブル
  schema.create_table("Entries",
                      :type => :hash,
                      :key_type => "ShortText") do |table|
    table.reference("name", "Names")
    table.text("summary")
    table.text("description")
  end

  # 全文検索用の語彙表。トークナイザーとしてN-gramを使用。
  schema.create_table("Terms",
                      :type => :patricia_trie,
                      :key_type => "ShortText",
                      :default_tokenizer => "TokenBigram",
                      :key_normalize => true) do |table|
   # 全文検索用の索引
    table.index("Entries.summary")
    table.index("Entries.description")
  end

  schema.change_table("Names") do |table|
    # 全文検索用の索引
    table.index("Entries.name")
  end
end

登録例: るりまサーチ

登録例: るりまサーチ

スキーマを定義したらデータを登録します。索引は自動で更新されるため、データ用のカラムにデータを登録するだけで動作します。

データの登録方法はgroongaのloadコマンドを使う方法と、rroongaを使う方法があります。

groongaのloadコマンド:

load --table Entries
[
  ["_key", "name", "summary", "description"],
  ["String#sub", "sub", "置換", "1つ置換"],
  ["String#gsub", "gsub", "置換", "全部置換"]
]

rroonga:

entries = Groonga["Entries"]
entries.add("String#sub",
            name: "sub",
            summary: "置換",
            description: "1つ置換")
entries.add("String#gsub",
            name: "gsub",
            summary: "置換",
            description: "全部置換")

Rubyで登録データの前処理を行う場合はrroongaを使う方がよいでしょう。Ruby以外で処理を行う場合はデータからJSONを生成し、groongaのloadコマンドを使う方がよいでしょう。るりまサーチはRubyで前処理*5をしているのでrroongaでデータを登録しています。

検索例: るりまサーチ

検索例: るりまサーチ

全文検索する場合は検索対象のカラムを指定する方法と、明示的に利用する索引を指定する方法の2通りあります。カラム単位で重み付けをしたい場合はカラムを指定し、索引単位で重み付けをしたい場合は索引を指定します。両方の指定方法を混ぜ合わせることもできます。

データの登録方法はgroongaのselectコマンドを使う方法と、rroongaを使う方法があります。

groongaのselectコマンド:

# 「description」カラムに「1つが」含まれているエントリを検索
select Entries description "1つ"
[[...],
 [[[...],
   [..., ["_key", ...], ["name", ...], ["summary", ...], ["description", ...], ...]],
  [..., "String#sub", "sub", "置換", "1つ置換", ...],
  ...]]
# 「sub」が含まれているエントリを検索。ただし、「name」が
# 「sub」だった場合は重みを大きくする。
select Entries "name * 100 | summary | description" "sub"
[[...],
 [[[...],
   [..., ["_key", ...], ["name", ...], ["summary", ...], ["description", ...], ...]],
  [..., "String#sub", "sub", "置換", "1つ置換", ...],
  ...]]

groongaのselectコマンド(HTTP経由):

# 「description」カラムに「1つが」含まれているエントリを検索
% wget -O - 'http://localhost:10041/d/select?table=Entries&match_columns=description&query=1つ'
[[...],
 [[[...],
   [..., ["_key", ...], ["name", ...], ["summary", ...], ["description", ...], ...]],
  [..., "String#sub", "sub", "置換", "1つ置換", ...],
  ...]]
# 「sub」が含まれているエントリを検索。ただし、「name」が
# 「sub」だった場合は重みを大きくする。
% wget -O - 'http://localhost:10041/d/select?table=Entries&match_columns=name*100|summary|description&query=sub'
[[...],
 [[[...],
   [..., ["_key", ...], ["name", ...], ["summary", ...], ["description", ...], ...]],
  [..., "String#sub", "sub", "置換", "1つ置換", ...],
  ...]]

rroonga:

entries = Groonga["Entries"]
# 「description」カラムに「1つ」が含まれているエントリを検索
result = entries.select do |record|
  record.description =~ "1つ"
end
# 「sub」が含まれているエントリを検索。ただし、「name」が
# 「sub」だった場合は重みを大きくする。
result = entries.select do |record|
  target = record.match_target do |match_record|
    (match_record["name"] * 100) |
      (match_record["summary"]) |
      (match_record["description"])
  end
  target =~ "sub"
end

PHPなどRuby以外の言語から利用する場合はgroongaサーバを立てて、HTTP経由で検索するのがよいでしょう。Rubyから利用する場合は、selectコマンドで十分ならselectコマンドを利用、より複雑なことをしたい場合はrroongaを利用するのがよいでしょう。selectコマンドでもドリルダウンはサポートされて入るので、多くの場合はselectコマンドで十分でしょう。

るりまサーチでは、selectコマンドが提供するクエリ書式を利用したくない、rroongaが提供するページネーション機能を利用したい、などの理由でselectコマンドではなくrroongaを使っています。rroongaを利用してドリルダウンを実現する例にもなっています。

るりまサーチを例にして、groongaを用いて全文検索システムを開発する場合の基本的な流れを説明しました。より詳しいことはGitHubるりまサーチのリポジトリにあるソースコードを見てください。

racknga

racknga

るりまサーチの検索WebインターフェイスはRuby 1.9とRackの上に構築されています*6。るりまサーチを開発した際に、るりまサーチ以外でも使えそうな部分がでてきたので、rackngaという名前でるりまサーチと別パッケージとして公開しています。

rackngaにはRackのミドルウェアとMuninプラグインが含まれています。MuninのプラグインはPassengerの以下の情報を収集します。

  • 処理したリクエスト数
  • 処理中のリクエスト数
  • プロセスの状態
  • プロセスの起動時間

Rackのミドルウェアは1つずつ説明します。

エラー通知

エラー通知

アプリケーション内でエラーが発生した場合にメールでその内容を通知するミドルウェアです。RailsのException NotifierのRack用です。

以下のように利用します。

config.ru:

require 'racknga'

notifier_options = {
  "host" => 127.0.0.1,
  "from" => "rurema@example.com",
  "to" => "developer@example.com",
  "charset" => "iso-2022-jp",
  "subject_label" => "[るりまサーチ] ",
}
notifiers = [Racknga::ExceptionMailNotifier.new(notifier_options)]
use Racknga::Middleware::ExceptionNotifier, :notifiers => notifiers
# ...
run your_application

できるだけ多くのエラーを検出するためになるべく最初の方でuseしてください。

キャッシュ

キャッシュ

主にサーバ1台や2台などで処理できる程度の中規模のPassenger環境で利用することを想定したキャッシュミドルウェアです。ヘッダーやボディを含めHTTPのレスポンス全体をgroongaのデータストアにキャッシュします。Passengerでは複数のインスタンスが別プロセスで起動しますが、groongaは複数プロセス間で同一のデータベースを操作することができるため、別のインスタンスがキャッシュした内容を他のインスタンスから参照することができます。以下のように利用します。

config.ru:

require 'racknga'
require 'racknga/middleware/cache'

# ...
# use Rack::Deflater
# use Rack::ConditionalGet
# ...
base_dir = Pathname.new(__FILE__).dirname.cleanpath.realpath
cache_database_path = base_dir + "var" + "cache" + "db"
use Racknga::Middleware::Cache, :database_path => cache_database_path.to_s
run your_application

他のミドルウェアと組み合わせやすいように、なるべくアプリケーションに近い部分に置くことをよいでしょう。

複数のサーバ間でキャッシュを共有したい場合は別の仕組みを利用することをオススメします。

条件付き圧縮

条件付き圧縮

ネットワーク帯域を節約するためには、レスポンスを圧縮して返すことが有効です。しかし、Internet Explorer 6では問題があることがわかっています。そのため、Internet Explorer 6の場合は常に圧縮しないようにするのがこのミドルウェアです。Rack::Deflaterのラッパーです。以下のように利用します。

config.ru:

require 'racknga'

# ...
use Racknga::Middleware::Deflater
# use Rack::ConditionalGet
# ...
run your_application

JSONP

JSONP

Web APIとしてサービスを提供する場合、JSON形式で結果を返すことが多くなっています。クライアント側でWeb APIにアクセスする場合はJSONPを利用することになります。

このミドルウェアはJSONPに対応しておらず単にJSONデータを返すだけのアプリケーションをJSONPに対応させることができます。また、以下のような配置にすることにより、キャッシュを有効にしたままJSONP対応にすることができます。

config.ru:

require 'racknga'
require 'racknga/middleware/cache'

use Rack::Middleware::JSONP

base_dir = Pathname.new(__FILE__).dirname.cleanpath.realpath
cache_database_path = base_dir + "var" + "cache" + "db"
use Racknga::Middleware::Cache, :database_path => cache_database_path.to_s

run your_application # "Content-Type: application/json"のレスポンスを返す

現在、るりまサーチはWebサービスを提供していませんが、将来の拡張を念頭においてこのミドルウェアがrackngaに含まれています。

まとめ

まとめ

るりまサーチはドリルダウンやキャッシュを利用することにより、快適に目的のドキュメントへ到達できるような工夫をしています。るりまサーチ以外にもリファレンスマニュアルを利用するツールがあるので有効活用しましょう。

ドリルダウンを効果的に利用した高速な全文検索システムにはgroongaが適しています。Rubyとの親和性も高いgroongaで全文検索システムを開発してみてはいかがでしょうか。汎用ユーティリティであるrackngaも一緒に用いることにより開発・運用が改善されるでしょう。

最後にお知らせです。クリアコードではプログラミングが好きな開発者を募集しています。プログラミングが好きな人は検討してみてください。

お知らせ

*1  数十秒以上かかる。

*2  文章から単語を抜き出す処理。

*3  メソッド名で検索することは多いですよね?

*4  実体はトークナイザーなしのハッシュテーブル。

*5  BitClustを使ってメソッド単位にドキュメントを分割するなど。

*6  RailsやSinatraなどは使っていません。

Tags: Ruby | このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-09-01

プログラミングRuby 1.9

前作プログラミングRubyのRuby 1.9対応版です。

前作:

プログラミングRuby 第2版 ライブラリ編
Dave Thomas/Chad Fowler/Andy Hunt/田和 勝/まつもと ゆきひろ
オーム社
¥ 4,410

このシリーズはページ数からもわかる通り、Rubyについて細かいところまで網羅しているのが特徴です。例えば、Proc.newlambdaの違いはわかりますか?以下のようなクラス定義の書き方による参照解決の違いはわかりますか?

module MyLibrary
  class MyClass
    # ...
  end
end

class MyLibrary::MyClass
  # ...
end

これまでのシリーズと同様、本書でもそのあたりの細かいところにも触れています。もちろん、1.8と1.9で変わった部分についても触れています。このシリーズでRubyを覚えたという人もわりといるようですが、それはこの網羅性が役に立ったのではないでしょうか。

おそらく、1回読んだだけではすべてを覚えることは無理でしょう。読んで、実際にRubyのコードを書いて、気になったところをまた調べ直す、そのサイクルができるのがこのシリーズです。このシリーズでRubyを覚えた人はこのようなサイクルを使ったのではないでしょうか。先日1.9.2がリリースされ、これから1.8から1.9への移行がより進むと考えられます。そのときに、つまずいたところを本書で調べて理解していくというサイクルに使えるでしょう。

Rubyは(わりと)読みやすいコードを(わりと)書きやすい言語仕様になっています*1が、複雑なこともいろいろできる仕様も多く含まれています。ほとんどの場合は複雑なことはしなくても済むはずですし、そのように書いておく方がよい場合の方が圧倒的に多いです。一度、(わからない部分があったとしても)全体を一通り読んでおいて、Rubyの動作はどうなっているかをざっくりと知っておくとよいでしょう。複雑なことをしそうになったときに、別の方法もあった気がする、と気付けるようになるくらいで十分です。気付けたら本書なりるりまなりで詳しく調べることができます。

本書は分量の多さから言語編とライブラリ編の2編構成になっています。言語編に比べてライブラリ編の内容が手薄になっているので気をつけてください。必ずしもそれぞれのライブラリの最新の情報に追従できているわけではありません。本書をきっかけにしてるりまやWeb上での情報などで補正する必要があるでしょう。

試しにRubyを勉強してみたい、という人には敷居が高いでしょう*2。しかし、Rubyを使いこなそうというくらいの気持ちがあるのであれば、助けになってくれることでしょう。

プログラミングRuby 1.9 −ライブラリ編−
Dave Thomas with Chad Fowler and Andy Hunt/まつもとゆきひろ/田和 勝
オーム社
¥ 4,620

*1  読みやすいコードを書こうという気がなければ読みやすいコードにはならないでしょう。

*2  まず、分量が多いですし。

Tags: Ruby | このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-08-23

名札には名前を大きく書きましょうジェネレータ改: cairoとPangoでPDF生成

早いもので来週末は日本Ruby会議2010です。日本Ruby会議では、たくさんいる(会ったことはないけど名前を知っている)参加者がお互いを認識しやすいように大きな名札をつけることが恒例となっています。

RubyKaigi日記でも名札には名前を大きく書きましょうと呼びかけています。この中で、「あらかじめ太くて大きなフォントで、黒々と印刷してきたものを持参して、名札に貼り付けるのはいかがでしょう。」と提案しています。しかし、自分でデザインするのはわりと面倒なものです。

そこで、kdmsnrさんが名札には名前を大きく書きましょうジェネレータを作りました。これはtwitter IDを指定するだけでRubyKaigi日記で提案されているようなデザインの画像を生成してくれます。

@kdmsnr

でも、印刷するならPDFの方が嬉しいよね、ということでPDFを出力できるように改造したのが名札には名前を大きく書きましょうジェネレータ改です。

@kdmsnr改

PDFも出力できるようにした他に、フォントを選べたり、細かく調整するためにSVGも出力できるようにしています。それでは、どのように実現しているかを説明します。

使っているもの

描画にはcairo、文字の配置にはPango、画像の読み込みにはGdkPixbufを用いています。どれもLinux、*BSD、Mac OS X、Windowsなど多くの環境で動作するライブラリです。ここでは、cairoとPangoだけ説明します。

cairo

cairoは2次元グラフィックを生成するためのライブラリで以下のような特長があります。

  • ベクトルベースのAPI
  • 描画処理のコードを変更せずに出力先を変えることができる

ベクトルベースのAPIとなっているということは品質を落とさずに拡大・縮小ができるということです。ジェネレータ改では、実際のサイズの画像とサムネイル画像を生成しますが、このようなことが以下のように描画処理を変更せずに実現できます。

def render(context)
  # 実際のサイズの描画
end

# 実際のサイズを描画するとき
render(context)

# 1/3サイズのサムネイルを描画するとき
context.scale(1 / 0.3, 1 / 0.3) # 描画処理の前にこれを呼ぶだけでOK
render(context)

描画処理のコードを変更せずに出力先を変えることができると、描画結果はPNGにしてブラウザで確認、印刷するときはPDF、編集する時はSVG、というように用途にあわせたグラフィックのフォーマットを提供することが簡単にできるということです。これは今回のようなWeb上で印刷物を生成する場合はとても便利な機能です。いちいちPDFで確認するのは面倒ですよね。サムネイルで一覧表示する場合もPNG+ブラウザの方が便利です。

cairoの詳しい使い方はRubyist Magazine - cairo: 2 次元画像描画ライブラリを見てください。

Pango

Pangoは多言語に対応したテキストの配置を行うライブラリです。フォントの扱いなどテキストの配置に関することを抽象化してくれるので、TTFやOTFなどフォントフォーマットの違いや、フォントファイルをどこに置くかなどをプログラム側で気にする必要がありません。

ジェネレータ改ではインストールされているフォントを列挙したり、できるだけ大きいテキストサイズを自動検出するためにPangoを利用しています。テキストを中央揃えにするのもPangoの機能を利用しています。

システムにインストールされているフォントの一覧は以下のように取得できます。

font_families = Pango::CairoFontMap.default.families.collect do |family|
  family.name
end
p font_families # => ["Mona", "梅明朝S3", "衡山毛筆フォント草書", ...]

残念ながらPangoを利用するためのまとまった日本語の資料はありません。興味のある人はソースコードを見てください。

まとめ

名札には名前を大きく書きましょうジェネレータ改をネタにしてcairoとPangoを紹介してみました。cairoとPangoはFirefoxやGTK+などデスクトップで使われることが多いライブラリですが、Webアプリケーションのようにサーバサイドでも有用なライブラリです。ジェネレータ改のように用途にあわせて画像のフォーマットを使い分けたい場合は、cairoとPangoを使ってみてはいかがでしょうか。また、日本Ruby会議2010に参加する人はジェネレータで作った名札を印刷して持っていってはいかがでしょうか。

名札には名前を大きく書きましょうジェネレータ改のソースコードはGitHubにあります。

Tags: Ruby | このエントリの Delicious history 3 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-08-18

日本Ruby会議2010で発表します: るりまサーチの作り方 - Ruby 1.9でgroonga使って全文検索

毎年開催規模が大きくなっている日本Ruby会議今年も参加します。今年も去年と同じくスポンサーと発表者として参加します。

発表タイトルは「るりまサーチの作り方 - Ruby 1.9でgroonga使って全文検索」です。

内容

発表内容はるりまサーチという、Rubyリファレンスマニュアル刷新計画 (通称るりま)の成果物であるRubyのドキュメントを全文検索するWebアプリケーションの関連技術を紹介するというものです。もう少し具体的にいうと以下のような話題になります。

  • 全文検索エンジンgroongaを用いた検索サイトの作り方
    • 「情報を絞り込む」を主体としたユーザインターフェイス
    • 高速に検索するためのデータの持ち方
    • groongaの性能を落とさずによさを活かすには、どのようにRubyと連携すればよいか
  • Ruby 1.9 + Rackで効率よくWebアプリケーションを作る方法
    • 運用時に発生した問題への対応
    • リソースを追加投入する前にやっておくべきスループット改善方法(中規模向け)
    • Webサービス用APIの提供

Twitterなどを見てもわかる通り、世界には情報がどんどん増えていきます。そうすると、その中から必要な情報を選ぶことが重要になっていきます。しかし、情報が溢れた世界では人力のみで効率よく情報を選択することは困難です。最近Twitterが提供をはじめた「おすすめユーザー」という機能も、溢れかえった情報の中から必要な情報を見つけることを支援するための機能と言えます。

必要としている人が必要な情報を見つけやすくしたい、そんなアプリケーションを作りたいと考えている人に聞いて欲しい内容です。もしかしたら、groongaが提供する必要な情報を見つけるための機能でそれを実現できるかもしれません。発表日時は最終日8/29(日)の13:30-14:00で、場所は中ホールです。同じ時間帯に、別の場所ではかずひこさんの外国で暮らすRubyistだけど何か質問ある?TermtterKaigiMSWin32版Ruby野良ビルダー養成塾などありますが、こちらは30分なので、こちらの発表の時間だけ抜け出すことも考えてみてください。

まとめ

日本Ruby会議2010で発表する予定の内容を紹介しました。面白そうだと思った方はぜひ参加してみてください。チケットはまだ少し残っているようです。また、チケットを譲りたいという方もいるので、まだチケットを持っていない方は連絡をとってみるのもよいかもしれません。

それでは、日本Ruby会議2010でお会いできることを楽しみにしています。

お知らせ

採用を再開しました。ソフトウェア開発者を2名募集しています。応募条件はプログラミングが好きなことだけです。学歴や年齢などは関係ありません。勤務地は東京都文京区または栃木県小山市になる予定です。詳しくは採用情報を見てください。日本Ruby会議2010の会場にはクリアコードの人が3人はいるはずなので、そのときに声をかけてもらえればその場でも説明します。

Tags: Ruby | このエントリの Delicious history 1 user | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-08-11

サーバ上でPDFやオフィス文書からテキストを抜き出す方法あれこれ

groongaなどを使って全文検索システムを作るときは、PDFやオフィス文書などからテキスト情報を抜きだして検索用インデックスを作る必要があります。Windowsでテキストを抽出するソフトウェアとしてはxdoc2txtなどがありますが、ここでは、Linuxサーバ上でテキストを抽出する方法を紹介します。

PDF

Linux上でPDFを閲覧する場合は、昔はXpdfでしたが、最近はEvinceOkularの方がよく使われているようです。どちらもPDFの処理にはXpdfからforkしたPopplerというライブラリを使っています。

popplerにはPDFからテキストを抽出するpdftotextというコマンドが付属しているため、それを利用してPDFからテキストを抽出できます。

% pdftotext hello.pdf hello.txt

これでhello.pdfのテキスト情報がhello.txtに出力されます。

Word文書・OpenDocumentFormatテキスト文書

WordやOpenOffice.org Writerで作成した文書はAbiWordでテキスト情報を抽出できます。

AbiWordは通常はGUI付きで起動しますが、オプションを指定することによりフィルタコマンドとしても利用できます。

Word文書からテキストを抽出するコマンドは以下の通りです。

% abiword --to txt --to-name hello.txt hello.doc

OpenDocumentFormatテキスト文書からテキストを抽出する場合も同じオプションです。

% abiword --to txt --to-name hello.txt hello.odt

Excelスプレッドシート・OpenDocumentFormatスプレッドシート

ExcelやOpenOffice.org Calcで作成したスプレッドシートはGnumericに付属するssconvertでCSVに変換できます。

ExcelスプレッドシートをCSVに変換するコマンドは以下の通りです。

% ssconvert --export-type Gnumeric_stf:stf_csv hello.xls hello.csv

OpenDocumentFormatスプレッドシートをCSVに変換する場合も同じオプションです。

% ssconvert --export-type Gnumeric_stf:stf_csv hello.ods hello.csv

PowerPointスライド・OpenDocumentFormatプレゼンテーション

PowerPointやOpenOffice.org Impressで作成したスライドをテキストに変換する方法はいくつかあるのですが、OpenOffice.orgを使う方法を紹介します。OpenOffice.orgを使う方法はWordやExcelの時も使えますが、事前に準備が必要なので、多少面倒です。

OpenOffice.orgを使って変換する方法にはOpenOffice.orgを変換サーバとして使う方法とコマンドとして使う方法がありますが、ここではコマンドとして使う方法を紹介します。コマンドとして使う場合もいろいろやり方がありますが、今回は、まずPDFに変換し、PDFからは上述のPopplerを使って変換することにします。

まず、以下の内容の~/.openoffice.org/3/user/basic/Standard/Export.xbaを作ります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Export" script:language="StarBasic">
sub WritePDF(url as string)
dim document   as object
dim dispatcher as object
document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService(&quot;com.sun.star.frame.DispatchHelper&quot;)

dim args1(1) as new com.sun.star.beans.PropertyValue
args1(0).Name = &quot;URL&quot;
args1(0).Value = url
args1(1).Name = &quot;FilterName&quot;
args1(1).Value = &quot;writer_pdf_Export&quot;

dispatcher.executeDispatch(document, &quot;.uno:ExportDirectToPDF&quot;, &quot;&quot;, 0, args1())

document.close(true)

end sub
</script:module>

そして、同じディレクトリにある~/.openoffice.org/3/user/basic/Standard/script.xlbに登録して、以下のような内容にします。<library:element>のところを追加しています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
<library:library xmlns:library="http://openoffice.org/2000/library" library:name="Standard" library:readonly="false" library:passwordprotected="false">
 <library:element library:name="Export"/>
</library:library>

これで準備ができました。以下のようにスライドをPDFに変換できます。

% ooffice -headless hello.ppt 'macro:///Standard.Export.WritePDF("file:///tmp/hello.pdf")'

PowerPointのファイル以外でもWord文書などOpenOffice.orgで開けるファイルを指定すれば、それらもPDFに変換できます。

以前はXがない状態では動かないため、Xvfbなど仮想的なXサーバを使ったものですが、最近のOpenOffice.orgはXがなくても動作するようです。すばらしい。

PDFに変換したら後は上述の方法でテキストを抽出できますね。

まとめ

Linuxサーバ上でPDFやオフィス文書からテキストを抽出する方法を紹介しました。以前はwvTextやxlhtml、ppthtmlなどを使うことが多かったでしょうが、最近はもっと精度よくテキストを抽出できるようになっているので、ここで紹介したツールも試してみてはいかがでしょうか。

2010-08-02

エキスパートPythonプログラミング

数ヶ月前、すでにPythonを知っている人向けのPythonの本が出版されました。オリジナルは2008年に海外で出版されたもので、これはその翻訳です。

内容

さらにステップアップしたいPython開発者向けの内容なので、構文の説明など初級者用の内容はなく*1、どうやってPythonを使うのがよいかを書いてあります。プログラミングだけではなく、開発全体を意識しているのが実務的といえます。パッケージを作り方を説明するところでも、単にEggを作るだけではなく、PyPIに登録するところまで説明しています。他にもよい名前について1章使ったり、テスト駆動開発に1章使ったりと、しっかりと大事な話題はおさえています。ドキュメントについての章があるのもPythonらしいですね。

ただし、幅広く扱っている分、少し物足りない部分もあります。例えば、テスト駆動開発の部分は標準添付のunittestよりもnoseの方により重みをおいて説明した方がより実務的でしょう。

一番有用なのは付録のPython 2とPython 3のUnicode文字列についてまとめたところかもしれません。この部分は日本語版用に翻訳者たちが書き下ろしたもので、マルチバイト文字列を扱う機会の多い日本のPython開発者には特に有用でしょう。Python 2とPython 3は互換性がないため、文字列の扱いを移行するときにこの付録が役に立つことでしょう。

また、オリジナルが出版されてから現在までにPython界隈の状況も変わってきていますが、それについて訳注で補足されているのもうれしいところでしょう。新しく書くコードでは最新の状況にあわせたコードにしたいものです。

まとめ

エキスパートPythonプログラミングの内容を簡単に紹介しました。

エキスパートPythonプログラミングではまったく触れられていませんが、単体テストフレームワークはunittestやnoseよりもPikzieがオススメです。

*1  むしろ最初にインストール方法があるのが違和感。ただ、MinGWなどもインストールするように書いてあるので、初心者用ではない。

2010-07-20

Rails 3.0 beta4でDeviseを使ってOpenID認証

とあるRails 3を使っているたいやき用のCMSでDeviseを使ってOpenID認証をするようにしたので、そのやり方を紹介します。RubyはRuby 1.9.2 RC2も出ていますが、今回はRuby 1.9.1を使います。

Deviseとは

DeviseRackベースの認証システムです。バックエンドにWardenを利用しているため、Basic認証やOpenID、OAuthなど認証方法を切り替えることができます。

ただ、以下の説明を読んでみてもらってもわかる通り、動き出すまでにそこそこの作業が必要になります。機能は豊富なので、動き出したらカスタマイズしてアプリケーションの要求に合わせていくことができるでしょう。日本語での情報もあまりありませんが、探せばいくつかはあるので、試してみてはいかがでしょうか。

とはいえ、今回はDeviseのデフォルトの認証方法ではなく、OpenIDでのみ認証することにします。また、未登録のユーザがログインしようとしたときは自動的に新規ユーザを作成することにします。このようにも使えるという例ということで読むとよいかもしれません。

インストール

まず、Rails 3.0 beta4をインストールします。

% sudo gem1.9.1 install rails --pre

サンプル用のアプリケーションを作ります。

% ruby1.9.1 rails new taiyaki
% cd taiyaki

次にDeviseをインストールします。

% sudo gem1.9.1 install devise --version=1.1.rc2

インストールしたDeviseを利用するため、以下のようにGemfileに追記します。

Gemfile:

gem 'devise', "1.1.rc2"

アプリケーションにDeviseが動作するために必要なファイルをインストールします。config/initializers/devise.rbなど主に設定ファイルです。

% ruby1.9.1 script/rails generate devise:install

いくつかは手動で設定する必要があります。それぞれ以下の通りです。

Deviseはパスワードの再設定をする機能もあり、そのときはユーザにメールを送信します。そのような機能を使うときはActionMailerのURL生成オプションを設定する必要があります。例えば、開発時のホスト情報を設定する場合は以下のようになります。

config/environments/development.rb:

config.action_mailer.default_url_options = {:host => 'localhost:3000'}

Deviseはリダイレクト先のURLを生成するときなどにデフォルトではroot_pathを使うので、rootパスへのマッピングを追加します。以下の例ではwelcome#indexを指定しているので、後でWelcomeControllerを作ります。

config/routes.rb:

root :to => "welcome#index"

Deviseはnoticeとalertのflashを設定するので、レイアウトに追加しておくとよいでしょう。例えば、以下のようにyieldの前に追加します。

app/views/layouts/application.html.erb:

<%# ... %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

<%= yield %>
<%# ... %>

作成

これでインストールは完了したので、コントローラーやモデルを作成します。

まず、ユーザー用のモデルを作成します。

% ruby1.9.1 script/rails generate devise User

このとき生成されるスキーマは、以下のようにデータベース上にパスワードのダイジェストなどの情報を持ち、それを利用して認証することになります。

db/migrate/XXXX_devise_create_users.rb:

class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      t.recoverable
      t.rememberable
      t.trackable

      # t.confirmable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both

      # t.token_authenticatable

      t.timestamps
    end

    add_index :users, :email,                :unique => true
    add_index :users, :reset_password_token, :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    # add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

しかし、今回は自分では認証情報を持たずにOpenIDで認証するので、データベース上に認証情報を持たないようにします。代わりにOpenID用のカラムを追加します。

db/migrate/XXXX_devise_create_users.rb(変更後):

class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.string :email
      t.string :nickname
      t.string :identity_url
      t.string :fullname
      t.string :birth_date
      t.integer :gender
      t.string :postcode
      t.string :country
      t.string :language
      t.string :timezone

      t.rememberable
      t.trackable

      t.confirmable
      t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
      t.token_authenticatable

      t.timestamps
    end

    add_index :users, :identity_url,         :unique => true
    add_index :users, :email,                :unique => true
    add_index :users, :confirmation_token,   :unique => true
    add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

変更したらスキーマを反映させます。

% rake1.9.1 db:migrate

モデルのコードにもデータベースで認証するためのコードが入っています。今回は必要のないユーザ登録用の:registerableオプションやパスワードの入力チェックなどをする:validatableオプションなどは外します。password_required?メソッドをオーバーライドしているのは、OpenIDで認証するためパスワードが必要がないからです。

app/models/user.rb:

class User < ActiveRecord::Base
  ...
  # devise :database_authenticatable, :registerable,
  #        :recoverable, :rememberable, :trackable, :validatable
  devise :database_authenticatable, :rememberable, :trackable
  ...

  def password_required?
    false
  end
end

Deviseではログイン画面などデフォルトのビューも提供してくれますが、今回はOpenIDを使った認証にするためビューをカスタマイズします。ビューをカスタマイズする場合は、コントローラーごとカスタマイズする方法と、ビューだけカスタマイズする方法がありますが、今回はコントローラーごとカスタマイズする方法にします。

コントローラーをカスタマイズするにはconfig/routes.rbに追加されたdevise_for:controllersオプションを指定します。以下のように指定するとUsers::SessionsControllerコントローラーを使います。

config/routes.rb:

devise_for(:users,
           :controllers => {:sessions => "users/sessions"})

コントローラーを作成します。

% ruby1.9.1 script/rails generate controller Users::Sessions

コントローラーをカスタマイズする場合は、ApplicationControllerではなくDevise::SessionsControllerを継承します。

app/controllers/users/sessions_controller.rb:

class Users::SessionsController < Devise::SessionsController
end

ログインフォームではOpenID用の識別子を入力してもらうようにします。

app/views/users/sessions/new.html.erb:

<%= form_for(resource,
             :as => resource_name,
             :url => session_path(resource_name)) do |f| %>
  <p>
    <label for="openid_identifier" >OpenID URL:</label>
    <%= text_field_tag :openid_identifier %>
  </p>
  <p><%= f.label :remember_me %> <%= f.check_box :remember_me %></p>
  <p><%= f.submit "Login" %></p>
<% end %>

あとは、トップページを準備すれば画面を確認することができます。

トップページ用のコントローラーを生成します。

% ruby1.9.1 script/rails generate controller welcome index
% rm public/index.html

トップページではログインページに移動できるようにします。ログイン時はログイン中のユーザ情報を表示します。

app/views/welcome/index.html.erb:

<h1>Welcome#index</h1>

<% if user_signed_in? %>
  <p>ようこそ<%= current_user.nickname %>さん</p>
  <%= link_to("ログアウト", destroy_user_session_path) %>
<% else %>
  <%= link_to("ログイン", new_user_session_path) %>
<% end %>

サーバを起動します。

% ruby1.9.1 script/rails server

http://localhost:3000/にアクセスすると以下のような画面になります。

トップページ

ログインページに行くと以下のようなフォームになります。

ログインフォーム

OpenID対応

それではOpenIDに対応します。DeviseからOpenIDを使うために、warden-openidを使います。

% sudo gem1.9.1 install warden-openid

Gemfileにも追記します。

Gemfile:

gem 'warden-openid'

WargenでOpenIDを使うようにします。

config/initializers/devise.rb:

Devise.setup do |config|
  ...
  config.warden do |manager|
    manager.default_strategies(:openid, :scope => :user)
  end
end

OpenIDの設定をします。warden-openidではOpenIDの認証が成功した時にコールバックが実行され、そこで認証情報に対応したアプリケーション用のユーザを返すことになります。今回は、ここで、ユーザが存在しない場合は自動的に新規ユーザを作成することにします。

config/initializers/openid.rb:

Rails.application.config.middleware.insert(Warden::Manager, Rack::OpenID)

Warden::OpenID.configure do |config|
  config.required_fields = User.required_open_id_fields
  config.optional_fields = User.optional_open_id_fields
  config.user_finder do |response|
    user = User.find_by_identity_url(response.identity_url)
    if user.nil?
      user = User.new
      user.extract_open_id_values(response)
      unless user.save
        message = "failed to create user: "
        message << "#{users.errors.full_messages.inspect}: "
        message << user.inspect
        Rails.logger.error(message)
        user = nil
      end
    end
    user
  end
end

OpenIDの情報とアプリケーションのユーザ情報をマッピングする処理はモデルで行います。

app/models/user.rb:

class User < ActiveRecord::Base
  REQUIRED_FIELDS = {
    :nickname => "nickname",
  }

  OPTIONAL_FIELDS = {
    :email => "email",
    :fullname => "fullname",
    :birth_date => "dob",
    :gender => "gender",
    :postcode => "postcode",
    :country => "country",
    :language => "language",
    :timezone => "timezone"
  }

  class << self
    def required_open_id_fields
      REQUIRED_FIELDS.values
    end

    def optional_open_id_fields
      OPTIONAL_FIELDS.values
    end
  end

  def password_required?
    false
  end

  def extract_open_id_values(response)
    profile_data = {}
    [OpenID::SReg::Response, OpenID::AX::FetchResponse].each do |response_class|
      data_response = response_class.from_success_response(response)
      profile_data.merge!(data_response.data) if data_response
    end
    [REQUIRED_FIELDS, OPTIONAL_FIELDS].each do |fields|
      fields.each do |model_key, profile_key|
        unless profile_data[profile_key].blank?
          self.send("#{model_key}=", profile_data[profile_key])
        end
      end
    end
    self.identity_url = response.identity_url
    self.nickname ||= identity_url
  end
end

一応、ニックネーム情報は欲しいとリクエストしますが、もらえなくてもなんとなく動くようになっています。この状態でログインページにOpenID識別子を入力して、認証に成功するとアプリケーションにログインする事ができます。

ログイン成功

ただし、現在リリースされているruby-openidはRuby 1.9のEncodingに対応していないため、認証中にASCII以外の文字列を含むページにアクセスすることになると失敗します。これを修正する方法はRuby 1.9.1 supportで報告済みですが、まだ取り込まれていません。

まとめ

Rails 3.0 beta4でDeviseを使ってOpenID認証する方法を紹介しました。Rails 3で認証まわりはどうしようか、と考えていている人は試してみるとよいかもしれません。ただ、betaやrcのものを使っているので、これから使い方は変わっていく可能性が高いと考えられます。注意してください。

そういえば、トップページにある会社紹介資料PDFを更新しました。エンジニア紹介ページなどが更新されています。

Tags: Ruby | このエントリの Delicious history 10 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-07-13

ActiveLdap 1.2.2 - Rails 2.3.8対応

RubyらしいAPIでLDAPのエントリを操作できるライブラリActiveLdapの新しいバージョンがリリースされました。以下のようにgemでアップデートできます。

% sudo gem install activeldap

ActiveLdapについてはこのあたりを見てください。

今回のリリースではRuby on Railsの最新安定版2.3.8に対応しました。ActiveLdapは国際化対応のためにRuby-GetText-Packageを使っています。そのため、ActiveLdapをRailsで使う場合にlocale_railsと一緒に使っている場合も多いでしょう。しかし、locale_railsの最新版はRails 2.3.8に対応していないので、locale_railsを利用している場合はアップデートするかどうかよく検討してください。(locale_railsのリポジトリ上では2.3.8に対応しているので、locale_railsのリリース版ではなくて未リリースのものを利用するのも対応策の1つです。)

LDAPといえば、日本Ruby会議2010では「Rubyで扱うLDAPのススメ」という企画があります。[ANN]RubyKaigi2010 企画 "Ruby で扱う LDAP のススメ" にご協力頂ける方を募集しています - tashenの日記ということなので、ぜひ、ご協力をお願いします。

Tags: Ruby | このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-07-05

第4期最終日

クリアコードは6月が期末なので今日が第4期の最終日になります。

今期になって初めて社員が増えました。2名増えたのでクリアコードは全員で7名になりました。

また、今期も前期と同様にいろいろなイベントに発表者として参加し(トップページにリストがあります)、これまで以上にたくさんの方とお話しすることができました。

業務でも継続してフリーソフトウェアの開発に関わってきました。いくつかはククログでも紹介してきました。

明日から第5期になります。これからもクリアコードをよろしくおねがいします。

このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-06-30

最後の行から順番に読み込む小さなRubyのクラス

Muninのプラグインを作るときなど、大きなサイズのログファイルを解析する必要がたまにありますよね。そんなとき、ファイルの先頭から処理をしていくとファイルサイズが増加するにしたがって処理時間も増えていってしまいます。Muninのプラグインの場合は最近5分間のデータだけあれば十分なので、ファイルの先頭からではなく、最後から処理する方が効率的です。最後から処理すると、ファイルサイズが大きくなっても処理時間にはほとんど影響がありません。

ということで、ファイルの最後から1行ずつ読み込む小さなRubyのクラスを作りました。groongaのリポジトリに入っているので、groongaと同じライセンスで利用できます。

class ReverseLineReader
  def initialize(io)
    @io = io
    @io.seek(0, IO::SEEK_END)
    @buffer = ""
    @data = ""
  end

  def each
    separator = $/
    separator_length = separator.length
    while read_to_buffer
      loop do
        index = @buffer.rindex(separator, @buffer.length - 1 - separator_length)
        break if index.nil? or index.zero?
        last_line = @buffer.slice!((index + separator_length)..-1)
        yield(last_line)
      end
    end
    yield(@buffer) unless @buffer.empty?
  end

  private
  BYTES_PER_READ = 4096
  def read
    position = @io.pos
    if position < BYTES_PER_READ
      bytes_per_read = position
    else
      bytes_per_read = BYTES_PER_READ
    end

    if bytes_per_read.zero?
      @data.replace("")
    else
      @io.seek(-bytes_per_read, IO::SEEK_CUR)
      @io.read(bytes_per_read, @data)
      @io.seek(-bytes_per_read, IO::SEEK_CUR)
    end

    @data
  end

  def read_to_buffer
    data = read
    if data.empty?
      false
    else
      @buffer.insert(0, data)
      true
    end
  end
end

以下のように使います。

File.open("/var/log/groonga/query.log", "r") do |file|
  ReverseLineReader.new(file).each do |line|
    break if no_more_need?(line)
    # ...
  end
end

ログファイルから直近のログだけを取り出して処理したいときなどに利用してみてはいかがでしょうか。

Tags: Ruby | このエントリの Delicious history 2 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-06-23

2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|