2006-10-12 (木)
■ 人気の日記プラグインを更新
3日連続で tDiary ネタ。
日記の中で、よくブックマークされた日記のリストを表示する、「人気の日記プラグイン」を作った。 といっても、はてなブックマーク - ○○ の注目エントリーで配信している RSS を変換しているだけ。
以前公開していた 人気の日記プラグイン (hatena_bookmark.rb) を、はてなブックマークカウンタープラグイン (hatena_bookmark_counter.rb) と、この人気の日記プラグイン (hatena_bookmark_entry) に分割したようなもの。
ダウンロード
trac の hatena_bookmark_entry.rbから、Original Formatをクリックするとダウンロードできます。 プラグインを有効にしたあとに、人気の日記を表示したい場所に、
<h3>人気の日記</h3> <%= hatena_bookmark_entry %>
といった具合に記述してください。
その他
- 負荷削減のため、一度取得した人気の日記リストはキャッシュされます(cache_dir/hatena/bookmark.dat)
- 人気の日記リストは、日記を更新したタイミングで取得します。一番最初は、日記を更新するまで日記リストが表示されません。
2006-10-11 (水)
■ Ruby だけで tDiary を動かす(小ネタ)
tDiary 繋がりで小ネタ。 ローカルな環境で tDiary を動かしたいけど、 Apache などの Web サーバを動かすのが面倒な場合がある。 そんなときに、 Ruby (に付属のWEBrick)だけで tDiary を動かす方法を試してみた。 ちょっとしたプラグインの開発などに使えるかも。
index.rb, update.rb のリネーム
tDiary のファイルをダウンロードし、 index.rb と update.rb のファイル名を変更する。
$ mv index.rb index.cgi $ mv update.rb update.cgi
tdiary.conf の修正
tdiary.conf をエディタで開いて、 @update の行を以下のように設定する。
@update = 'update.cgi'
@data_path の設定も忘れずに。
server.rb の作成
以下の内容で server.rb というファイルを作る。
#!/usr/bin/env ruby
require 'webrick'
config = {
:DocumentRoot => '.',
# :BindAddress => '127.0.0.1',
:Port => 3001,
}
server = WEBrick::HTTPServer.new(config)
server.mount('/index.cgi', WEBrick::HTTPServlet::CGIHandler, 'index.cgi')
server.mount('/update.cgi', WEBrick::HTTPServlet::CGIHandler, 'update.cgi')
trap('INT') { server.shutdown }
trap('TERM') { server.shutdown }
server.start
サーバの起動
$ ruby server.rb
http://サーバ名:3001/ にアクセスすると、日記のトップページが表示される。
サーバの停止
CTRL + C でサーバが停止する。
注意
認証機能も無いし、ログも残らないし、運用では使えないので注意。 あくまでローカルな環境で動かすように。
まじめにWEBrickで動かしたい場合は、tDiaryletを使うのがいいかと。
2006-10-10 (火)
■ はてなブックマークカウンタープラグイン
Rails の勉強は一回お休み。 おさかなラボさんのところに、被はてなブックマーク数を簡単に取得できる方法が書かれていた。 人気の日記プラグインを作った時は XMLRPC を使って面倒なことをやっていたのに、いつの間にかこんなに便利になっていたんだな…。
早速、 tDiary のプラグインにしてみる。 取り込んだのは以下の機能。
- ブックマーク数を画像で取得する API の公開について (2006-07-12), 自分のブログに被ブックマーク数を表示する
- はてなブックマークコメントをその場で表示するライブラリの公開 (2006-09-21), はてなブックマークコメントその場で表示ライブラリ
- はてなブックマークカウンターの提供開始について (2006-10-04)
以下、使い方など。興味のある方は使ってみてください。
ダウンロード
注: tDiary 2.1.4 以降でないと動作しません。
ソースはとりあえず trac で管理しています。 hatena_bookmark_counter.rbを開き、ページ下部のOriginal Formatからダウンロードしてください。
使い方
被ブックマーク数
プラグインをインストールすると、セクションの終わりに被ブックマーク情報が自動的に表示されます。
: 被ブックマークの一覧を表示します(はてなブックマークのサイトへ移動します)
: 「コメントをその場で表示するライブラリ」を有効にした場合のみ表示されます(後述)。
: 被ブックマーク数を表示します。ブックマークされていない場合は何も表示されません。
: 被クリップの一覧を表示します(livedoor clip のサイトへ移動します)
: livedoor clip の被クリップ数を表示します。クリップされていない場合は何も表示されません。
category_to_tag.rb プラグインをインストールしている場合は、タグの横に被ブックマーク情報を並べて表示します。
カウンター
はてなブックマークカウンターを表示する場合は、設定画面の「ヘッダ・フッタ」を開き、表示したい箇所に以下の内容を追記します。
<%= hatena_bookmark_counter %>
コメントをその場で表示
コメントをその場で表示するライブラリを使う場合には、もう少し設定が必要です。
- ライブラリ用のスタイルシートをダウンロード(先ほどと同じように、ページ下部のOriginal Formatをクリック)し、 tDiary の theme ディレクトリにコピーします。
- hatena_bookmark_counter.rb をエディタで開き、11行目を以下のように書き換えます。
@conf['hatena_bookmark_counter.comment_viewer'] = true
これで、被はてなブックマーク数の隣に、コメントをその場で表示するアイコン
が表示されるようになります。
アイコンをクリックすると、はてなブックマークのコメントが表示されます。
その他
- 「コメントをその場で表示」機能はまだ不安定のようで、よく取得に失敗します。
- section_footer.rb や 人気の日記プラグインとは機能が競合するかもしれません。
追記
livedoor clip でも同じようにクリップ数が取得できるようなので、一緒に表示するように修正しました。 ダウンロード方法などは変わりません。
■ もしかして
section_footer.rb に 1470.net へのリンクを追加 - HsbtDiary (2006-07-11)を見ていて気がついたんだけど、 section_footer.rb でも被ブックマーク数の表示に対応しているっぽい。 公開されている section_footer.rb は古いままみたいだけど。
Widget-Simple に対応されたものが公開されるのを気長に待っていようっと。
2006-10-09 (月)
■ Rails に(再)挑戦 7日目 - 一覧表示で本文の一部だけ表示 (Read More)
2日目に一覧表示 (list) と個別表示 (show) でデザインを共有したけど、今は一覧表示のときも本文を全て表示するようになってる。 これを、本文の一部だけ表示して、残りは Read More (個別表示へのリンク) とする。
まずはビューから修正する。 一覧表示 (list.rhtml) と検索結果表示 (search.rhtml) から、 _page.rhtml に対してローカル変数 short (値は true) を渡す。 _page.rhtml では、 short が true だった時に本文の一部だけを表示するようにする。
-<%= render(:partial => 'page', :collection => @pages) %>
+<%= render(:partial => 'page', :collection => @pages,
+ :locals => { :short => true }) %>
_page.rhtml では、ヘルパーの parse メソッド (NoteHelper#parse) を呼び出す時に、 short 変数を渡すように修正する。 同時に、第一引数もページ本文の文字列 (page.body.sub) から、ページオブジェクト (page) に変更した。 これは、 Read More で個別表示のリンクを表示するために、ページの id が必要なため。
updated: <%=h show_time(page.updated_on) %>
</div>
- <%= parse(page.body.sub(/^.*\n/, '')) %>
+ <%= parse(page, short) %>
</div>
ヘルパー (note_helper.rb) では、 short == true の場合に本文の一部だけを表示する。 具体的には、見出しの行(先頭が「!」で始まる行)以降を、 Read More で置き換えている。
def parse(page, short = false)
level = 3
source = page.body.sub(/^.*\n/, '')
if short
source.sub!(%r|^!.*|m) do
href = url_for(:action => 'show', :id => page.id)
"([[Read More...|#{href}]])"
end
end
html = HikiDoc.new(source, :level => level).to_html
さっきも書いたけど、 Read More は個別表示へのリンクとする。 個別表示のリンクは、 url_for(:action => 'show', :id => page.id) で取得できる。 これを、 HikiDoc スタイルのリンク文字列にしている。 例えば、 id が 1 のページだったら、以下のようになる。
[[Read More...|/note/show/1]]
これを HikiDoc で変換するとこうなる。
<a href="/note/show/1">Read More...</a>
これでリポジトリにコミット。 リビジョン16か。
少し気になるのは、ヘルパーにパース関連のロジックが集中してきたこと。 最初は Page モデルに to_html メソッドを追加しようかと思ったけど、モデルにビューの処理を書くのも変なので、ヘルパーに書いている。 デフォルトではモデルから url_for は呼べないし。
まぁ、とりあえずコントローラやヘルパーに処理を書いて動かしてしまって、後で処理の場所を移してもいいし。 こうやって少しずつ機能を追加していけるのが、 Rails のいい所かも。
■ カート
天気のいい三連休の締めくくりとして、ミニサーキットに行ってきた。 四輪、二輪、カートと順に走る姿に圧倒される。
んで、レンタルカートに初挑戦。 初心者向けのカートだったんだけど、全く乗りこなせなかった。
でも、楽しかった。 また行きたいな。
■ 秋の夜長に
何を思い立ったのか、急に Jazz が聴きたくなった。
頭と身体が疲れていて、リラックスしたがっているのかもしれない。
iTMS や Amazon でフラフラと検索していたら
ベスト・ジャズ100(オムニバス/メル・トーメ/ケイ・スター/ダイナ・ショア/ジャック・ジョーンズ/カーメン・マクレエ/アニー・ロス/スー・レイニー/ジャック・ティーガーデン/ジューン・ハットン/イリアーヌ)の評判が高かったので、勢いで注文しちゃった。
数日前に届いてからずっと聴いているんだけど、当たりだったかも。 まず、知っている曲が多いのが嬉しい。 音楽にそれほど詳しい訳じゃない(ましてや Jazz はなおさら)けど、それでも分かる曲が多数。
- Fly Me To The Moon … エヴァンゲリオンのエンディングテーマとしても有名。宇多田もカバーしてたね。
- When You Wish You Upon A Star … ディズニーの「星に願いを」。
- Over The Rainbow … これもディズニー。元はオズの魔法使いの主題歌らしい。
- Sing Sing Sing … 映画 Swing Girl のアレ。古い音源なのでモノラル録音なのが残念。
- My Favorite Things … サウンド・オブ・ミュージックの曲。ちょっと弾けたアレンジになってる。
- Don't Worry, Be Happy … 缶コーヒー Roots の CM 曲。
- Moon River … よく聴くと思ったら、「ティファニーで朝食を」の主題歌らしい。
- Can't Take My Eyes Off You … 「君の瞳に恋してる」。Tommy february もカバーしてた。
- Time After Time … シンディ・ローパーの CD で初めて聴いたなぁ…。
6枚組で3,500円だし、 Jazz の入門としては最適なのかも。 ここ数日の夜は、この CD を聞きながら Rails と戯れてる。
2006-10-08 (日)
■ Rails に(再)挑戦 6日目 - ページの検索
いよいよ RandomNote の特徴である検索に取り掛かる。 本家 RandomNote では、データを管理しているテキストを順に開いて、正規表現で検索している。 さて、どうしようか。
- Page.find(:all) して正規表現 → メモリが持たない
- 全文検索エンジン (Hyper Estraier) を使う → セットアップが大変
- データベースの全文検索機能を使う → データベースに依存する & SQLiteじゃ無理?
悩ましい…ので今は深く考えずに、データベースに対する like 検索(!)で済ますことにする。 AND検索はできないし、インデックスを張れないから処理は重いし、欠点ばかりだけどね。 これも後で考えよう。 まずは Rails での実現方法を覚えるのが先だ。
ビュー
どこから手を付けようか迷ったけど、まずは分かりやすいビューから。 検索フォームは全てのページについているから、レイアウト (app/views/layouts/note.rhtml) に手を加える。
<%= start_form_tag :action => 'search' %> <%= text_field 'search', 'word' %> <%= submit_tag 'Search' %> <%= end_form_tag %>
検索フィールド (text_field) には2つのパラメータ ('search', 'word') を渡している。 こうすると、実際に出力される HTML は以下のようになる。
<form action="/note/search" method="post"> <input id="search_word" name="search[word]" size="30" type="text" /> <input name="commit" type="submit" value="Search" /> </form>
Rails で特長的なのは name="search[word]" かな。 データをハッシュとして扱うのがポイント。
このあたりの処理は scaffold で生成されたコードを読むと参考になる。 ページを生成するときの処理 (NoteController#create) は、
@page = Page.new(params[:page])
としているし、ページを更新するときの処理 (NoteController#update) は
@page.update_attributes(params[:page])
となっている。 モデルに対してハッシュをそのまま突っ込めるようにしているのね。 豪快だ…。
さて、少し脱線したよ。 とにかく、検索フィールドに入力したデータは params[:search][:word] で取得できるみたい。 検索フィールドに前回の検索語句を表示するようにしておこう。
<% word = params[:search][:word] if params[:search] %> <%= start_form_tag :action => 'search' %> <%= text_field 'search', 'word', :value => word %> <%= submit_tag 'Search' %> <%= end_form_tag %>
コントローラに検索機能を追加
ビューを作ったので、次はコントローラ。 NoteController に search メソッドを追加する。
def search
@pages = Page.fulltext_search(params[:search][:word],
:order => 'updated_on DESC')
end
ほとんどモデルに丸投げという…。 まぁ、一覧表示 (list) と同じノリだね。
モデルに検索メソッドを追加
ってことで、ようやくメインの処理。 app/models/page.rb を修正し、モデル (Page) にクラスメソッド (Page.fulltext_search) を追加する。 といっても、 find(:all) の検索条件に body like '%検索語句%' を指定するだけ。
def Page.fulltext_search(word, options = {})
options[:conditions] = ["body like ?", "%#{word}%"]
find(:all, options)
end
さらにビュー
忘れてた。 NoteControll#search の後に呼ばれるビュー (search.rhtml) が必要。 といっても、基本的には一覧表示 (list.rhtml) と同じものが使える。 Pagenate の使い方が分からないので、とりあえずは検索結果を全て表示するか。
<%= render(:partial => 'page', :collection => @pages) %>
検索してみる
ちゃんと動いているみたい。まだ AND 検索はできないけど…。
今日もリポジトリに登録。
■ 本文中のリンク (BracketName)
簡易だけど検索機能を付けたので、次に BracketName を作る。 BracketName とは、 [[ と ]] で括った文字列のこと。 Wiki だと別ページへのリンクになるんだけど RandomNote だとその言葉を検索するリンクになるのが面白い。 以下、 RandomNote の説明文より引用。
[[ ]] で囲まれた言葉は、その言葉を検索するリンクになります。
さて、 HikiDoc を使いながらどうやって文法を拡張するかだけど、同じく HikiDoc を使っている tDiary のソース (wiki_style.rb) を参考にさせてもらった。 WikiSection#to_html のソースを抜粋。
html = HikiDoc::new( string, :level => 3, :empty_element_suffix => '>' ).to_html.strip
html.gsub!( %r!<span class="plugin">\{\{(.+?)\}\}</span>!m ) do
"<%=#{CGI.unescapeHTML($1)}%>"
end
html.gsub!( %r!<div class="plugin">\{\{(.+?)\}\}</div>!m ) do
"<p><%=#{CGI.unescapeHTML($1)}%></p>"
end
なるほど。 HikiDoc で HTML に変換した後に、正規表現で置換 (gsub!) しているのか。 同じように、ヘルパーの parse メソッドを修正する。
def parse(body)
- HikiDoc.new(body, :level => 3).to_html
+ html = HikiDoc.new(body, :level => 3).to_html
+ html.gsub!(%r|<a href="(.+?)">(.+?)</a>|m) do
+ href, text = $1, $2
+ href == text ? link_to_search(text) : Regexp.last_match
+ end
+ html
end
+
+ def link_to_search(text)
+ link_to(text, { :action => 'search', 'search[word]' => text },
+ { :post => true })
+ end
検索へのリンクを生成するロジックは、これまた RandomNote の特徴であるサイドバーの検索履歴で今後も使いそう。 なので、link_to_search という別メソッドにしておいた。 といっても、内部では link_to を呼んでいるだけなんだけど。
link_to は便利なメソッド。 アクション (:action) とパラメータ ('search[word]') を指定するだけで、必要な URL を生成してくれる。 この場合、コントローラは同じ NoteContoroller なので、 URL は
http://192.168.92.210:3000/note/search?search%5Bword%5D=test
のようになる。
これで BracketName は完成。 リポジトリに登録しておしまい。 もうリビジョン14か。
追記
…また忘れてた。 検索への呼び出しは GET じゃなくて POST を使うようにしている。 何故かというと、 RandomNote での検索は副作用(検索数のカウント)を伴うから。 クローラによって消したはずのキーワードがいつの間にか復活するという問題が出ているみたいだし。 一般的に、副作用を伴う操作は POST を使った方がいい。
ということで、コントローラ (NoteController) の search アクションを、 POST のみ受け付けるようにした。
verify :method => :post, :only => [ :create, :update, :search ],
:redirect_to => { :action => :list }
これでリビジョン15。





Before...
■1 しばた [PSUの方は2週間でやめちゃったんで、はまっているのは別のゲームだったり…。]
■2 bewaad [はじめまして。 はてなブックマークカウンタープラグインを導入したいと思っているのですが、リンクを選択しても、 >..]
■3 まちゅ [リンク先が間違っていたので修正しました。 ご指摘ありがとうございます&すみません。]