新年度

 5月になって新年度というのもなんですが、まだお知らせしていなかったので。

  • 東京工科大学を退職しました。4年間お世話になりました。
  • 東洋大学で非常勤講師を始めました。経営学部の「情報メディア論A・B」を担当させていただいています。
  • 引き続き慶應メディアコムにも出入りさせていただいています。今期は春1コマ、秋2コマです。
  • いわゆる専業非常勤講師というやつです。しばらくは貯金で食いつながなければ。

My Open Archive を自分で作る

 すごいブックマーク数だなあ。

 「このようなドキュメント共有サービスも図書館システムでうまく使えないかなあ」と思っていて、My Open Archiveを使うことも考えてみたのですが、

 というわけで、My Open Archiveは通さず、直接Scribdを呼び出すことにしました。Project Next-L Enjuにアップロードしたファイルを図書館の資料として登録するとともに、Scribdにも同時に送信します。登録された資料はこんな感じで表示されます。既定では図書館員の権限を持つユーザのみファイルのアップロードを可能にしてありますが、もちろん一般ユーザに許可することも可能です。

 あとは関連図書の情報をファイルの本文から自動で取ってきて、いっしょに表示できるようになるといいんだけどね。

Rails 2.3.2でロックの挙動が変わっている

 Ruby on Rails 2.3.2がリリースされたので、さっそくインストールしました。自分の作っているアプリケーションでテストを実行してみると、2.3.1のときに出ていなかった"ActiveRecord::StaleObjectError: Attempted to delete a stale object"というエラーが出てきました。この ActiveRecord::StaleObjectError は lock_version を使ったロック機能によるものですが、いつもは更新時に起きるのに、今回はどうもレコードの削除時に起きているようです。また、エラーを出しているのはいずれもこのロックに加えて、関連先のモデルに対して counter_cache と":dependent => :destroy"による連鎖削除を指定しているモデルのようです。

 原因を調べるために、新しく以下のふたつのモデルを作りました。なお、環境は Mac OS X 10.5.6 と Ruby 1.8.7, データベースには SQLite を使っています。

  • app/models/user.rb
class User < ActiveRecord::Base
  has_many :blogs, :dependent => :destroy
end
  • app/models/blog.rb
class Blog < ActiveRecord::Base
  belongs_to :user, :counter_cache => true
end

 migration は以下のような定義です。

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.integer :lock_version, :default => 0
      t.integer :blogs_count, :default => 0

      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

class CreateBlogs < ActiveRecord::Migration
  def self.up
    create_table :blogs do |t|
      t.references :user
      t.string :title

      t.timestamps
    end
  end

  def self.down
    drop_table :blogs
  end
end

 migration を実行した後、console でレコードを操作してみます。2.3.2だとこうなります。

mac:blog nabeta$ script/console
Loading development environment (Rails 2.3.2)
>> u = User.create(:name => 'nabeta') # Userを作成する
=> #<User id: 1, name: "nabeta", lock_version: 0, blogs_count: 0, created_at: "2009-03-16 15:09:40", updated_at: "2009-03-16 15:09:40">
>> u.blogs
=> []
>> u.blogs.create(:title => 'my blog') # Userに関連づけられたBlogを作成する
=> #<Blog id: 1, user_id: 1, title: "my blog", created_at: "2009-03-16 15:10:00", updated_at: "2009-03-16 15:10:00">
>> u
=> #<User id: 1, name: "nabeta", lock_version: 0, blogs_count: 0, created_at: "2009-03-16 15:09:40", updated_at: "2009-03-16 15:09:40">
>> u.reload
=> #<User id: 1, name: "nabeta", lock_version: 1, blogs_count: 1, created_at: "2009-03-16 15:09:40", updated_at: "2009-03-16 15:09:40">
>> u.destroy # Userを削除しようとする。以下のとおり失敗する
ActiveRecord::StaleObjectError: Attempted to delete a stale object
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/locking/optimistic.rb:127:in `destroy_without_callbacks'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/callbacks.rb:337:in `destroy_without_transactions'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:229:in `send'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:229:in `with_transaction_returning_status'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:182:in `transaction'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:228:in `with_transaction_returning_status'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:192:in `destroy'
	from (irb):6
>> 

 User を削除しようとすると、問題の ActiveRecord::StaleObjectError が発生します。2.3.1だとこうなります。

mac:blog nabeta$ script/console
Loading development environment (Rails 2.3.1)
>> u = User.create(:name => 'nabeta') # Userを作成する
=> #<User id: 1, name: "nabeta", lock_version: 0, blogs_count: 0, created_at: "2009-03-16 15:21:23", updated_at: "2009-03-16 15:21:23">
>> u.blogs
=> []
>> u.blogs.create(:title => 'my blog') # Userに関連づけられたBlogを作成する
=> #<Blog id: 1, user_id: 1, title: "my blog", created_at: "2009-03-16 15:21:29", updated_at: "2009-03-16 15:21:29">
>> u
=> #<User id: 1, name: "nabeta", lock_version: 0, blogs_count: 0, created_at: "2009-03-16 15:21:23", updated_at: "2009-03-16 15:21:23">
>> u.reload
=> #<User id: 1, name: "nabeta", lock_version: 1, blogs_count: 1, created_at: "2009-03-16 15:21:23", updated_at: "2009-03-16 15:21:23">
>> u.destroy # Userを削除しようとする。以下のとおり成功する
=> #<User id: 1, name: "nabeta", lock_version: 1, blogs_count: 1, created_at: "2009-03-16 15:21:23", updated_at: "2009-03-16 15:21:23">
>> u.blogs
=> [#<Blog id: 1, user_id: 1, title: "my blog", created_at: "2009-03-16 15:21:29", updated_at: "2009-03-16 15:21:29">]
>> u.reload
ActiveRecord::RecordNotFound: Couldn't find User with ID=1
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/base.rb:1595:in `find_one'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/base.rb:1578:in `find_from_ids'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/base.rb:616:in `find'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/base.rb:2695:in `reload_without_dirty'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/dirty.rb:94:in `reload_without_autosave_associations'
	from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/lib/active_record/autosave_association.rb:191:in `reload'
	from (irb):7
>> Blog.find(:all)
=> []
>> 

 User はエラーを起こさずに削除されています。

 もう少し2.3.2の動作やソースコードを調べてみると、最近 ActiveRecord でロックを扱っている部分に変更があったことがわかりました。また、上記の例では counter_cache と ":dependent => :destroy"による連鎖削除の両方を指定しているときに ActiveRecord::StaleObjectError が発生し、どちらか片方だけでは発生しないこともわかりました。

ざまみろ?

ざまみろ、関東!

次世代は永遠にやってくるのだ、幸にしろ不幸にしろ、というOPAC考え話。

 ぼくは関東の人間でも関西の人間でもないので、このへんのライバル(?)意識があんまりぴんとこないのですが、おもしろそうな企画があったら京都くらいだったら飛んでいくので、もっともっと刺激的な、「関西はすげーだろ」と悔しがらせるような企画をたてて、「うらやましーだろ」と大々的に宣伝してください。実際、去年は「猫の司書さん」の発表図書館退屈男さんのレポート)のためだけに、夜行列車で京都まで0泊2日で往復してきました…いや、ほんとうはこのため「だけ」ではなく、廃止直前の急行「銀河」に乗るという目的もあったのですが、このときの話はこのためだけに行く価値のあるものでした。

 ところで、どなたか来年のcode4lib conferenceにいっしょに行きませんか? もちろん Project Next-L の発表をするという前提で。それまでには「ざまみろ、アメリカ!」と誰か書いてくれるくらいのものを作っておきます。

ビジネスモデル説明会

 3月9日の説明会は無事終わりました。伝えたいことは伝えたつもりなのですが、質疑応答の時間にベンダーさんからの質問が1件もなかった、という厳しい現実がありました。

 ちなみに、「Webベースという時点で検討の対象外」とは複数の図書館員から直接言われたことがあります。操作性を考えればもっともな話だとは思います。

「ふたつのサイト」のお詫びと訂正

 前回の日記について、非常に寝ぼけたことを書いていました。
 http://wwwsoc.nii.ac.jp/http://www.jla.or.jp/ の間のリダイレクトはDNSだけでできますが、http://wwwsoc.nii.ac.jp/jla/ のように転送先がディレクトリを含む場合はDNSだけではだめで、転送元にmod_rewriteや転送用のWebページなどによるリダイレクトの設定が必要です*1。もし http://www.jla.or.jp/ から http://wwwsoc.nii.ac.jp/jla/ にリダイレクトさせる場合、www.jla.or.jp のためのWebサーバが必要になるため、「www.jla.or.jpの実体を解約してwwwsoc.nii.ac.jpにコンテンツを集約し、DNSでリダイレクト」という選択はできなくなります。

 読者や日本図書館協会のみなさま、たいへん失礼いたしました。
 お詫びとして、9日(月)の説明会の際に、現在のホームページの事情を協会の方に聞いてくるとともに、ブログも含めた話について提案してこようと思います。

*1:wwwsoc.nii.ac.jp の設定を変更しないことを前提としています。

日本図書館協会のふたつのサイト

 会員でもないのになぜこんなことを考えているのかわからないけど、もうちょっとだけおつきあい下さい。

 おなじみの日本図書館協会のホームページは、日本における図書館や図書館員の現状を忠実に体現している、非常によくできているものです。ALACILIP韓国KLA中国図書館学会と比較しても、そのでき映えは群を抜いていると言ってよいでしょう*1

 さて、この日本図書館協会のホームページは、

のふたつのサイトに存在します。どちらのURLでも同じ内容のページが表示されます。

 さらに、すでにお気づきの方もいらっしゃると思いますが、この状態はかなり昔、まだ nii.ac.jp が nacsis.ac.jp だったころから続いています。

 実はつい先ほどまで、「これは実体はひとつのサイトで、DNSで両方からのアクセスをそこに向けているんだろう」と思っていました。(2009年3月1日追記: これは今回の場合は不可能ですしかし、実際にDNSレコードを見てみると、

tanabe@kamata:~$ host www.jla.or.jp
www.jla.or.jp has address 210.128.238.26
www.jla.or.jp mail is handled by 10 bh0.iij.ad.jp.
tanabe@kamata:~$ host wwwsoc.nii.ac.jp
wwwsoc.nii.ac.jp has address 157.1.35.201
wwwsoc.nii.ac.jp mail is handled by 5 wwwsoc.nii.ac.jp.

このふたつはIIJとNII、全く別のネットワークに存在するようです。また、それぞれのサーバにあるトップページについてHTTPのヘッダを見てみると、

tanabe@kamata:~$ telnet www.jla.or.jp 80
Trying 210.128.238.26...
Connected to www.jla.or.jp.
Escape character is '^]'.
HEAD / HTTP/1.0

HTTP/1.1 200 OK
Date: Fri, 27 Feb 2009 10:43:54 GMT
Server: Apache
Last-Modified: Wed, 18 Feb 2009 02:00:40 GMT
ETag: "1000000006c8e-175b-46327c95c8200"
Accept-Ranges: bytes
Content-Length: 5979
Connection: close
Content-Type: text/html

Connection closed by foreign host.
tanabe@kamata:~$ telnet wwwsoc.nii.ac.jp 80
Trying 157.1.35.201...
Connected to wwwsoc.nii.ac.jp.
Escape character is '^]'.
HEAD /jla/ HTTP/1.0

HTTP/1.1 200 OK
Date: Fri, 27 Feb 2009 10:44:16 GMT
Server: Apache
Last-Modified: Wed, 18 Feb 2009 02:07:40 GMT
ETag: "60f26-1709-e2653300"
Accept-Ranges: bytes
Content-Length: 5897
Connection: close
Content-Type: text/html

Connection closed by foreign host.

Last-Modified(最終更新時刻)が7分も違いますし、Content-Lengthの値も異なっています。実際のファイルを見ると、改行コードだけが異なっていました。

 つまり、ww.jla.or.jp と wwwsoc.nii.ac.jp の各サーバ上にあるファイルは別々に存在するものであり*2、www.jla.or.jp と wwwsoc.nii.ac.jp のどちらか片方(時刻からすればNII)がミラーサイトとして機能している状態だということです。

 どのようにしてふたつのサイトのファイルの同期をとっているのかは、これだけではわかりません。NIIのサーバは ssh が使えるので、rsync と併用して*3同期をかけているのかもしれませんし、単にFTPクライアントを使って手動で同期させているのかもしれません。

 このふたつは最初からこのようなミラーリングを行う関係ではなかったようで、Internet Archive によれば、www.jla.or.jp と wwwsoc.nacsis.ac.jp で異なるコンテンツが存在していた時期があったようです。このページでアーカイブされているのは、www.jla.or.jp が1999年、リンク先の wwwsoc.nacsis.ac.jp が1998年のものなので当時の関係を正確に表しているものではないのですが、それでも「ふたつのサイトに異なるコンテンツが存在していた」という状況は把握できます。

 興味があるのは、なぜこのようなミラーリングの状態が長らく続いているのか、ということです。もちろんミラーリング自体が悪いわけではありませんが、大量のアクセスが集中するようなサイトではなさそうですし、IIJやNIIの回線がそれほど頻繁に停止することもないと思いますので、負荷分散やバックアップとしてのミラーリングの意味は非常に薄いように思います。なぜこのように「ふたつのサイト」が存在しているのか、なにか歴史的な事情があるのか、ご存知の方がいらっしゃいましたら教えていただけないでしょうか。もちろん、事実の誤認がありましたら遠慮なくご指摘ください。

 ここからは仮定の話です。

 もし今の www.jla.or.jp のファイルがあるWebサーバを維持するのにお金がかかっていて、ミラーリングをする必要性が薄いと判断されるのであれば、Webサーバを解約して(jla.or.jp のドメインはそのまま保持)、そのお金でNIIのサーバにブログでも入れて、コンテンツを移行してみるというのはいかがでしょうか。たとえばMovableTypeNIIのサーバでも動作実績がありますし、日本図書館協会で作っているブログ(企画調査部学校図書館部会(すでにページ消滅))も集約できますし、商用版だとサポートに加えて「コミュニティ・ソリューション」なんていうウケのよさそうな機能もあるので、検討してみる価値はあるのではないかと思います。
 ただし、WebについてはDNSで jla.or.jp をNIIのサーバに向けるとしても(2009年3月1日追記: これは今回の場合は不可能です、NIIで面倒を見てくれない電子メールをどうするかという問題が残ります。さすがにフリーメールを使うわけにはいかないでしょうから、結局現在のサーバをそのまま使わざるをえない場合も出てくるかもしれません。現在のサービスの契約内容を知らずに書いているので、ここであまり細かく考えても意味がないのですけどね。

 あと、現在のすばらしいホームページについて、事務局の人手が足りていないのは仕方がないとしても、会員になら詳しい人はそれなりにいると思うのですよ。「こういうことをやってみたいけど、どうやっていいのかわかりません。お知恵を貸してください、お礼をさせていただきますので」といった形で協力を呼びかけてみればよいのに、と思います*4。もちろん、協力してくれた方は大いに表彰してあげることが必要です。

 えっ、せっかく図書館や図書館員の現状をここまで忠実かつ正直に体現しているホームページなのにもったいない? うーん、たしかにそれはそうかも。

*1:丸山さんのブログの記事も参照。

*2:仮に何らかの方法でネットワーク上でファイルを共有しようとすれば、コンピュータの時刻を同期させなければ動作に支障が出るはずです。

*3:でも rsync って改行コードの変換なんてできるんだっけ? マニュアルを読んだら、ファイル名については異なる文字コードへの変換ができるみたいだけど。

*4:こういうところで、若手の会員を集めることが重要になってくるのです。