Ruby on Rails

rublog-0.2.0リリースしました。

rublog-0.2.0公開しました。変更点は、

  1. pingサーバの登録機能追加。pingサーバを独自に登録できるようにしました。
  2. コメント/トラックバックの承認可否追加。コメント・トラックバックを承認制にしました。(スパムで懲りた...)
  3. Amazonアソシエイト機能の追加。記事を書くときにASINコードを入力するだけででプロダクトレビューになります。
  4. hReviewフォーマット対応。アマゾン商品レビューの場合、hReviewフォーマットで出力します。
  5. カテゴリ・アーカイブブロック表示の追加。定番なので
  6. ブログ記事へのタグキーワード付加。これが自分でも結構楽しい。タグでの絞り込みもできます。
  7. 覚えきれないほどのバグフィックス(まだまだあるよ)

これで、ようやくカタチになってきたかなぁ。後は、コツコツとバグ取りしていきます。ダウンロードはSourceForgeまたはrublogダウンロードから。
Rubricks+rublogで構築した、本レビューサイトbookworms::本の虫もよろしかったらご覧ください。

Rubricksコンポーネントのスキーマ更新

rublogコンポーネントのバージョンアップの開発もそろそろ区切りを付けようと思っているのですが、スキーマのアップデートの方法でしばらくの間悩んでいました。なにしろ行き当たりばったりの開発なのでスキーマの変更がたくさんあって、この変更をどう処理したものかうまい方法が見つからず思案していたわけなんです。Railsのスキーマ管理のやり方としてはActiveRecord::Migrationを使用するのが素直なことはわかっていたのですがこれだとアプリケーション全体のバージョンに影響が出てしまいます。かといって別途SQLを用意してユーザにコマンドを打たせるのも今ひとつスマートじゃない気がして。

hReviewをどうブログツールに実装するか

早速、hReviewフォーマットで「砂をつかんで立ち上がれ(中島らも)」作成してみました。自作なのでどんどん試していけるのがいいですね。構造としては

<div class="hreview">
  <div class="summary"> ブログ記事タイトル </div>
  <div class="descripction"> ブログ記事本文 </div>
  <div class="product">
    <dl class="item">
      <dt class="fn url">  
       <a href="....">本のタイトル</a>
      </dt>
      <dd>著者</dd>
      <dd>出版社</dd>
      <dd>値段</dd>
    </dl>
  </div>
  <span class="reviewed vcard">
    <span class="fn">hippos</span>
  </span>
  <abbr class="dtreviewed" title="2006-11-29T14:26:57">
   2006-11-29
  </abbr>
  <a class="permalink" href="..." rel="selfbookmark">permalink</a>
</div>

悩んだのは画像の扱いで、item属性として画像を表示するかどうかなのですが、見栄えの好みとして本文中に画像を持っていきたかったので敢えてitem属性の中では画像を使用しませんでした。自分としては意外にすっきりできたと思いますがどうでしょう?タイトルと本文はもともと入力欄がありますのでこれはそのまま流用。肝心のレビュー対象となるproductはAmazonのASINコードを入力しそこから情報を取得しています。レビューには付き物の★の格付けは敢えてはずしました。

アップロードファイルの保存先

ファイルのアップロード機能をRailsで実現するのは意外と簡単で、HowtoUploadFiles in Ruby on Railsに詳細なチュートリアルがあるのですが、悩んでいるのはアップロードしたファイルをどこの保存するかなんです。チュートリアルや「RailsによるアジャイルWebアプリケーション開発」ではDBに保存する例があってそれにならってmediumblobのカラムに保存するよう作成してみたのですが、ちょっと気持ちが悪い。案の定、容量の大きなファイルをアップロードすると問題が発生します。

「続きを読む...」の実装を考える(の続き)(の続き)

この件に関して大抵の日本語blogは最初に「要約」や「概要」を書かないから、英語圏blogツールの概要機能と相性が悪いと思うという意見のblogを読み考えさせられました。
確かに、blogの中で最も大切な本文を機械的に一定の長さでぶった切って(...)とかを無理矢理付けてしまうのは傲慢すぎる。僕のような書き捨ての文章であればまだしも、きちんと導入部と本文を意識して文章を書く場合その努力を無駄にしてしまうわけです。drupalの<!--break-->も書き手の努力と自由を制限しないよう考慮した仕様なのでしょう。

「続きを読む...」の実装を考える(の続き)

trackback.moduleをパクって一応Ruby版を作成しました。こんな感じ(というかほとんど同じ?)

def truncate_utf8(_string,_len,_dots = "(続く...)")
  return _string if _string.length < _len
  if _string[_len] < 0x80 || _string[_len] >= 0xc0
    return _string[0.._len-1] + _dots
  end	
  while(_len >= 0 && _string[_len] >= 0x80 && _string[_len] < 0xc0)
    _len = _len - 1
  end
  return _string[0.._len-1] + _dots
end

「続きを読む...」の実装を考える

ブログでは大抵トップページに各記事の要約一覧があって、個々の記事の最後が(続きを読む...)みたいになっています。これをどう実装するか?適当な長さでブッた切っちゃうと文字化けするししばらく悩んでいましたが意外なところでヒントをみつけちゃいました。
先日、トラックバックの実装をするとき参考のためdrupalのtrackback.moduleを読んでいたのですがその中でexcerptを適当な長さにカットするために使用しているtrancate_utf8()というのがあって、これはまさに使えそうなので早速コードunicode.incを調べてみました。

function truncate_utf8($string, $len, $wordsafe = FALSE, $dots = FALSE) {
   $slen = strlen($string);
   if ($slen <= $len) {
     return $string;
   }
   if ($wordsafe) {
     $end = $len;
     while (($string[--$len] != ' ') && ($len > 0)) {};
     if ($len == 0) {
       $len = $end;
     }
   }
   if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) {
     return substr($string, 0, $len) . ($dots ? ' ...' : '');
   }
   while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0) {};
   return substr($string, 0, $len) . ($dots ? ' ...' : '');
}

トラックバック機能をRubyで実装(受信編)

送信がなんとかできそうなので、受信の方も。送信側がなんとかできたので単純にその逆を実装します。トラバの受け口となるコントローラとアクションtb_controller/recvを用意して各記事のトラックバックURLをhttp://example.com/tb/recv/[記事ID]とします。これで準備完了。

def recv
 if url?(params[:url])
  @trackback = Trackback.new
  @trackback.blog_id = params[:id]
  :
  @trackback.save	
  @error = 0
  @massage = 'OK.'
 else
  @error = 1
  @massage = 'Missing TrackBack url.'
 end
end

こんな感じになります。もちろん、受信した後どんな処理を行うかは自由ですので必ずしもシリアライズする必要はないのかも知れません。url?の部分ははURLの形式をチェックするメソッドです実際にはもう少し色々チェックしたりすることがあるかと思います。また、http://example.com/tb/recv/[記事ID]の記事IDの部分はparams[:id]で取得できました。このあたりがRailsの楽チンなところですね。返すxmlは

トラックバック機能をRubyで実装(送信編)

トラックバックの送信機能の実装が大体できました。前回調べた情報を元に、Net::HTTPでpostします。このあたりはamazonのwebサービスを利用する時少し勉強したのですんなりできました。

require 'net/http'
include URI
query = "title=タイトル&url=http://myblog/text/001&blog_name=マイブログ&excerpt=トラバさせてね。"
Net::HTTP.start('送信先host') { | http |
  response,body = http.post('送信先path',URI.encode("query=#{query}"),
    {'Content-Type'=>'application/x-www-form-urlencoded'})
}

トラックバックの実装

ここまできたら最後はトラックバックの実装も目指しています。ものすごく単純化するとこの機能は送信元からのPOSTリクエスト

  • title
  • excerpt(概要)
  • url(対象となるurl)
  • blog_name

を受け取り、トラックバックの対象となるurlを抜き出して通常はそのurlにリンクを張ってその結果を送信元に返すという単純な動作です。xmlレスポンスは

<?xml version="1.0" encoding="iso-8859-1"?>
<response>
<error>1</error>
<message>The error message</message>
</response>

になります。(もちろん、エラーがなければ<message>の部分は不要です)

pingの戻り値 - あ〜そうかいと言わないで

weblogupdate.pingをしたあとの結果なんですが

<?xml version="1.0"?>
<methodResponse>
 <params><param>
   <value><struct>
     <member>
       <name>flerror</name>
        <value>
         <boolean>0</boolean>
        </value>
     </member>
     <member>
      <name>message</name>
        <value>Thanks for the ping.</value>
     </member>
   </struct></value>
 </param></params>
</methodResponse>

の形式で返ります。drupalのping.moduleではxmlrpc()の戻り値をTrue/Falseで判定していますが、RubyのXMLRPC::Clientでは

 result = connection.call('weblogUpdates.ping','my site',ping_uri);
 p result

としてみたら結果は、

{"message"=>"Thanks for the ping.", "flerror"=>true}

でしたので<member>のname/valueをhashで返すようです。結果判定はresult["flerror"]を判定すれば良いようです。

ping送信機能の実装

RSS/RDFが一応実装できたので次ぎはこれも必須といっていいping送信。無料でpingを一括送信してくれる便利サイトもあるし別にムリに実装しなくても良いのかも知れませんが...やっぱりね。まずは、ぼくが今使っているこのdrupalのpingモジュールの実装を眺めてみました。

function ping_ping($name = '', $url = '') {
  $result=xmlrpc('http://rpc.pingomatic.com','weblogUpdates.ping',$name, $url);
  if ($result===FALSE){
    watchdog('directory ping',t('Failed to notify pingomatic.com (site).'),WATCHDOG_WARNING);
  }
}

と意外に簡単そう。調べてみるとRubyでは、php同様XMLRPC::Clientというライブラリがありました。これを使ってping送信できるようです。XMLRPC::Clientのドキュメントによればクライアント生成メソッドにはnew,new2,new3とありますが、一番簡易なnew2()を使うことにします。肝心のping更新メッセージはcall()メソッドで送信しますが、例えばテクノラティの場合、要求するメッセージ(パラメータリスト)は

<?xml version="1.0"?>
<methodCall>
  <methodName>weblogUpdates.ping</methodName>
    <params>
      <param><value>ブログ名をここで書く</value></param>
      <param><value>http://www.YOURWEBLOGURL.com/</value></param>
    </params>
</methodCall>

というふうになっていますから、call()で指定するrpcパラメタはphpの場合と同様にrpcのメソッド名="weblogUpdates.ping"、"サイト名"、"サイトURL"になります。

RSS配信機能の実装

RSS配信機能を実装しました。
Rubricks用のコンポーネントの開発は書評ブログを作成したいということがきっかけで始めたのですが、amazonのアフェリエイト用機能にのめり込んでしまい当初の書評ブログの方が全然進んでいなかったのですが、ようやく着手したというところです。

それで、当節ブログといえばRSS配信機能くらいないとなってことで昨日あたりから始めたのですが、RailsのBuilderクラスを使えば意外に簡単なことがわかりました。

アマゾンアフェリエイト用コンポーネント第一段階完成!

Rubricks用アマゾンアフェリエイトコンポーネントの開発ですが、第一段階は終了しました。永遠に残るバグはあるものの、お試し運用で懸案だった三点(インストール直後のエラー・Paginateのレイアウト・Safariの文字化け)が一応の解決をみたので第一段階完了とします。また、登録したオススメ本をカテゴリ分類する機能も追加しました。

この数ヶ月はなかなか楽しかったです。初めてRuby(+on Rails)+Rubricksに触れ最初はちょっとお試し程度に始めたのですが途中からすっかりハマってしまいました。いまどきアマゾンアフェリエイトをターゲットにしたアプリケーションなんて山ほどあってそのどれもが私が作ったコンポーネントよりスマートで高機能なのでしょうが今となってはどうでもよろしい。

MissingSourceFile? Railsの動的ロードで対応

試行錯誤の結果、すべてのrequireをコメントアウトし、使用している独自のValueObjectクラスのnamespaceを明示的に修飾することで解決しました。

generateコマンドで生成される、コントローラやモデルクラスは自動的に名前空間が明示されますが、独自のValueObjectクラスには修飾がされていなかったためこれを明示するよう修正しました。考えてみればモデルクラスなんかはいちいちrequireせず使用しているわけですからもう少しはやく気がつくべきでした。こうすることでモデルクラスと同じように、requireせず使えるようになるみたいです。起動時にロードパスに組み込まれていないもの(今回のようにcomponents/xxx/xxxはまだ作成されていない)は完全なパスを指定してrequireするより、動的に実行時にロードしたほうが理にかなっているように思います。

コンテンツの配信