Friday, November 16, 2012

mmrd (MongoDB MapReduce Driver) リリースしました

MongoDBのMapReduceをNode.jsから実行/テストするためのモジュール、mmrdをリリースしました。

インストール

$ npm install mmrd

使い方

例えばMongoDBのワードカウントなどで、以下のようなMapReduceを書いていたとします。

// map.js
function map() {
  var words = this.text.split(' ');
  words.forEach(function(word) {
    emit(word, 1);
  });
}
//reduce.js
function reduce(key, values) {
  var result = 0;
  values.forEach(function(v) {
    result += v;
  });
  return result;
}

このとき、mmrdを使うと以下のような形でこのMapReduceをNode.jsから実行できます。

// runner.js
var mmrd = require('mmrd');

// map test
(function() {
  console.log("Word count / Map test");
  var doc = { text: "foo bar baz" };
  mmrd.loadMap('map.js');
  mmrd.map(doc, function(key, value) {
    console.log(key, ":", value);
  });
})();

// reduce test
(function() {
  console.log("Word count / Reduce test");
  mmrd.loadReduce('reduce.js');
  var key = "foo";
  var values = [1, 1];
  var reduced = mmrd.reduce(key, values);
  console.log(key, ":", reduced);
})();

// integration test
(function() {
  console.log("Word count / Integration test");
  var docs = [
    { text: "foo bar baz" },
    { text: "foo baz baz"}
  ];
  mmrd.loadMap('map.js');
  mmrd.loadReduce('reduce.js');
  var result = mmrd.mapReduce(docs);
  console.log(result);
})();
起動
$ node runner.js

もちろん、mochaやshouldを使ったテストも書けます。当たり前ですが。

お試しあれ。

Tuesday, October 16, 2012

Rubyで短縮URL(リダイレクトされるURL)からコンテンツを取得する

短縮URLは普通リダイレクトで目的のコンテンツまで辿り着く。Rubyで短縮URLで指定されたコンテンツを扱いたいが、http_clientはリダイレクトには対応しているけど、デフォルトではhttpsの短縮URLに対してhttpのURLにリダイレクトされたときに例外を吐くのでbit.lyなんかではうまく使えない。

なので、以下のように常にリダイレクトするようにコールバックを設定すると、この制限を回避できる。

require 'httpclient'

def get_content_with_redirection(url)
  client = HTTPClient.new
  def client.allow_all_redirection_callback(uri, res)
    urify(res.header['location'][0])
  end
  client.redirect_uri_callback = client.method(:allow_all_redirection_callback)
  client.get_content url
end

html = get_content_with_redirection("http://bit.ly/RRKD7d")
puts html

Monday, September 24, 2012

fluentdのインストールと簡単な使い方

fluentdはRubyで書かれたsyslogdのようなロギングミドルウェア。様々なログをネットワーク上で色々ルーティングして、たとえば最終的にMongoDBに登録したりすることができる。

fluentdをインストールして使ってみる。 インストールはgemで一発。

$ gem install fluentd

fluentdを起動するには、作業ディレクトリを作ってコマンドで設定ファイルを作成する。起動はfluentdを-cオプションでキックする。

$ fluentd --setup ./fluent
$ cd fluent
$ fluentd -c conf/fluent.conf

動作確認は別コンソールで次のように打ち込むと、先ほどのコンソールにログが出力される。

$ echo '{"json":"message"}' | fluent-cat debug.test

ここまでは公式サイトと同内容。 次に、テキストファイルをtailのようにウォッチして、fluentdに転送してみる。 conf/fluent.confを次のように記述する。

<source>
  type tail
  path /path/to/test.log
  tag debug.*
  format /^(?<log>.*)$/
</source>

<match debug.**>
  type stdout
</match>

ここで、sourceディレクティブのtagはどのmatchディレクティブにログをルーティングするかを決めるためのキーとなる。RabbitMQのルーティングキーと同じ感じ。formatディレクティブは、出力形式を正規表現で設定する。このとき、マッチした箇所に?<name>で名前を付ける。上記例だとlogという名前にしてる。

この状態でfluentdを起動して/path/to/test.logに追記すると、コンソールにログが出力される。

$ echo test >> /path/to/test.log

Wednesday, September 19, 2012

Rails3のviewで、JavascriptのオブジェクトにRubyのオブジェクトをjson(など)で渡す

Rails3のview(erb)で、Rubyのオブジェクトをto_jsonしたものをJavascriptの変数に設定する際は、html_safeメソッドを使う。
var json = <%= {:foo => "FOO"}.to_json.html_safe %>;

Saturday, September 08, 2012

MacにR + randomForestをインストール

OSX 10.7にRをインストールする。今回はMacPortsを使う。

$ sudo port install R
これでRコマンド(対話型のインタープリター)がインストールされる。終了はq()関数。
$ R
> q()

対話型のインタープリターではなく、ソースをファイルから実行するにはRscriptコマンドを使う。とりあえず引数を出力するサンプルを作ってみる。

# hello.r
arg <- commandArgs(TRUE)
cat("hello", arg, "\n")
実行
$ Rscript hello.r world
hello world

PerlのCPANやRubyのRubygemsのようなパッケージ管理ツールが組み込まれていて、install.packages関数でパッケージをインストールできる(CRANというらしい)。試しに機械学習ライブラリのrandomForestをインストール。

> install.packages("randomForest")

MacPortsでインストールしていると、/opt/local/以下の書き込み権限がないという警告がでるので、今回はコンソールにいわれる通り、~/R/...にライブラリフォルダを作った。途中でTclのダイアログでダウンロード元を選ばされるので、普通に東京を選んだ。

インストールしたライブラリを使うのにはlibrary関数を使う。

> library("randomForest")
randomForest 4.6-6
Type rfNews() to see new features/changes/bug fixes.

その他、参考サイト。

Tuesday, September 04, 2012

RubyでCSVをエスケープして出力

改行を含んだりするカラムがあるCSVを出力するとき、全てのカラムを""で囲んで出力するには:force_quotesオプションを使う。
CSV.open(filepath, "w", :force_quotes => true) do |csv_writer|
  csv_writer << ["出力する文字列"]
end
カラムごとに出力フォーマットを変更する場合は、convertersオプションを利用する。例えば2番目のカラムをテキスト形式で出力する場合は:
converter = ->(field, field_info) {
  case field_info.index
  when 1
    CSV::Converters[:text].call(field, field_info)
  else
    field
  end
}
CSV.open(filepath, "w", :converters => converter) do |csv_writer|
  csv_writer << [1, "出力する文字列"]
end
ここではCSVクラスにデフォルトで用意されているCSV::Convertersにある、文字列用のコンバーターを利用している。

Gitでタグ間の変更量を調べる

たとえばtag_1というタグを打っていて、このタグとHEADとの差分をappディレクトリ以下のみ調べるコマンドは以下。
$ git diff --shortstat tag_1 HEAD ./app
 79 files changed, 2657 insertions(+), 531 deletions(-)
--shortstatの代わりに--statを指定すると、変更したファイルごとの変更量も出力される。

Sunday, April 08, 2012

Rails3でscaffoldのテンプレートをカスタマイズ

Rails3ではscaffoldなどで生成するテンプレートをカスタマイズできる。まず、自動生成するコードの元になるテンプレートを、以下のコマンドでlib/templatesに作成する。
$ rake rails:templates:copy
あとは作成されたテンプレートをいじるだけ。以下、作業内容など。
  • デザインに関すること(CSS用のクラス設定など)
  • will_pagenateのページング対応
  • 文言の日本語化
  • .rbファイルにマジックコメントを追加 (# -*- coding: utf-8 -*-)

Thursday, March 01, 2012

Javascriptで年初からの日数/週数を取得する

Javascriptで年初からの日数と週数を数えるメソッドをDateクラスに追加する。週数の方は、日曜を起点とした週数を数える(Rubyの%Uに相当)。

Date.prototype.getDayOfYear = function() {
  var onejan = new Date(this.getFullYear(), 0, 1);
  return Math.ceil((this - onejan) / 86400000);
};

Date.prototype.getWeekOfYear = function() {
  var onejan = new Date(this.getFullYear(), 0, 1);
  var offset = onejan.getDay() - 1;
  var weeks = Math.floor((this.getDayOfYear() + offset) / 7);
  return (onejan.getDay() == 0) ? weeks + 1 : weeks;
};
使い方は:
var time = Date.now();
var today = new Date(time); // Fri Mar 02 2012 16:49:11 GMT+0900 (JST)
today.getDayOfYear(); // => 62
today.getWeekOfYear(); // => 9

参考

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件に絞っている。