Saturday, February 25, 2012

RabbitMQ + RubyでRPC

RabbitMQとRubyで、同期的なRPC (Remote Procedure Call)を実装する。

server

client

サーバーにメッセージを送信するときは普通にサーバーがsubscribeしてるキューに届くように、適当なexchangeにpublishする。ここではex.rpc。

サーバーからのレスポンスの方は若干工夫が必要で、クライアントはリクエストのメッセージ送信前に、受信用の一時キューを作成しておいて、AMQPメッセージのreply_toヘッダーにキュー名を入れておく。サーバー側はリクエストを受け取ったら、処理結果をreply_toのキューに向けて送信する。このときはamq.directというデフォルトで用意されているexchangeを利用する。

サーバー側ではapiを増やすのが簡単なように、@acceptable_methodsにメソッド名を列挙しておくと、そのメソッドをクライアントから呼び出すことができるようにしてある。

このコードではクライアントはRPCの呼び出しの度にAMQPのコネクションを作成していて非効率なので、実際のアプリではAMQP.start (EventMachineのメインループ)はもっと外側にないとダメでしょう。

Tuesday, February 21, 2012

Rails3からRabbitMQに接続

Ruby AMQPはEventMachineに依存しているため、利用する際にはEventMachineのイベントループ内にいる必要がある。RailsからRabbitMQに接続する場合は、RailsのプロセスからRabbitMQにTCPのコネクションを張り、その中でchannelを作成することになる。

RabbitMQとのコネクションは、$RAILS_ROOT/config/initializers/amqp.rbというファイルを作成してその中で作成すると、起動時に実行されてコネクションを繋いでおくことができる。

接続設定

接続の設定ファイルはconfig/amqp.ymlなどとして保存しておけばいいんじゃないでしょうか。

development:
  host: localhost
  vhost: development

test:
  host: localhost
  vhost: test

production:
  host: localhost
  vhost: production

Thin

RailsのサーバーにThinを利用している場合、ThinがEventMachineベースなので特に気にせずconfig/initializers/amqp.rbには以下のように記述すれば良い。

def connect
  yml = YAML.load File.read(File.join(Rails.root, 'config/amqp.yml'))
  config = {}
  yml[Rails.env].each do |key, value|
    config[key.to_sym] = value
  end

  AMQP.connection = AMQP.connect(config)
end

EventMachine.next_tick do
  connect
end

controllerでパブリッシュ

例えばAmqpController/publishでパブリッシュする場合、以下のようになる。

class AmqpController < ApplicationController
  def publish
    connection = AMQP.connection
    channel = AMQP::Channel.new(connection)
    exchange = channel.direct 'ex.direct'
    msg = 'Hello, world'
    exchange.publish(msg, :routing_key => 'tasks')
    render :text => msg
  end
end

参考

Monday, February 20, 2012

ruby amqpで手動acknowledge

AMQPでは、キューからコンシューマーにメッセージが配信されても、コンシューマーがそのメッセージを正しく受け取ったことをブローカー(RabbitMQ)に通知(acknowledge)しないといけない。

subscribeメソッドを引数なしで使うと、自動的にacknowledgeされるが、:ack => trueを指定すると手動で設定することになる(最初、逆かと思った)。

# 自動 ack
queue.bind(exchange, :routing_key => 'tasks').subscribe do |headers, payload|
  # do some process
end
# 手動 ack
queue.bind(exchange, :routing_key => 'tasks').subscribe(:ack => true) do |headers, payload|
  # do some process
  headers.ack
end
メッセージ内容に応じてackするか決める場合は後者を使う。headers.ackメソッドでブローカーにacknowledgeしている。これをしないとメッセージはキューから削除されず、例えばこのコンシューマーがブローカーに再接続すると前回受け取ったメッセージが再送される。

RubyからRabbitMQに接続

RubyのクライアントからRabbitMQに接続し、Publish / Subscribeの動作確認をする。ここではdirect exchangeを使う。

ライブラリインストール

AMQPクライアントライブラリはamqpを使う。よくメンテナンスされているし、ドキュメントもよく整備されているので決定版でしょう。

$ gem install amqp

consumer

まずはメッセージを購読するconsumerを作成する。 ここでは以下のような設定でメッセージを待ち受けている。

  • queue名:サーバー自動生成(空文字を渡すことで設定)
  • exchange名:ex.direct
  • exchangeからqueueに送信する際のルーティングキー:'tasks'
最後のあたりでCTRL+Cで止められるようにしている。

producer

次にメッセージをpublishするスクリプト。

こちらは単純にexchangeを指定して送信するだけ。その際、consumerに合わせてルーティングキーを'tasks'にする。

実行

最初にconsumer.rbを実行し、別ウィンドウでproducer.rbを実行して、consumer.rbに'Hello, world'と表示されれば成功。その際、procuder.rbの方にも'sent: Hello, world'と表示される。publishメソッドでパブリッシュし、RabbitMQから送信完了の応答があるとブロック内が呼ばれる。

Sunday, February 19, 2012

MacでのRabbiqMQ インストール

MacにRabbitMQとWebの管理コンソールをインストールする。

  • MacOS X 10.6.8
  • Erlang 5.9
  • RabbitMQ v2.7.1

RabbitMQインストール

RabbitMQはMacPortsでインストール。

$ sudo port install rabbitmq-server
起動はrabbitmq-serverコマンド。
$ sudo rabbitmq-server
Activating RabbitMQ plugins ...

+---+   +---+
|   |   |   |
|   |   |   |
|   |   |   |
|   +---+   +-------+
|                   |
| RabbitMQ  +---+   |
|           |   |   |
|   v2.7.1  +---+   |
|                   |
+-------------------+
AMQP 0-9-1 / 0-9 / 0-8
Copyright (C) 2007-2011 VMware, Inc.
Licensed under the MPL.  See http://www.rabbitmq.com/

起動できたらCTRL-Cでメニューっぽいものが表示されるのでAキー(abort)で一旦終了。本来、rabbitmqctl stopとするのが正常な止め方のようだが、僕の環境でsudo rabbitmqctl stopとするとログ(/opt/local/var/log/rabbitmq/rabbit@hostname.log)に以下のようなエラーメッセージが出た。
=ERROR REPORT==== 19-Feb-2012::23:03:53 ===
** Connection attempt from disallowed node 'rabbitmqctl67610@xxx' **
これはErlang cookieというのが正しく設定されていないのが原因のようだが、ひとまずここでは放っておく。

ホスト名に注意

Erlang関連はホスト名に注意が必要。会社の環境にインストールしたとき、DHCPを使っていた影響で、macのホスト名がIPアドレスになっていた。この状態だとRabbitMQを起動しようとしても、以下のようなログが出力されうまく起動できない。(以下の149はDHCPで振られたIPの最後の8ビットの値)

ERROR: epmd error for host "149": timeout (timed out establishing tcp connection)
正しいホスト名に設定すれば起動できる。デフォルトのbashならターミナルのプロンプトの一番左側におそらくホスト名が表示されていると思われるので、参考に。

RabbitMQ Web管理コンソールプラグイン インストール

RabbitMQにはWeb画面でRabbitMQを管理できるプラグインがデフォルトでバンドルされているので、公式ページを参考にインストールする。 最初に必要なディレクトリがないので作成してから、コマンドを一発叩くだけ。

$ sudo mkdir /opt/local/etc/rabbitmq/
$ sudo rabbitmq-plugins enable rabbitmq_management
# 再度RabbitMQ起動
$ sudo rabbitmq-server
Activating RabbitMQ plugins ...
6 plugins activated:
* amqp_client-0.0.0
* mochiweb-1.3-rmq0.0.0-git
* rabbitmq_management-0.0.0
* rabbitmq_management_agent-0.0.0
* rabbitmq_mochiweb-0.0.0
* webmachine-1.7.0-rmq0.0.0-hg
# ... 略
そうしたら以下にアクセス。デフォルトのユーザー/パスワードはguest/guest。
RabbitMQ WebConsole
これでWeb画面からexchangeやらqueueやらをWebコンソールから作ったり削除したりできる。

参考URL

Thursday, February 16, 2012

RabbitMQ / ActiveMQ, and so on.

今開発しているシステムに、MQを導入しようと考えている。今の所RabbitMQが第一候補だが、比較しているページなどを調べてみた。

  1. stackoverflow : ActiveMQ or RabbitMQ or ZeroMQ or
  2. Python messaging: ActiveMQ and RabbitMQ
  3. Second Life Wiki : Message Queue Evaluation Notes
  4. RabbitMQ vs Apache ActiveMQ vs Apache qpid

Pythonを使ってActiveMQとRabbitMQを比較している上記2のサイトが特に参考になる。要約すると:

  • どちらも実用に耐えられる製品である
  • 全体的に言うとActiveMQの方がRabbitMQよりも性能が良い
  • RabbitMQはpulish(書き込み)が非同期なので高速だが、subscribe(読み出し)がそれに比較して遅い。書き込みが非常に多いケースだとメッセージがキューにたまっていくので、ベンチマークをすると一定の時間でクラッシュする。
  • 同時に作成可能なキュー数は、ActiveMQだと64,000くらい、RabbitMQだと32,000くらい

ActiveMQも良さそうである。ただやはりActiveMQはJMSでJavaから使うのが普通っぽいし、ドキュメントもそういった使い方のものが多そうである。うちのシステムは今のところ大体がRuby。もちろんRuby用のクライアントライブラリも用意されているが、どうしようか。

Wednesday, February 08, 2012

RubyでJSONのprettyprint

RubyでJSONをインデントして出力するには、JSON.pretty_generateを使う。

require 'json'
my_json = { :array => [1, 2, 3, { :sample => "hash"} ], :foo => "bar" }
puts JSON.pretty_generate(my_json)
結果は:
{
  "array": [
    1,
    2,
    3,
    {
      "sample": "hash"
    }
  ],
  "foo": "bar"
}
参考:How can I “Pretty” format my JSON output in Ruby on Rails?

ちなみに、コマンドラインからのときはpythonを使った以下のワンライナーが使える。

$ echo '{"foo": "lorem", "bar": "ipsum"}' | python -mjson.tool
参考:How to pretty-print JSON script?

rsolrで複数ドキュメントをアップロード

検索エンジンのSolrをRubyから使っている。ライブラリはrsolr。 このrsolrで複数ドキュメントを一括でアップロードする時、相当するメソッドがないように見えたが、 ソースを見たらaddに配列で渡せば問題なく複数ドキュメントを扱えるようになっていた。

require 'rsolr'
solr = RSolr.connect :url => "http://localhost:8080/solr"
documents = [
  {:id => "1", :text => "text 1"},
  {:id => "2", :text => "text 3"},
]
solr.add documents
solr.commit
最後のcommitは忘れがちなので気をつける。

日本語の解説本『Apache Solr入門』ではsolr-rubyを紹介していたが、rubygemsでのダウンロード数がrsolrのほうが随分上なのでこっちを選んだ。

Tuesday, February 07, 2012

MacにMeCab + Rubyバインディングインストール

以下の環境にMeCab 0.992 と ruby-mecab 0.992をインストールする。

環境

  • MacOSX 10.6
  • ruby 1.9.2p312 (2011-08-11 revision 32926) [x86_64-darwin10.8.0]

参考

MeCabインストール

MeCab本体は公式サイトに乗っている通りにソースからインストール。ちなみに僕は~/localにインストールした。

$ tar zxfv mecab-0.992.tar.gz
$ cd mecab-0.992
# MeCabインストール
$ ./configure --prefix=/Users/your_home/local
$ make
$ make check
$ make install
辞書はUTF-8で作成するようにした。
# IPAの辞書インストール
$ tar zxfv mecab-ipadic-2.7.0-20070801.tar.gz
$ mecab-ipadic-2.7.0-20070801
$ ./configure --prefix=/Users/your_home/local --with-charset=utf8
$ make
$ make install
これでインストールで来たので、コマンドラインで確認。
$ mecab # これでREPLとして起動する
すもももももももものうち # <= ターミナルに打ち込む
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

Rubyバインディングインストール

ダウンロードページからRubyバインディングをダウンロードする。

$tar xzvf mecab-ruby-0.992.tar.gz
$ cd mecab-ruby-0.992
ここで、僕はMeCabを~/localにインストールしているので、以下のパッチをextconf.rbにあててライブラリの参照先を修正した。/usr/localにインストールしている場合は不要(たぶん)。 (参考)
--- extconf.rb.org 2012-02-08 14:10:41.000000000 +0900
+++ extconf.rb 2012-02-08 14:13:08.000000000 +0900
@@ -8,5 +8,6 @@
 }
 
 $CFLAGS += ' ' + `#{mecab_config} --cflags`.chomp
+$LDFLAGS = '-L/Users/your_home/local/lib'
 
 have_header('mecab.h') && create_makefile('MeCab')
で、コンパイルしてインストール。
$ ruby extconf.rb
$ make
$ make install
付属のtest.rbがそれらしく動けばOK。
$ ruby test.rb 
0.992
太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
この 連体詞,*,*,*,*,*,この,コノ,コノ
本 名詞,一般,*,*,*,*,本,ホン,ホン
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
# (略)

mecab-rubyのエンコーディング

mecab-rubyをRuby 1.9で使うと、結果の文字列のエンコーディングがascii-8bitになっている。

# coding: utf-8
require 'MeCab'

t = MeCab::Tagger.new
node = t.parseToNode('日本語のテスト')
surface = node.next.surface
p [:surface, surface.encoding, surface]
# => [:surface, #<Encoding:ASCII-8BIT>, "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"]
仕方がないのでString#force_encodingで強制的にUTF-8に変換する。 http://d.hatena.ne.jp/miyapong/20100706/1278419260
surface = node.next.surface.force_encoding("UTF-8")
p [:surface, surface.encoding, surface]
# => [:surface, #<Encoding:UTF-8>, "日本語"]

Tuesday, January 31, 2012

ubuntu11.10にruby 1.9.2, Rails 3.2をインストール

会社でUbuntu上にRailsをインストールしようとして、若干手間取ったのでメモ。(Ubuntuはまっさらからのインストール)

(最終的な)環境は以下の通り。

  • OS: Ubuntu 11.10
  • Ruby 1.9.2
  • Rails: 3.2.1

関連パッケージのインストール

Git, RVM, Rubyをインストールする。会社環境はプロキシ内なので、そのあたりも注意。

まずはプロキシの設定。必要に応じて.bashrcなりにも追記。

$ export http_proxy="your.proxy.name:8080"
$ export https_proxy="your.proxy.name:8080"
ついでcurl, Git, build-essentialインストール。
$ sudo apt-get install curl
$ sudo apt-get install git
$ sudo apt-get install build-essential
で、~/.gitconfigに以下を追記。
[http]
  proxy = %http_proxy%
Ubuntuのデフォルトでは各種devパッケージが入っていないようなので、zlib1g-dev libssl-dev libreadline-gplv2-dev libxml2-dev libsqlite3-devを追加インストール。参考
$ sudo apt-get install zlib1g-dev libssl-dev libreadline-gplv2-dev libxml2-dev libsqlite3-dev

Rubyインストール

rvmでRuby本体をインストール。

$ rvm install 1.9.2
このままだとgem installが失敗するので、zlibをコンパイル。参考
$ cd ~/.rvm/src/ruby-1.9.2-p290/ext/zlib
$ ruby extconf.rb
$ make
$ make install
同様に、bundleで使うsslのためにopensslもコンパイル。
$ cd ~/.rvm/src/ruby-1.9.2-p290/ext/openssl
$ ruby extconf.rb
$ make
$ make install

Railsインストール

ここでようやくrailsインストール。

$ gem install rails
$ rails -v
Rails 3.2.1
$ rails new testapp
$ cd testapp
ただ、ここでscaffoldするとExecJS::RuntimeUnavailableが発生、と怒られるので、Gemfileにexecjs, therubyracerを追加。 参考
gem 'execjs'
gem 'therubyracer'
ここでもう一度bundle install(ここで、上記build-essentialが入っていないとg++がなくてコンパイルできない)
$ bundle
ようやくscaffoldを実行し、動作確認。
$ rails g scaffold user user_name:string age:integer
$ rake db:migrate
$ rails server
できた。

Thursday, January 26, 2012

Gitのリモート(bare)リポジトリの扱い

Gitで、リモートにあるbareリポジトリ(repo.gitのようなリポジトリ)の扱いについてメモ。

ここでは以下のようなリポジトリ構成になっているものとします。

  • CENTER.git : チーム外で管理しているリポジトリ
  • MAIN.git : チームの開発者が普段pushするリポジトリ
  • LOCAL : 開発者が利用するリポジトリ

bareリポジトリでのマージ

bareリポジトリ(MAIN.git)上ではマージはできない。これは第一にbareリポジトリにはwork copyがないから。work copyがないと、マージで衝突したときに解決する手段がない。bareリポジトリ上のブランチのマージは、一度開発環境(LOCAL)など別のリポジトリ上にプル/チェックアウトし、そこでマージしたうえでリモートにpushする。

外部リポジトリからの取り込み

CENTER.gitで修正があり、それをMAIN.gitに取り込むにはgit fetchを使う。これだけで取り込みはOKのはずだが、fetchで取り込んだだけではHEADが更新された最新に移動しないので、reset --softする(work copyがないので、softでないとエラーとなる)。

# 以下、MAIN.git上での操作
$ git fetch <CENTER.git>
$ git reset --soft HEAD
この後でgit logすると、最新の変更がログに出力される。

ブランチを指定してpush

たとえばoriginに指定されているリモートリポジトリのdev-remoteブランチに、ローカルのdev-localブランチをpushする場合:

$ git push origin dev-local:dev-remote

リモートのブランチからクローンする

リモートリポジトリ(MAIN.git)のdev-remoteブランチを、ローカルにdev-localブランチとして取得する場合、まずgit cloneした後、リモートのブランチを指定してcheckoutする。

git clone <MAIN.git>
git checkout -b dev-local remotes/origin/dev-remote

リモートにタグを反映する

ローカルで作成したタグをリモートに反映する

$ git push --tags

RAILS_ENVの追加や環境ごとの違いへの対応方法

Railsではデフォルトでdevelopment, test, productionという環境に対応できるように作られているのはご存知の通り。 この環境ごとの設定や、新しい環境の追加方法などについて、簡単にまとめる。

ちなみに執筆時点では以下の実行環境を利用している。

  • Ruby: 1.9.2
  • Ruby on Rails: 3.0.5

環境名を取得する

Rubyのコードの中から現在の環境名(development|test|productionなど)を取得するのは、Rails.envを利用する。

新しい環境設定を追加する

例えばstagingという環境を新規に追加するには、以下の作業をする。

  1. RAILS_HOME/config/environmentsにあるproduction.rbをstaging.rbにコピーする
  2. database.ymlにstagingの項目を追加する
もちろんその他に環境依存の設定ファイルなどがあれば、そちらにもstagingを追加する。これで例えば以下のようにすればstagingの設定でrailsを起動できる。
$ rake db:create RAILS_ENV=staging
$ rake db:migrate RAILS_ENV=staging
$ rails server -e staging

環境ごとに違うgemを使う

たとえばdevelopmentとproductionで違うgemを利用する場合、Gemfileに以下のように記述する。

group :development, :test do
  gem 'sqlite3-ruby'
end
group :staging, :production do
  gem 'mysql2', '0.2.7'
end
bundle installする際には、以下のようにすると特定の環境で必要なgemを除外できる。
$ bundle install --without test development
参考: Bundler : Using Groups

Saturday, January 14, 2012

scala のパーサーコンビネーターで数式を評価

Scalaのパーサーコンビネーターの練習のため、数式をパースして計算してみる。

参考にしたのはコップ本と、以下のサイト。

実行

実行引数に数式を文字列で与えると計算結果を標準出力にプリントする。

$ scala ArithmeticParser.scala "(2 + 3) * -2.5"
-12.5

数式の文法定義

ValueからParenまでのクラスは、数式をモデリングするためのもの。ここはパーサーからは独立している。

ArithmeticParserで、これらのクラスに入力の数式をマッピングしていく。

def factor: Parser[Value] = (
  floatingPointNumber ^^ { case n => Number(n.toDouble) }
  | "-"~>floatingPointNumber ^^ { case n => Number(n.toDouble * -1.0) }
  | "("~>expr<~")" ^^ { case e => Paren(e) }
)
factorがもっとも細かい単位にマッチする。つまり、数値か括弧()。数値を表すfloatingPointNumberは継承元のJavaTokenParsersで定義されていて、浮動小数表現にマッチする。マッチしたあとの処理(返り値の計算)は^^で定義する。この箇所のように複数種類の要素にマッチする場合は、それぞれを | で列挙する。"-"~>floatingPointNumber で負の数(-2.5など)にマッチさせる。最後の"("~>expr<~")"で括弧表記にマッチさせる。"x"~>という表記は、読み取った"x"を捨てる。逆に"x"~という表記であれば"x"を解析結果として利用できる。

def expr: Parser[Value] = term~rep(addSub)     ^^ { case f1~rest => Formula(f1, rest) }
def term: Parser[Value] = factor~rep(multiDiv) ^^ { case f1~rest => Formula(f1, rest) }
termはfactorの掛け算/割り算の連続(2 * 3など)。連続ということをrepで表現している。exprは同様にterm同士の足し算/引き算の連続(2 + 3など)。またfactorのうち括弧表現のときは、内容としてexprを含む。

def multiDiv: Parser[Rest] = ("*"|"/")~factor ^^ { case op~v => Rest(op, v) }
def addSub: Parser[Rest]   = ("+"|"-")~term   ^^ { case op~v => Rest(op, v) }
multiDivは掛け算のうち、記号と後ろ側の数値にマッチする(/ 2など)。したがって1 * 2 / 3というtermは、イメージとしては[1, [[*, 2], [/, 3]]]という形にパースされる。addSubも同様。

パースと計算の実行

parseAll(expr, e).get.eval
パースの実行はparseAllで。第一引数に、入力全体にマッチさせる関数を指定。第二引数にパースする文字列を渡す。結果としてパース結果のオブジェクトが帰って来るので、getで最終的な^^で指定したパース結果を取得する。今回はValue型で帰って来るので、evalで計算している。

最初は全然よくわからなかったが、ちょっと書いてみると確かに強力そうな気がしてきた。

Thursday, January 12, 2012

RubyでTwitterのユーザーがフォローしているユーザーのリストを取得する方法

TwitterのAPIでは、次の二つを使う。

Rubyでは以下のような感じ。

require 'twitter'
ids = Twitter.friend_ids("daixque").ids[0, 10]
users = Twitter.users(ids)

取得できる量に制限があるので(最大100件)、先頭の10件に絞っている。

Tuesday, December 27, 2011

Neo4j 使い方メモ

今、Neo4jを調査しているのでメモ。

Neo4jはグラフDBとよばれるNoSQL系のデータベースです。Twitterのフォロー関係のような、有向グラフ構造を保存できます。

http://neo4j.org/

Neo4jはJavaからは組み込みで利用できますが、その他ネイティブ系のバインディングが全然ありません。そのかわり、RESTによるJSON/HTTPのインターフェイスが用意されています。

ここではRESTサーバーとRubyクライアントのセットでNeo4jを利用する例を紹介します。

サーバーのインストール

Neo4jサーバーをコマンドラインから起動するだけなら、ダウンロードページから落としてきて起動するだけ。

サービスに登録するためのコマンドも用意されています。

sudo ./bin/neo4j install

サーバー起動

$ cd NEO4J_HOME
$ bin/neo4j start

以下のURLからWebの管理画面を表示できます。

http://localhost:7474/webadmin

サーバー終了

$ bin/neo4j stop

データの削除

以下のフォルダを削除すると、全てのデータを消去できます。

NEO4J_HOME/data/graph.db
ただし、サーバーが停止しているときに消さないといけないみたいです。消したつもりが復活したりしました。

データ削除インターフェイスを追加するアドオンとかも見つけましたが、1.5までの対応のようです。(執筆時点でのNeo4j最新は1.6。無理矢理動くかは未検証)

https://github.com/jexp/neo4j-clean-remote-db-addon

ドキュメント

RESTインターフェイスについては以下にドキュメントがあります。

http://docs.neo4j.org/chunked/milestone/rest-api.html

ただ、そこまで充実しているわけではありません。 Java APIも含め、全体を網羅したPDFもありました。

http://docs.neo4j.org/pdf/neo4j-manual-stable.pdf

Neography

僕はRubyから利用したいので、NeographyというNeo4jのRESTクライアントを使います。

https://github.com/maxdemarzi/neography

インストールはgemで。

gem install neography

Neography以外にも、RailsのActiveModelのように動作するものとして、以下のようなものがあるようです。

  • neology: Neographyのラッパー
  • architect4r: よりActiveModelに近いが、クエリーのみ実装されていて、index等はサポートしない

traverse

ノードやリレーションの作成などのシンプルな操作はgithubのサンプルを見てもらうとして、グラフをトラバースする方法についてメモしておきます。ちょうどneographyを使ったfacebookの友達サジェストのサンプルがありました。

https://github.com/maxdemarzi/neography/blob/master/examples/facebook.rb

サジェストする部分を見てみます。

def suggestions_for(node)
  @neo.traverse(node, "nodes", {
    "order"         => "breadth first",
    "uniqueness"    => "node global",
    "relationships" => { "type" => "friends", "direction" => "in" },
    "depth"         => 2,
    "return filter" => {
      "language" => "javascript",
      "body"     => "position.length() == 2;"
    }
  }) 
end

最初の引数nodeはグラフ走査の起点です。第二引数"nodes"はノードの走査をする、ということを指定しています。 第三引数のオプションとして、それなりの指定があります。

orderでは幅優先"breadth first"を指定しています。他に深さ優先"depth first"が指定できます。

return filterでは、検索条件を指定します。ここではnodeから2ホップのノード(友達の友達)を返すように指定しています。

depthでは、トラバースする際の最大の深度を指定しています。

Cypher

Neo4j特有のクエリー言語として、Cypherがあります。

http://docs.neo4j.org/chunked/snapshot/cypher-query-lang.html

neo.execute_queryというメソッドでCypherクエリーを実行できます。 たとえば先ほどの友達サジェストは、以下のように書けます。

require 'rubygems'
require 'neography'

@neo = Neography::Rest.new

def create_person(name)
  @neo.create_node("name" => name)
end

def make_mutual_friends(node1, node2)
  @neo.create_relationship("friends", node1, node2)
  @neo.create_relationship("friends", node2, node1)
end

def suggestions_for(node)
  n = Neography::Node.new node
  q = "START n=node(#{n.neo_id}) MATCH n -[:friends]-> f -[:friends]-> t WHERE not(t.name = n.name) RETURN t.name"
  result = @neo.execute_query(q)
  result["data"].map { |f| f.first }
end

johnathan = create_person('Johnathan')
mark      = create_person('Mark')
phill     = create_person('Phill')
mary      = create_person('Mary')
luke      = create_person('Luke')

make_mutual_friends(johnathan, mark)
make_mutual_friends(mark, mary)
make_mutual_friends(mark, phill)
make_mutual_friends(phill, mary)
make_mutual_friends(phill, luke)

puts "Johnathan should become friends with #{suggestions_for(johnathan).join(', ')}"
結果:
Johnathan should become friends with Mary, Phill

ここで実行しているクエリーはこんな感じです。

START n=node(#{n.neo_id})
MATCH n -[:friends]-> f -[:friends]-> t
WHERE not(t.name = n.name)
RETURN t.name

traverseの例とちょっと論理的に同等とは言えないかもしれませんが(同値チェックのあたり)、この例ではうまく動きます。 Cypherではこのように、STARTで起点、MATCHで検索条件、WHEREでフィルター条件、RETURNで戻り値を記述します。MATCHの記法はなかなか強力で、a -[:link]-> t <-[:link]- bのように、逆向きのリレーションも扱えます。

その他

TinkerPopというところが、グラフDBのためのソフトウェアスタックを用意しています。

http://tinkerpop.com/

グラフのトラバースなどに特化したクエリー言語Gremlinや、RESTインターフェイスのRexsterなどがあります。 本当ならNeo4jを直に使うよりこちらを経由した方が良いと思うのですが、ちょっとまだこなれてない感じがしました。

今日のところは、とりえあえずここまで。

Monday, October 10, 2011

jQuery+jsonpでエラーハンドリング

jQueryの$.ajaxでjsonpを使うと、errorハンドラが発火しない。jQuery+jsonpでエラーハンドリングが必要なケースは、jquery-jsonpを使う。

$.jsonp({
  url: url,
  dataType: 'jsonp',
  callbackParameter: 'callback', // パラメータ名がjsonpではなくcallbackParameter
  success: function(data, status) {},
  error: function(XHR, textStatus, errorThrown){}
});

参考:jQuery ajax (jsonp) ignores a timeout and doesn't fire the error event

Thursday, September 15, 2011

Cameleon : HTTPスタブ フレームワーク

Rubyで書かれたHTTPのダミー/モックサーバーを手軽に作るためのツール、Cameleonをリリースしました。

仕事でよくHTTPの外部システムを使うアプリを作ることがあるのですが、Cameleonはその外部システムのモックを簡単に作るために開発しました。何度か実際の開発案件で利用しています。

使い方はRuby on Railsの感じを参考にしています。

特徴

  • 基本はレスポンスのファイルをフォルダに配置するだけ
  • "_switch.rb"というシンプルなRubyのDSLでレスポンス用のファイルを選択
  • RESTfulなサービスのモックも簡単に作成できる(HTTPメソッドサポート)

インストール

Rubygemsで一発インストール

$ gem install cameleon
インストールできるとcameleonコマンドが使えるようになります。

Getting started

まずは、cameleonのプロジェクトをnewコマンドで作ります。

$ cameleon new your_project_name
できたディレクトリに移動してserverコマンドを実行するとデフォルトでポート9292でサーバーが起動します。
$ cd your_project_name
$ cameleon server
別のターミナルを立ち上げて、curlでテストしてみましょう。
$ curl http://localhost:9292
Hey, I am cameleon!
Edit response folder to change response whatever you want.
see README.md more detail.
はい、とりあえず動いていることは確認できました。

使い方

たとえば次のようなインターフェイスを作成することを考えます。

  • URLのパスが/path/to/interface
  • レスポンスは単純に"ok"
この場合、cameleonではresponse/path/to/interfaceディレクトリを作成し、その中にokと書いたファイルをdefault.txtとして保存すればOKです。
$ mkdir -p response/path/to/interface
$ cd response/path/to/interface
$ echo "ok" > default.txt
curlでテストしてみましょう。
$ curl http://localhost:9292/path/to/interface
ok
同様のことはgenerateコマンドを使うとより簡単です。ここでは/path/to/fooに新しいインターフェイスを作ってみます。
$ cameleon generate path/to/foo
こちらもテスト。
$ curl http://localhost:9292/path/to/foo
edit me: response/path/to/foo/default
作成できてますね。レスポンスは必要な内容に書き換えて下さい。

_switch.rb

普通アプリ開発で利用するのであれば、モックとはいえ、レスポンスをリクエストに応じて変化させる必要があるでしょう。そのときはフォルダ内に"_switch.rb"というファイルを作成し、その中でどのレスポンスファイルをレンダリングするかを選択します。

_switch.rbはRubyで自由に記述できます。レスポンスの内容を切り替えるためには、renderメソッドでレスポンスファイルを選択します。renderメソッドが呼ばれると、そこで_switch.rbからはすぐに抜け、以降の処理は継続しません。逆に、最後までrenderメソッドが呼ばれなかった場合は、defaultで始まるファイルをレンダリングします。また、_switch.rbではparamsにリクエストパラメータが格納されているので、これを使ってレスポンスを選択するといいでしょう。

_switch.rbのサンプルです。

# you can access URL parameters by 'params'
case params[:key]
when "foo"
  # curl http://localhost:9292/gallery/params?key=foo
  render "foo.txt"
when "bar"
  # You can also use POST or PUT. See 'rest' more about hadling HTTP methods.
  # curl -XPOST http://localhost:9292/gallery/params -d"key=bar"
  render "bar.txt"
end

# if else, render "default" (any extention acceptable) file.

Gallery

galleryコマンドで、response/galleryディレクトリにサンプルが出力されます。最初は_switch.rbで使えるAPIなどの参考になるとおもいますので利用して下さい。

$ cd YOUR_CAMELEON_HOME
$ cameleon gallery

私はけっこう便利に使えているので、ぜひ利用してみて下さい。

Wednesday, August 31, 2011

markd

markd is rich html builder for markdown, and is especially focused on printing purpose. You can get well-formatted printings (or PDF by preview) when you print html using modern browser (such as Google Chrome, Firefox, Safari).

Source code is available on github.

markd is based on bluecloth and google code prittify.

features:

  • Well-formatted HTML with CSS
  • Good style for printings
  • Auto numbered chapters
  • Code Highlighting

Installation

You can install markd using RubyGems as below:

$ gem install markd

Usage

Build html from markdown:

$ markd -o out_dir markdown.md
where markdown.md is target markdown file and out_dir is directory that html/css/js files are generated into.

Use -h (or --help) option to show detail.
$ markd -h

License

markd is released under the MIT license.

Enjoy!

Monday, August 29, 2011

Facebookのユーザーのサムネイル

Facebook からユーザーのサムネイル画像を取得するのは次のURL。
<img src="http://graph.facebook.com//picture?type=square" />
UserIDを取得するのは、facebook_oauthでは
client = FacebookOAuth::Client.new(
  :application_id     => "your application id",
  :application_secret => "your application secret",
  :token              => "your token"
)
user_id = client.me.info["id"]

Thursday, August 25, 2011

markd

markdを公開しました。Rubyで書かれたMarkdownをHTMLに変換するツールです。特にソースコードを含むドキュメントを簡単かつ奇麗に作成することができます。ソースはgithubから。

念のため説明すると、markdownはeメールの装飾でよく使われる書式でテキストファイルをマークアップするフォーマットです。 githubに登録されているプロジェクトの多くのトップでREADME.mdが表示されいますが、これがmarkdownです。markdown記法についてはこちらのページが詳しいです。

markdはそのmarkdown形式のファイルからHTMLファイルを出力するコマンドラインツールです。markdの出力はHTMLですが、ブラウザから印刷することでそれなりの品質のドキュメントとなることを目指しています。

markdの特徴
  • ソースコードがカラーリングされる
  • h1, h2タグに章番号が自動で振られる
  • ブラウザからの印刷に最適化されている
RubyのMarkdownプロセッサには既にblueclothなどがあります。markdも内部でblueclothを利用しています。 markdはblueclothの出力結果に対してHTMLを整形し、CSS/Javascriptを追加してデフォルトで一通りレイアウトされた結果を出力します。

インストール

Rubygemsでインストールできます。
$ gem install markd

使い方

markdコマンドに引数でmarkdownファイルを渡します。
$ markd markdown.md
デフォルトではdocsというディレクトリが作成され、その中にHTMLファイルなどが生成されます。出力先を変更したい時には-oオプションを使います。
$ markd -o OUTPUT_DIR markdown.md
この例ではOUPUT_DIRというディレクトリに結果が出力されます。

ライセンス

MITライセンスです。