ruby Archive
redisでユーザをfollowしたときにTimeLineをsortして再構築
前回のつづき。
課題の1つに挙げてた中で、「Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない」というのがありましたが、こんなかんじでいいのかな。自分のTLに他人のTLを混ぜて、sortしてstoreし直し。(redis-rbが0.2.0じゃないと動かないかも)
def merge_timeline(user_id)
my_id = @login_user[:id]
return if @redis.type?("uid:#{user_id}:posts") == "none"
user_statuses = @redis.lrange("uid:#{user_id}:posts", 0, 100)
user_statuses.each do |status|
@redis.lpush("uid:#{my_id}:home", status)
end
@redis.sort("uid:#{my_id}:home", :order => "desc alpha", :store => "uid:#{my_id}:home")
end
でも、そうしたあとにremoveしたときにまた再構築し直さないと駄目なことに気づいた。。と、いうかremove面倒ですねぇ、どうするんだろう。泥臭く全statusを走査し直しなのか、それとも順序付Setとか使えば楽にremoveできるのかな。また考え直しです。
あと、このupdateについて、github上のRedTweetのコードもpushし直してますので、興味ある方はご確認ください。
redisとRailsでTwitterクローン「RedTweet」を作りました
前回「Mac OSXにredisをインストール」で、redisを動かす環境まではできたので、せっかくなんでテスト的に何かサービスを作ってみよう、ということでTwitterクローンのRedTweetを作ってみました。
redisを使ったTwitterクローンは、PHP版のRetwisと、それをSinatraで書き直したRetwis-RBがあるのですが、サンプルコードはいくらっても世の中に少しは役立つだろうと思ってRails版で実装してみました。オンラインで動作できる環境はないので、git cloneしてscript/serverで手元の起動で確認ください、、と投げやり気味ですみません。とりあえず次の項目は一通り実装しています。
- ユーザID発行
- Login / Logout
- Follow / Remove
- 自分のTimeline, Public Timeline, 各ユーザのTimelineの閲覧
ちなみにRedTweetって名前はRedisとTweetを混ぜて直感でつけた名前で、git pushしたあとで同じ名前のサイトがあることが発覚したくらい直感でつけた名前です。
目的
さて、今回はまじめにTwitterクローンを作ることが目的ではなくて、実際は、次の項目を目的として実装してみました。
- Retwisのデザインを読んで、それに従って一通り実装してみる
- redisのAPIの仕様を学ぶ
- RDBを一切使わない、NoSQLでWebサービスを作るためのノウハウを身につける
結局全て似通った話になるのですが、上記のデザイン仕様書はTwitter的なサービスを作り上げることで、KVSをどのように利用すればいいのか、がかなり分かりやすく説明されてあるので、いい勉強になりました。また、ユーザ情報はString, 各TLはList, Following/FollowerをSetで管理することで、redisの主要なAPIを網羅できたことも、redisの学習に役立ちました。
と、同時に課題もすでに見えていて、
- スケーリングがどこまでできるかはまだ手元で理解できていない
- やっぱりActiveRecord的にラップしたライブラリは必須
- Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない
なんかが今の段階で挙げられます。1.はテストデータを作ることで解決するはず。2.はやっぱりmustだなぁ、と思えるところまでははっきりした理解で、ユーザ名をIDから引くときなど毎回決まったprefixがついたkeyから探索するのは冗長すぎてやってられません。OhmというHashとObjectのマッピングライブラリもあるので、このあたりも1度使ってみたほうが良さそうだな、というところ。3.はFollowした瞬間に一定数TweetをListにLPUSHして、そのlistの中でSORTすればいいのかな??正直、SORTまだよくわかってません。
まとめ
というわけで、課題は多いものの、redisとRailsで最低限の動作をするものは実装できました。NoSQLでもサービスを作り上げることは理解できたので、Ohmなど、他のライブラリも使ったりすることで、 redisの利点をもっと伸ばして理解を深めたいな、というところですね。
Mac OSXにredisをインストール
Tokyo Cabinet / Tokyo Tyrantに代表されるKey-Value Storage,いわゆるKVSは多くのプロダクトが乱立してなかなか違いを理解するのも難しい状況になりつつあるのですが、ここ数日はredisに注目をしています。redisとは何ぞやというと、公式サイトには次のような特徴が挙げられています。
- memcachedのようないわゆるKVS
- valueにはStringだけではなく、List, Setも利用できる
- データに対するpush/pop, add/removeのような操作はすべてatomicな操作
- 通常はメモリ上で動作するが、定期的にデータをディスクに書き出す(常に書き出すような設定も可能)
- Master-Slaveのレプしケーションをサポート
- Ruby, Python, Perl, Java...などの各言語のバインディングを用意
と、かなり豪華なスペックで、なんでもかかってこい!なプロダクトです。国内の利用例はまだ聴いた事無いですが、海外ではgithubやCraigslistが採用したことでここ最近有名になってきました。
さて、そんなredisの開発環境を整えてみたいと思います。環境はMac OSX 10.5。
redisのインストール
ソースコードから入れてもいいのですが、Macの場合は、MacPortsでインストール可能です。
$sudo port -d sync
で、インストールリストを最新の状態にしておいて
$sudo port install redis
で、インストールされる、、はずなのですが、僕の環境だと途中で止まりました。
$ sudo port install redis ---> Fetching redis ---> Attempting to fetch redis-1.2.2.tar.gz from http://redis.googlecode.com/files/ ---> Verifying checksum(s) for redis ---> Extracting redis ---> Configuring redis ---> Building redis ---> Staging redis into destroot ---> Creating launchd control script ########################################################### # A startup item has been generated that will aid in # starting redis with launchd. It is disabled # by default. Execute the following command to start it, # and to cause it to launch at startup: # # sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist ########################################################### ---> Installing redis @1.2.2_0 ---> Activating redis @1.2.2_0 Error: Target org.macports.activate returned: couldn't open "/opt/local/var/log/redis.log": no such file or directory Error: Status 1 encountered during processing.
なんかlogディレクトリがないみたいなので、ディレクトリ作成してから一度アンインストールしてやりなおしてみます。
$ sudo mkdir /opt/local/var/log $ sudo port uninstall redis $ sudo port install redis ================================================================== * To start up a redis server instance use this command: * * redis-server /opt/local/etc/redis.conf * ================================================================== ---> Cleaning redis
これで、インストールできました。サービスに登録したい場合は、途中のログに書いてあるとおり、
sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist
で、OKです。
Rubyのバインディングのインストール
僕は普段Rubyを使うので、Rubyのバインディングもあわせてインストールしてみることにします。gemでインストール可能です。
sudo gem install redis
redisの起動
redisを起動するときは、redis-serverを実行します。MacPortsでインストールした場合、/opt/local/bin にバイナリがインストールされてあるので
sudo /opt/local/bin/redis-server /opt/local/etc/redis.conf
で、起動します。confの中には、起動するときのポート番号や、ディスクに書き出すタイミングや条件などの設定項目があります。(ちなみに、起動時に設定ファイルのパスを与えていますが、これはビルド時?に設定しておけば、実行時に与える必要は無いようです。ただ、MacPorts利用時もそれが可能なのかどうか?は不明です)
では、実際に動作を確認してみたいとおもいます。確認するときは、redis-serverと同じ場所にあるredis-cli, またはtelnetで6379に対して接続してコマンドを入力します。(今回はtelnetで接続して生のコマンドを叩いて確認してみます)ValueをStringとして扱うときは、
set
set foo 3 bar +OK get foo $3 bar
listとして扱うときは、lpush
lpush artists 8 fishmans +OK lpush artists 9 clammbon +OK lpush artists 7 polaris +OK lrange artists 0 -1 *3 $7 polaris $8 clammbon $8 fishmans
Listの要素追加もStringの要素追加と同じでO(1)で実現できるのがredisのポイントですね。実際の細かなコマンドの仕様については、リファレンスを参照ください。
ここまでざっと環境構築まで確認できたので、次回はもうすこしredisを使い倒してその結果をまとめたいと思います。
CakePHPとRuby on Railsの違い
最近、仕事でRailsを使い始めたので、今までよく使っていたCakePHPとどこが一緒でどこが違うのかをざっくりまとめてみました。まだRailsは勉強中なので、理解が不十分だったり間違っている箇所もあるかと思いますが、それらの点についてはコメントなどでご教授いただければ幸いです。
Controller
CakePHPの場合、任意のアクションにおいて、/users/show/katsuma のように、URLで「/」で区切られているものは、アクション以降の文字列も勝手に引数に分けてくれます。なので、アクション側の定義で
function show($id, $name)
のように引数を分けて定義しておいてあげれば、勝手に値が割り当てられることになります。
Railsの場合はconfig/routes.rbで振り分け方法を定義しておいてあげる必要があります。上の例だと
map.connect 'users/show/:id/:name', :controller => 'users', :action => 'show'
こんな感じになるでしょうか。
一方で、Railsの場合、routes.rbはものすごい強力で、map.connetのかわりに好きな名前のメソッドを指定するだけで「(名前)_url」で指定したcontroller, actionなどを含むURLを作成することなんかできたりします。たとえば
ActionController::Routing::Routes.draw do |map| map.berryz '', :controller => "berryz", :action=> "show" end
こんな感じにroutes.rbで定義しておいた上で、
berryz_url(:id => 'miyabi')
で呼び出すと、
http://localhost:3000/berryz/show/miyabi
を意味することになります。Railsすごい。。。!
また、GETで引数指定で渡ってきたも、routes.rbで指定された引数も、すべて param[:hoge]のシンボル指定で取得できるのも特徴でしょうか。
ApplicationController
CakePHPの場合、任意のControllerクラスの継承元であるApplicationControllerは "app/" ディレクトリ直下に "application_controller.php" の名前で設置されます。
Railsの場合は、"app/controllers/" の中に "application.rb"の名前で設置され、その場所と名前が微妙に異なります。
この名前については、これ規約からも外れてるよなぁ。。と思ってたら、どうやらRails 2.3.0からは"application_controller.rb"に名前が変わるようですね。詳しい話はこちらに書いてました。(参照元:そういえば ApplicationController ってファイル名の規約を守ってなかったんだな)
View(レイアウト)
CakePHPの場合、大枠のレイアウトはviews/layouts/default.ctp に、そのレイアウトのHTMLを記述します。
Railsの場合は、views/layouts/application.rhtmlに記述することになり、その名前は異なります。統一性の観点から言うと、Railsのこの名前の方が個人的には好きです。
部分テンプレート
CakePHPの場合、部分テンプレート(element)は、views/elements/ 以下に header.ctp の名前で保存しておきます。elementの呼び出す場合は、View側で
$this->element('header')
の、ように呼び出します。
Railsの場合は、特定のcontroller内での共通テンプレート、全controllerでの共通テンプレートとそれぞれ別に分けることができます。 前者の場合は、 views/user/_header.rhtml のように、"views/controller名/_{部分テンプレート名}.rhtml"の形式になります。 逆に後者の場合、views/shared/_header.rhtmlのように、"views/shared/_{部分テンプレート名}.rhtml" の形式になります。
部分テンプレートの呼び出し方は、前者の場合は、<%=render :partial=>'header'/> のように、後者の場合は :partialで指定する値が"shared/header"のようになります。
この点については、Railsはここまで細かい指定がなくてもいいのにな、、と思います。Cakeの方が直感的な規約だし、呼び出し方も簡単かな、と。
Filter
Cake,Railsともにcontrollerにおいて、その前後にフィルタをかけることが可能です。いわゆるbeforeFilter, afterFilterですね。 Cakeでは特定のController、またはApplicationControllerにおいてbeforeFilter/afterFilterアクションを定義しておくことで、そのフィルタを通すことが可能です。
Railsの場合、フィルタにはメソッドはもちろんですが、クラス、ブロックの3つのレベルで指定可能です。また、複数のフィルタが定義されている場合は、その定義された順番にフィルタが適用されます。
このRailsの細かな指定は凄い、としか言いようがないかんじ。特にブロックで渡すことができる柔軟性は使いこなせばすごく便利そうな印象です。(まだ自分はそこまで使いこなせてません)
静的ファイル
CakePHPの場合、画像やCSSファイルなど、静的ファイル(や、routes.phpのルーティングに外れるもの)は、"webroot/" 以下に設置しておけばOKです。
逆に、Railsの場合は、"public" ディレクトリに設置することになり、そのディレクトリ名が異なっています。
これについては、Railsを意識しまくったCakeとしては、どうしてここの名前だけ変えたのかはよく分かりません。。。
まとめ
ざっと目につきやすい相違点をまとめてみました。Railsについてはまだまだ触り始めたばかりなので、相違点はまだまだあるでしょうし、注意して理解を深めて行きたいと思います。 また、今回こうやって相違点を考えていくことで、むしろお互いの理解が深まるんじゃないかな、とも思っています。
ちなみに、チュートリアルについて、book.cakephp.orgは日本語化されてるわけですが、guides.rubyonrails.orgは日本語化されてないんでしょうか?? すごくよくまとまってそうなので、できれば日本語で読んでみたいのですが。。
Rubyでブックマークカウンタの修正スクリプト書きました
(追記 : 2009/02/12) ソースはgithubでホスティングすることにしました。
ここ最近、このBlogで使ってるソーシャルブックマークカウンタで、deliciousの数がぜんぜん動いていませんでした。どうもドメイン変わった時期の前後あたりから、APIでかえってくるJSONのパースにコケているようで、Perlのモジュールの手を出す方法がさっぱり分からなかったので途方に暮れてました。で、暮れてばっかりだとアレなのでRubyの勉強がてら修正スクリプト書いてみました。超素人コードですけど。
仕掛けは単純で、プラグインをインストールするとmt_bookmark_countってテーブルができるので、そこのbookmark_count_delicous_counter, bookmark_count_total_counterをがしがしupdateさせてるだけです。deliciousのAPIへはまとめて15個URLづつリクエスト投げてます。13, 32行目あたりのDB情報、URL情報を適当に直したら動くんじゃないかな、と思います。
ただ、このBlogのMTもバージョンは超古くて3.3とかだし最近のMTのバージョンでこのプラグインが動くかどうかも定かではないです。。あくまでオレオレパッチ。もしご利用されたい方がいらしたら使ってみてください。要MySQL/Rubyですが、次のサイトに従ってインストールすると楽にできました。
動かし方は上のリンクの圧縮ファイルを解凍し、ruby delicious.rb で動くと思います。あとはサイトを丸ごと再構築すればdeliciousのカウンタも反映されるはずです。
まともにRubyでコード書いたの初めてだったですけど、個人的にはPerlよりも書きやすかったかも。割とさくさく書けて楽しかったです。さらっとツール書くのは便利ですね。
Mac OSXで初めてのRubyを始めてみました
Rubyの勉強を始めてみたくなったので、O'REILLYの「初めてのRuby」
を購入してみました。MacPortsで最新版のRubyを導入しつつ、1章から読み進めているのですが、早速ハマったところがあるのでそのメモを残しておきたいと思います。
Ruby1.9の導入
MacPortsでruby19の名前でインストールできます。
sudo port -d install ruby19
/usr/bin/ruby には、元からインストールされているrubyが入っているので、これをMacPortsでインストールしたものと入れ替えます。(シンボリックリンク張り替えちゃったけどこれでいいのかな?ruby_selectみたいのないのかな)
cd /usr/bin sudo mv ruby ruby.org sudo ln -s /opt/local/bin/ruby1.9 ruby
ruby --version
と、入力して
ruby 1.9.1 (2008-10-28 revision 19983) [i386-darwin9]
こんなかんじの出力になればOKだと思います。
日本語(マルチバイト)の利用
1.5.4制御式のあたりについて。通常だとマルチバイト文字の利用でエラーがでちゃいます。たとえば
p "こんにちわ!こんにちわ!><"
で、
odd.rb:8: invalid multibyte char (US-ASCII) odd.rb:8: syntax error, unexpected $end, expecting keyword_end
などとエラー文が出力されてしまいます。 これは、次のように冒頭に利用するエンコード方式をコメントで書けばOK
#!/usr/bin/ruby
を
#!/usr/bin/ruby # coding: UTF-8
に変更すればOK。
tkパッケージの利用
1.5.5のコールバックのあたりの話。実は冒頭のインストール方法だとtkパッケージがインストールされていないため、利用できません。(require文でコケる) なので、tkパッケージを有効にして入れ直します。variantsオプションで有効なインストールオプションを確認します。
$ port variants ruby19
ruby19 has the variants:
universal
c_api_docs: Generate documentation for Ruby C API
tk: Build using MacPorts Tk
mactk: Build using MacOS X Tk Framework
tk, mactkと似たようなものが2つあります。これ両方有効にしてインストールするとコケちゃいます。とりあえずmactkの方を有効にしてインストールしなおし。
sudo port install ruby19 +c_api_docs +mactk
ところがこれだとエラーでコケます。
Error: The following dependencies failed to build: doxygen graphviz pango urw-fonts
いろいろビルドに失敗しているみたいなので全部個別にインストール。
sudo port install urw-fonts sudo port install pango sudo port install graphviz sudo port install doxygen
この上で、もう一度インストールしなおし。
sudo port install ruby19 +c_api_docs +mactk
これで次のrequire文でコケません。
require "tk"
まとめ
ちょっとづつRubyもわかってきました。これから毎日1章づつくらいのペースで進めていきたいとおもいますよ!