ActiveRecordとMongoidは共存できる

第5回 MongoDB.jp 勉強会 in Tokyoに行ってきた! メモとかはまたのちほどアップするとして(そういえばRubyKaigi最終日のメモもアップできていない)、@yuki24 さんの発表で「ActiveRecordとMongoidは共存」できるという話を聞いて、ちょっと試してみた。

その過程で適当に作ったコードをgithubに上げておく。Todoを作成・更新した際にその内容をTodoLogに吐き出すというモノ。

mongoidのインストール

これは普通に。

$ gem install bson_ext mongoid
$ rails g mongoid:config

Mongoidのモデルの作成

mongoidをインストールするとmodelジェネレータがmongoidのモデルファイルを生成してくれる。

$ rails g model todo_log title:string done:boolean 
      invoke  mongoid
      create    app/models/todo_log.rb

ActiveRecordのモデルの作成

modelジェネレータがmongoidに割り振られたので、代わりにactive_record:modelジェネレータを使う。

$ rails g active_record:model todo title:string done:boolean
      create  db/migrate/20110730071022_create_todos.rb
      create  app/models/todo.rb

これはrails gコマンドの出力を見て初めて気づいた。

アプリでやってること

Todoのafter_create、after_updateコールバックにTodoLog.createを追加すれば、TodoLが作成・更新された際にTodoLogが作成される。

最後に

やってみれば当たり前なんだけどちゃんと動く。 mongoid入れただけでmodelがそれ用のジェネレータになっていたのでできないものだと思ってた。 調べてみるってとても大事。

あとスキーマレスは便利。ついさっき、「TodoLogなんだからtodo_id入れなきゃな」と気づいたのでコードをちょっとだけ修正したのだけれど、それだけでtodo_idが保存されるようになった。凄く手軽。 もちろん後から追加したんだから、それ以前のレコードにはtodo_idは保存されていないんだけど。


RubyKaigi2011(二日目)に参加してきた

今日も午後から参加してきた。@a_matsudaさん、@yharaさんの話が聞けなくて、寝坊してしまったことを激しく後悔している。

5 years know-how of RSpec driven Rails app. development. / @moro

最近のrspecやfixture-replacementを使ったテストの書き方のノウハウ。 fixture-replacementはFabricationを使ってるみたい。

寝坊して最後の5分しか見られなかったけど、早くもスライドUstが公開されているのでそちらをチェックしよう。

  • shared_context
  • fixtureはマスタ、fixture_replacementはリソース、beforeはイベントが向いてる
    • 適宜使い分けるといい

Rails3レシピブック買いました!

Efficient JavaScript integration testing with Ruby and V8 engine. / -Chris Kowalik

V8エンジンをRubyから呼び出してJavascriptのIntegrational Testをしようという話。

肝心のMustangとMikeの位置関係が理解しきれなかった。CとC++の混在してるMustangじゃなくてC++で統一されたMikeを使おうよ、って話でいいのかな。あとで別の人のレポートを読もう。

  • selenium時間かかりすぎ
  • selenium,watir,phantom 遅い
  • phantomはただのwebkitラッパーで、遅いことにかわりない
  • ヘッドレス
    • HtmlUnit(Java)
      • いち押し
      • 設定が面倒くさい
    • Zombie
    • EnvJS
  • 欲しいのは
    • 速さ
    • javascript support
    • 使いやすいAPI
    • ポータビリティ
  • Mustang – V8 in Ruby
    • nu7hatch/mustang
    • もとはrubyracer
    • スクラッチから設計・実装された
    • パフォーマンス重視
  • V8素敵
    • 世代GC
    • メモリモデルが優れてる
    • 暗黙クラス機能(?
    • インラインキャッシュ
    • ネイティブコードの生成
  • Mike
    • MustangはC/C/C++で書かれてる
      • 全部C++で書けばいいのに
    • https://github.com/nu7hatch/mike
    • 複数ウィンドウのサポート
    • フレームのサポート
    • ユーザの操作のサポート
      • クリック
      • フォーム入力
    • Javascriptの実行
    • アラート・ポップアップ
  • Q&A
    • V8のインスタンスはLinux上だと1個しか作れなかったと思うけど
      • 1つのインスタンスに対して複数のコンテキストを持たせることができるので問題にはならない

Advancing Net::HTTP / @wycats

net/httpは凄くいいライブラリだけど、ほんのちょっと機能が足りなかったのでnet2/httpとしてフォークしたよ、ついでにnet/reactorってのを書いたよ、っていう話だと思う。

IO周りが苦手なので全然ついていけなかった。

  • net/http
    • ブロックが多い
      • socketやレスポンスのリリースに使うからそうなってる
    • rackでリクエストとレスポンスに別のスレッドが必要になった
  • net2/httpにフォークした
    • ブロックの有無の分岐
  • よくわからん!

!RubyKaigi

時間が空いたので、3階で行われてた!RubyKaigi(not RubyKaigi)をチラ見してきた。

  • RubyKaigi会場にはmac多すぎて非macな私は孤独を感じてる! ** macは筐体のデザインが統一されているのでいっぱいいるように見える

実際Mac使ってる人多いよなぁ。Windows持ってきてる人は手元でどうやって作業しているんだろう?(@yuguiさんも「動いたらいいな」と思ってる)Cygwin?

個人的にはpowとか出てきちゃったのでなかなかmacからほかに移れない。でもLinuxもちゃんと勉強しなきゃだよなぁ…。

The Gate / @kakutani

Dave Thomasの基調講演の代理だったらしい。あまりメモを取れなかった。

今振り返ったら、昨日の@ayuminさんの発表ともなんかつながってるんだなぁと思った。 「たのしいRuby」って本が出てる通り、Ruby楽しい。だから仕事でも遊びと別け隔てなくそれをやっていきたい。 でもそれをやるために、「自分じゃない誰かにインスピレーションを与える」必要がある。 で、@ayuminさんの場合はそれが「攻めの人たちにRailsの必要性を説く」ってことだったのかもしれない。 (違ってたらゴメンなさい)

  • 達人プログラマー
    • 10年以上前の本だけど、今読んでも新しい発見がある
  • Pragmatic bookshelfにはいい本がある
  • アジャイルサムライ
    • 初めてアジャイルをやる人とかに、アジャイルの全体像を伝えるための本
  • Rubyが変えるのは会社ではなく人。変わった人が業務や業界を変えて行く。
  • 楽しく仕事をすることが大事
    • 生産性を上げる
    • 誰と仕事をするかが大事
  • 業務と業務外とを分けてたら立ちいかなるんじゃない?
    • 生きることの達人は、仕事と遊び、労働と余暇、心と体、教育と娯楽、愛と宗教の区別を付けない。何をやるにしろ、その道で卓越していることを目指す。仕事か遊びかは周りが決めてくれる。当人に取っては、つねに仕事であり遊びでもあるのだ(要出典)
  • 3つの宿題
    • 自分じゃない誰かにインスピレーションを与える
      • find someone to inspire who’s not like you
    • 自分の中に多様性を持つ
      • diversity
    • 前の人が通った轍から降りる
      • 新しい視点を持つ
    • そして楽しみながらやりなさい
  • Yesterday Is History, Tomorrow Is a Mystery, but Today Is a Gift. That Is Why It Is Called the Present. * 「カンフー・パンダ」からの一節らしい

LT

メモる余裕ナシ!

  • @nagachika
    • 9.2 trunk-commits/day
    • コミット中22%がバグ修正、しかし少なくとも3%が新たなバグを作り出している
  • @_yue_04
    • Ruby OS
  • @pwim
    • 外国人と日本人との壁をなくすために #trbmeetup を主催している
    • とても素晴らしいLTだった!
  • @niku_name
    • gem install myrurema
  • @florere
    • MacRubyで実装されたJamesと会話をする
    • Jamesは寝起きが悪い
  • こんどうようへい
    • Rails3 vs .Net MVC3
    • modernizr
    • .Net MVC3、テストの機能が有料版VisualStudioに依存
  • @ryopeko
    • rubykaigi.orgのソースは永和さん、えにしテックさんの最新の技術トレンドが注ぎ込まれてるのでとても参考になる
    • rubykaigi.com(ちなみにregional.rubykaigi.comも)はgithubベースで管理されてる
  • @stillpedant
    • vimperator : Firefox to vim
    • keysnail : Firefox to emacs
    • インタラクティブCLI
    • Rios::Proxy – gem install rios
      • コマンドライン入力・出力をフック
  • Eloy Durán & Vincent Isambart
    • MacRubyでCocoaを操作できるIRBっぽいものを作った
      • github.com/alloy
    • MacRubyはCocoaとがっちり連携できるから面白そう
  • @m_seki
    • ERB
  • Joseph Wilk
    • Limited Red
    • Work In Progressを減らす
    • 測定して可視化する

RubyKaigi2011(初日)に参加してきた

午後から参加してきた。

とりあえず箇条書きのメモを載せておく。あとで感想を書こう。

Ruby を利用した大規模ウェブサービスの開発・運用 / @hotchpotch

cookpadの中の話。extensionsがとても興味深かった。 この後のgithubとかもそうなんだけど、テスト=CIがもう当たり前なんだな、という感じがした。

  • 1.8.7/2.3
  • varnish
    • 30ms
  • tofu
  • solr
    • 集合を扱う(Facet
    • 重み付け検索(Boost Search
    • 動的なフィールド追加(Dynamicfields
    • 速度は変わらない、検索の柔軟性
      • 空間検索できるらしい
      • amebaの事例http://www.cyberagent.co.jp/news/press/2010/0708_2.html
  • ベストに集中
    • シンプル
    • キャッシュにのりやすい
    • 非同期を活用
      • 共通部分とユーザ固有とを分けることでキャッシュしやすくする
      • ユーザの体感速度的に問題ない
  • テスト
    • rspec 1.x
      • unit
      • functional
      • integration
        • JS周りの重視
          • capybara-webkit
    • リモートサーバ上でテスト
      • http://d.hatena.ne.jp/secondlife/20110410/1302442313
      • CIを通ったコードのみリリース
      • みんなテストを書く
      • CIで動かないテスト→将来的に価値のないテスト
  • extensions
    • 特定の条件下のみで有効になる機能拡張
    • 新機能の試用などに利用
    • 没になったら rm -rf app/extentions/foo_ext
    • specなくてok
      • 仕様の変更が多い
    • 例外が発生したら自動的に無効になる

Shipping at the Speed of Life / @atoms

githubの中の人の話。マシンガントーク。凄くたくさんのツールを使ってるみたい。

  • githubの維持のためにたくさんのツールを作った
    • 単純作業の自動化
  • BrowserdMod
  • collectd
    • 時系列データの収集(mysqlの書き込み時間とか
  • nagios
  • redis counters
    • 何が何回使われたか
  • campfire
    • コミュニケーション
    • http://propaneapp.com/
  • Haystack
    • hoptoadみたいなもの
    • 例外を監視
  • hubot
  • CI/Jenkinsの結果をcampfireに通知
  • new relic
    • http://newrelic.com/
    • パフォーマンスプロファイラ
      • レイヤごとに表示
  • silverline
    • https://silverline.librato.com/
    • アプリケーション別にリソースを監視
  • デプロイ
    • トピックブランチをデプロイすることもある
      • それならmasterにロールバックできる
      • 一部のサーバーに
      • subset deploy
      • heaven というライブラリで、特定のサーバに特定のブランチをデプロイ
  • まとめ
    • 大きなリリースをするときにはブログにポストを書いてユーザの反応を見る
    • 常にデータを集めておく、必要になったときに便利
    • カスタムな機能でプロセスの簡略化、柔軟性
      • プロダクトの成長で変化する
    • いいものをリリースすことを心かける
      • 価値を頻繁に提供する
        • 2週間スプリントとか生ぬるい
  • ユーザを大事にする
  • Q&A
    • マイグレーションどうしてる
      • まずカラム追加
      • それからコードを投入して使う
    • チャットなんでcamfireなん?
      • 安いし十分
    • 物理サーバだけ?
      • 小さなツールはrackspace使ってる
      • puppetとchef knifeで管理してる

Toggleable Mocks and Testing Strategies in a Service Oriented Architecture / EngineYardの中の人

HTTPのテストの仕方。あまり聞けなかった。

  • single responsibility principle
  • コードベースを小さくできる
  • チームで別々の部分を開発できる
  • RESTで通信
  • サーバのテストをするのが難しい
    • 遅い
    • 元の状態に戻せない(変更を受ける側が切り離されている
  • モックを作る
  • mock
    • オブジェクトの偽物
  • stub
    • メソッド(振る舞い)の偽物
  • fake
    • データの偽物
  • fakeweb
    • net/httpに相当するstub
    • fakewebの実装を知っている必要がある
      • テストにfakewebの初期化コードが入ってくるから?
  • Artifice
    • https://github.com/wycats/artifice
    • Rackアプリケーションをnet/httpなテストのつなぎ先として指定できる
      • プロダクションのバックエンドのコードをそのままテストに利用できるかも
  • Q&A
    • mockするだけじゃだめ?
      • integrationalなテストとして
        • unitテストならmockでもいいのかも、適宜
    • 非同期のテストは?
      • あまり経験ない
    • 異常系のテストはどうする?
      • フェイクでエラー状態のデータを作る
      • できないところはユニットテストでカバー

Issues of Enterprise Rubyist ~SIerのなかのRubyistが考えるべきこと~ / @ayumin

今日一番興味深かった発表。Rubyは使いたいけど、リスク回避の回避でその方向に持っていくのは難しい。必要としている場所に必要であるということを提示できないといけない。

  • SIerの顧客がrubyに求めること

    • 安い早い柔軟
    • エンタープライズに耐えられる品質
  • 自分の手元をrubyで変える

  • わざわざやるほど逼迫した需要がない
  • たのしいRubyより仕事の役に立つRuby。役立つRubyより、開発現場に欠かせないRuby。
  • リスク回避の回避は難しい
    • 攻めの部署に、すぐに使えて、柔軟に変更できる。効果をちゃんと測定できるソリューションを提案
      • 攻めの部署
        • 企画、営業
      • まもりの部署
        • 開発、運用
  • Q&A
    • ruby/railsは何が向いてると思います?
      • 定型化されていないもの(営業管理?CRM,SFA

日本の図書館はどのようにRubyを使っているか – 「Next-L Enju」と「国会図書館サーチ」 / -

途中から見たけど、とても面白そうだったようなのでUstでみようと思う。 elispは衝撃的。

  • solr
  • 目録登録がemacs lispで動く

Rubyで作った “critical mission” システムについて / @emorima

TokyoRubyistMeetupでお聞きした、東京ガスでの事例。RubyはWebサービス以外でも使われるべき。

  • 東京ガスの事例
  • リアルタイムにセンサからの情報を得るために、100プロセスx100スレッド/プロセス=10000スレッド
    • ディスクリプタ上限を超えていた
    • できるできないではなく、やらなければならない!
  • 計算量が必要な部分はCで実装
  • 230プロセスを使うプログラム、Cで作りたい?
  • “Rubyすげぇ”
    • 直感的
    • 容易
      • Thread
      • UDP/TCP Socket
    • 便利なメソッド
      • yield
      • instance_eval
    • テストが簡単に書ける
      • 正常系は当たり前
      • 異常系もできる
  • 大規模システムでも使えると思う
    • もったいない
    • ただスピードと信頼性は課題
  • Q&A
    • なんでruby?
      • お客さんがrubyを選んだから
    • rubyのアップグレードする?
      • 基本やらない
      • 安定稼働がなによりも大事
      • やるときは安定性のテストを行う

scopeでtwitterっぽいルーティング

「Ruby on Rails 3 アプリケーションプログラミング」のルーティングの項をパラパラとめくっていたら、scopeを使ってtwitterっぽくトップレベル(?)にユーザ名を持ってくるようなルーティングができることを知ったのでメモ。

概要

例えばroutes.rbには次のように記述したとする。

scope ':uid' do
  resources :todos
end

このとき次のようなURLでアクセスすると、USERNAMEの部分をparams[:uid]で受け取ることができる。

http://scope-routing-sample.dev/USERNAME/todos

サンプルアプリを作ってみた

早速サンプルのTODOアプリを作ってgithubに上げてみた:scope-routing-sample

次の手順で動く、はず(bundler, rvm, pow, powderが導入済みの前提で)。

$ git cloen git@github.com:a2ikm/scope-routing-sample.git
$ cd scope-routing-sample
$ rvm use 1.8.7@scope-routing-sample --create
$ bundle
$ powder
$ rake db:migrate
$ open http://scope-routing-sample.dev/

開いたら、

  • 適当にユーザを登録(Uidは半角英数字がベター)
  • 一覧の画面でUidをクリックして開く
  • さぁURLを確認!

ハマったところ

app/views/todos/_form.html.erbにあるform_forがちょっと面倒くさかった。

ただのresourcesなら

form_for(@todo) do |f|

のような記述でよしなにやってもらえるんだけど、今回の場合には、

  • :uidに何が入るのかを指定する必要がある
  • Todoのインスタンスが保存済みか否かで:idを指定するか否かが変わる

ため、一工夫する必要があった(form_for and scopes, rails 3 – stackoverflowを参考にした)。

form_for(@todo, :url => (@todo.persisted? ? todo_path(:id => @todo.id, :uid => @uid) :
                                                 todos_path(:uid => params[:uid]))) do |f|

こうすることで、fには@todoに関するオブジェクトが入り、action属性のURLにはtodo_pathとtodos_pathが適宜切り替えられ、:idと:uidを指定することができる。

まとめ

  • scopeを使ったルーティングはカッコイイかもしれない
  • form_forで一工夫が必要

scopeをネストすればgithubみたいにUSERNAME/PROJECTNAMEみたいなURLも作れるのかな?今度試してみよう。


perlのpackage越しのサブルーチン呼び出し時の引数

perlでpackage越しのサブルーチンを呼ぶ際に”::”と”->”だと引数が違う気がしていたけど、あまりよく考えていなかった。というわけでこんなスクリプトを書いた。

$ cat package_args.pl
#!/usr/bin/env perl

package Foo;
use Data::Dumper;

sub bar {
  print Dumper(\@_);
}

package main;

print "Foo::bar\n";
Foo::bar;

print "\n";

print "Foo->bar\n";
Foo->bar;

1;

さて、実行してみる。

$ perl package_args.pl
Foo::bar
$VAR1 = [];

Foo->bar
$VAR1 = [
          'Foo'
        ];

つまり、

  • Foo::bar() だと引数にパッケージが含まれない
  • Foo->bar()だと第1引数にパッケージが含まれる

nginx + unicorn + Rails on Mac

とりあえず動かしてみたのでメモ。

unicornを動かす

まずはgemをインストール。

$ gem install unicorn

unicornの処理を設定する

$ cd <RAILS_ROOT>
$ vi config/unicorn.rb

<RAILS_ROOT>/config/unicorn.rbはこんな感じ(nginx + unicorn を試してみたからほぼそのまま拝借):

# ワーカーの数
worker_processes 2

# ソケット経由で通信する
listen File.expand_path('tmp/sockets/unicorn.sock', ENV['RAILS_ROOT'])

# ログ
stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

# ダウンタイムなくす
preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      # SIGTTOU だと worker_processes が多いときおかしい気がする
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

そして起動。

$ unicorn_rails -c config/unicorn.rb -E production -p 5000 -D

unicorn_railsはRailsアプリケーションをunicornで起動するためのコマンド。最近のRailsはRackに乗っかっているからunicornコマンドだけでも十分だけど、unicorn_railsならではのメリットもあるらしい(さくらの VPS に登録してみた(4) nginx + unicorn参照)。

-cオプションで設定ファイルを指定、-EでRAILS_ENVを指定(デフォルトはdevelopment)、-pでポートを指定(デフォルトは8080)、-Dでデーモンとして動作。それ以外については-hオプションで一覧が表示される。

nginxを動かす

まずHomebrewでインストールして起動。

$ brew install nginx
$ cp /usr/local/Cellar/nginx/1.0.4/org.nginx.nginx.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/org.nginx.nginx.plist

この時点で http://localhost:8080 にアクセスするとnginxの初期画面が表示されるはず。

apacheでいうところのconf的なものは/usr/local/etc/nginx以下に入れられる。 さて、設定。

$ cd /usr/local/etc/nginx
$ mkdir -p sites-available sites-enabled
$ vi nginx.conf
$ ln -s /usr/local/etc/nginx/sites-available/default sites-enabled/default
$ vi sites-available/default

まずは初期画面に関する設定をnginx.confからsites-available/defaultに書き写す。

nginx.confの差分:

@@ -32,87 +32,6 @@ http {
 
     #gzip  on;
 
-    server {
-        listen       8080;
-        server_name  localhost;
-
-        #charset koi8-r;
-
-        #access_log  logs/host.access.log  main;
-
-        location / {
-            root   html;
-            index  index.html index.htm;
-        }
-
-        #error_page  404              /404.html;
-
-        # redirect server error pages to the static page /50x.html
-        #
-        error_page   500 502 503 504  /50x.html;
-        location = /50x.html {
-            root   html;
-        }
-
-        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
-        #
-        #location ~ \.php$ {
-        #    proxy_pass   http://127.0.0.1;
-        #}
-
-        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
-        #
-        #location ~ \.php$ {
-        #    root           html;
-        #    fastcgi_pass   127.0.0.1:9000;
-        #    fastcgi_index  index.php;
-        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
-        #    include        fastcgi_params;
-        #}
-        # deny access to .htaccess files, if Apache's document root
-        # concurs with nginx's one
-        #
-        #location ~ /\.ht {
-        #    deny  all;
-        #}
-    }
-
-
-    # another virtual host using mix of IP-, name-, and port-based configuration
-    #
-    #server {
-    #    listen       8000;
-    #    listen       somename:8080;
-    #    server_name  somename  alias  another.alias;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
-
-    # HTTPS server
-    #
-    #server {
-    #    listen       443;
-    #    server_name  localhost;
-
-    #    ssl                  on;
-    #    ssl_certificate      cert.pem;
-    #    ssl_certificate_key  cert.key;
-
-    #    ssl_session_timeout  5m;
-
-    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
-    #    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
-    #    ssl_prefer_server_ciphers   on;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
-
+    include /usr/local/etc/nginx/sites-enabled/*;
 }

末尾の追加項目は、nginx.confにべた書きするのではなく、sites-enabled/以下に分割されたファイルを読み込むように設定するための記述である。 sites-available/defaultは次のようにした。

+server {
+     listen       8081;
+     server_name  localhost;
+     
+     location / {
+         root   html;
+         index  index.html index.htm;
+     }
+}
+

もともとは8080をlistenしていたが、Railsアプリのほうに8080を使いたかったのでここではとりあえず8081にした。 root html;はドキュメントルートをhtmlディレクトリ(ここでは/usr/local/Cellar/nginx/<VERSION>/htmlになる)に設定している。

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8081 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

nginxとunicornをつなぐ

次にunicornとつないでRailsアプリを表示してみる。

$ ln -s /usr/local/etc/nginx/sites-available/<APPNAME>-unicorn sites-enabled/<APPNAME>-unicorn
$ vi sites-available/<APPNAME>-unicorn
upstream redmine-unicorn {
  server unix:/path/to/RAILS_ROOT/tmp/sockets/unicorn.sock;
}

server {
  listen 8080;
  server_name localhost;

  root /path/to/RAILS_ROOT/public;
  access_log /path/to/RAILS_ROOT/log/access.log;
  error_log /path/to/RAILS_ROOT/log/error.log;

  location / {
    if (-f $request_filename) { break; }
    # ファイルが存在しなければunicornにproxyする
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://<APPNAME>-unicorn;
  }

  # この記述がないと静的ファイルが表示されなかった
  location ~* \.(ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
    expires 1y;
  }
}

ここまでやって文法にミスが無ければnginxを再起動して、 http://localhost:8080 にアクセスしてみる。

$ /usr/local/sbin/nginx -t # 文法チェック
$ launchctl unload ~/Library/LaunchAgents/org.nginx.nginx.plist
$ launchctl load ~/Library/LaunchAgents/org.nginx.nginx.plist

最後に

とりあえず動いた。

以下、気になる点:

  • ApacheとNginx、PassengerとUnicorn、どっちがどういいんだろう?
  • 再起動するためにlaunchctlを呼ぶのは面倒くさい、なにか適当なコマンドはないのかな?
  • passengerのnginxモジュールを入れるためにはnginxを再コンパイルする必要があるみたいで、そうなるとRVMでgemsetを自由に切り替えられる環境とは相性が悪いのでは(本番ではRVMを使わないから関係ないだろうけど)
  • nginxの設定ファイルのセミコロンが面倒くさい

ちなみに今回なぜ80を使わずに808xを使ったかというと、powが80を奪ってしまっているから。Apacheに関してはIPv6で動かして回避するという方法があるらしいけど、nginxはどうなんだろう?


Macでpkill

homebrewでproctoolsをインストールする。

$ brew install proctools

has_manyな関連先をまとめてINSERTする

fields_foraccepts_nested_attributes_forを使って、has_manyな関連先をまとめてINSERTする方法。ソースはgithubに上げておいた。ちなみにRails 3.0.8。

Post has_many Tags through Taggingsというモデルがあったとする。

Post has_many Tags through Taggings.

とりあえずscaffoldはこんな感じ。Taggingだけは画面が要らないのでmodelだけ。

$ rails g scaffold posts title:string text:text
$ rails g scaffold tags name:string
$ rails g model tagging post:references tag:references
$ rake db:migrate

コードの修正で重要なのは次の2点。

app/models/post.rb に accepts_nested_attributes_forを設定する。

class Post < ActiveRecord::Base
  has_many :taggings
  has_many :tags, :through => :taggings
  accepts_nested_attributes_for :taggings
end

これで次のようなコードが実行された時にTaggingもまとめて作ってくれるようになる。

Post.create(
  :title => "タイトル", :text => "本文",
  :taggings_attributes => [
    { :tag_id => 1 }, { :tag_id => 2 }
  ]
)

app/views/posts/_form.html に fields_for を使ってリレーション先についてのフォームを作る。第一引数がtaggings_attributes[]になっているのがポイント。これはaccepts_nested_attributes_forに合わせて設定する。

  <div class="field">
    Tags<br />
    <% @taggings.each do |tagging| %>
      <%= f.fields_for "taggings_attributes[]", tagging do |tf| %>
        <%= tf.select :tag_id, Tag.all.map { |x| [x.name, x.id] } %>
      <% end %>
    <% end %>
  </div>

なお、 @taggings はapp/controllers/posts_controller.rbで事前に用意しておく。 @postからリレーションで辿らないのは、新規にPostを作るフォームでは@post.taggingsが必ず空なのでeachが回らないから。

  # GET /posts/new
  # GET /posts/new.xml
  def new
    @post = Post.new
    @taggings = Array.new(3) { Tagging.new }

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @post }
    end
  end

  # GET /posts/1/edit
  def edit
    @post = Post.find(params[:id])
    @taggings = @post.taggings
  end

あとはサーバを起動して/tagsからタグを登録し、/postsから記事を投稿すればok。 一応どんな感じの画面になるのかスクリーンショットを載せておこう。

作り込むなら、

  • accepts_nested_attributes_for のreject_ifオプションでtag_idがblankなら無視する
  • dependentにdestroyを設定する
  • Taggingでpost_idとtag_idのペアが一致するものが複数できないように制限をかける

などの点が気になるけど、今回は置いておく。

accepts_nested_attributes_for自体はhas_oneでも使えて、それに合わせてfields_forの引数も単数形にすれば良い。詳しくはリファレンスを参照。


geminaboxでローカルにgemをホストする

geminaboxを使ってローカルにgemをホストしてみる。

(とりあえずでやってみただけなので、手順等にミスがあるかも)

まずはサーバを立てる

rvmとpowが入っている前提で。 ~/var/geminabox 以下を使うとする。

$ rvm use 1.8.7@geminabox --create
$ gem install geminabox
$ mkdir -p ~/var/geminabox/data
$ echo "rvm use 1.8.7@geminabox" > ~/var/geminabox/.rvmrc
$ echo 'require "rubygems"
require "geminabox"

Geminabox.data = "#{ENV['HOME']}/var/geminabox/data"
run Geminabox
' > ~/var/geminabox/config.ru
$ ln -s $HOME/var/geminabox $HOME/.pow

これで http://geminabox.dev/ にアクセスすればgeminaboxでホストしているgem一覧が表示される。

つぎにgemを追加する

たとえばhello-0.0.1.gemをrubygems.orgから落としてきて追加する。

$ wget http://rubygems.org/downloads/hello-0.0.1.gem
$ gem inabox hello-0.0.1.gem

これをやると http://geminabox.dev/ の一覧に追加される。

使ってみる

$ gem sources -a http://geminabox.dev/

もしくは~/.gemrcのsourcesに手動で追加する。

:sources:
- http://geminabox.dev
- http://rubygems.org

bundlerだったらGemfileに追記する。

source "http://geminabox.dev"
source :rubygems

では実際にインストールしてみる。

$ gem install hello
Successfully installed hello-0.0.1
1 gem installed

できた!

最後に

powとrvmのペアが最強すぎる。サーバをいちいち立ち上げる必要もないし、geminaboxの依存するgemと開発用のgemとが混在することもない。

あとはrubygems.orgから一度落としたやつをgeminaboxにコピーすることができたら完璧だ。 gemコマンドのフックってできないのかな?


LaunchAgentsで標準出力・エラー出力を設定する

LaunchAgentsで起動しているデーモン等で標準出力・エラー出力先を指定するには、StandardOutPathとStandardErrorPathを設定する。

例えばmemcachedでは次のようにする(ちなみにhomebrewでインストールした)。

vi ~/Library/LaunchAgents/com.danga.memcached.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.danga.memcached</string>
  <key>KeepAlive</key>
  <true/>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/memcached</string>
    <string>-l</string>
    <string>127.0.0.1</string>
    <string>-vv</string><!-- エラーや警告、コマンド等を出力するオプション -->
  </array>
  <key>StandardOutPath</key>
  <string>/usr/local/var/log/memcached.log</string>
  <key>StandardErrorPath</key>
  <string>/usr/local/var/log/memcached.log</string>
  <key>RunAtLoad</key>
  <true/>
  <key>WorkingDirectory</key>
  <string>/usr/local</string>
</dict>
</plist>
launchctl unload ~/Library/LaunchAgents/com.danga.memcached.plist
launchctl load ~/Library/LaunchAgents/com.danga.memcached.plist

memcachedの場合は-vvもしくは-vオプションも必要なので要注意。