My Brain is Open.

思いついたことを適当に列列と

rvm+passenger3+nginxで作るRailsアプリケーションサーバ

  • rvm : Ruby Version Manager。コマンド一つでRubyバージョンを切り替えたりgemsetとかを切り替えたりできます。
  • nginx : 軽量WebProxy。HTTPサーバとしても使えます。
  • Passenger : mod_railsとも。Ruby on Railsをデプロイするときに超便利。

まぁ、ここを見ればとりあえずはいいわけですが。

[RVM Document : Passenger]
http://rvm.beginrescueend.com/integration/passenger/

僕の方ではこうしました。

  • RVMはSystem Wide Install ( prefix = /usr/local/rvm )
  • アプリケーション用のバージョンとgemsetをインストールしておく
rvm install 1.9.2-head
rvm use 1.9.2-head
rvm gemset create my_app
rvm gemset use my_app
cd /RailsApp/Path/
gem install bundler # (Rails3の場合)
bundle install # (Rails3の場合)
  • nginx は既存のアプリケーションと分けるためにホームディレクトリ以下におく ( /home/user/nginx )
  • nginx.confには次のような内容を記述
passenger_root /usr/local/rvm/gem/1.9.2-head@my_app/gems/passenger-3.0.2 ;
passenger_ruby /usr/local/rvm/wrapper/1.9.2-head/ruby ;

location / {
    port 8080;
    # ...
    passenger_enabled on;
    root /RailsApp/Path/public;
}

これでアプリケーションを動かすことができる。

rvmを使った理由は、以下の環境を同時に構築するため。

むちゃくちゃさせてる気がするけど、動いてるからよし。

references_oneの挙動

MongoDBの(埋め込みでない)参照を用いる references_one の挙動について。

class Person
  include Mongoid::Document
  field :name
  references_one :car
end

class Car
  include Mongoid::Document
  field :name
  referenced_in :person
end

モデルがこうなっているときに、次のような車の取り合いをやってみますw

p1 = Person.new(:name => "hoge") # => BSON::ObjectId('...000001')
p2 = Person.new(:name => "fuga") # => BSON::ObjectId('...000002')
c = Car.new(:name => "toyoda") # => BSON::ObjectId('...000003')

p1.car = c
p1.save #=> true
c.save #=> true
c.person_id #=> BSON::ObjectId('...000001')

p2.car = c
p2.save #=> true
c.person_id #=> BSON::ObjectId('...000002')

p1.car == c #=> true

参照される側で参照元IDを取得できるのですが、それは一旦最新のものに置き換わってしまうようです。
一方で、ちゃんと参照元から車を参照できることに変わりはない…と。
そうなると、場合によっては validates_uniqueness_of :car とかで制限することになるんでしょうか。
今のところ譲渡するようなロジックを組む予定はないですが、こういう所は埋込みにするなり考えないとダメですね。

埋め込みドキュメントの参照

Mongoidのドキュメントに微妙に書いてないのでメモ。

class Person
  include Mongoid::Document
  
  embeds_many :phones
end

class Phone
  include Mongoid::Document
  field :number
  embedded_in :person, :inverse_of => :phones
end

となっているときに、ある電話番号の電話を所有するような人物を探す方法はどうすればいいだろう。
原点に立ち戻ってMongoDBの"dot notation"を使って以下のようなクエリでさがすことができる。

Person.where("phones.number" => "03-XXXX-XXXX")

Mongoidのドキュメントだと、埋め込みドキュメントへのインデックス作成のところで少し出てくるけど、肝心のクエリのところに書いてないから少し戸惑ってました。

なにはともあれ、解決。

Mongoidによるatomicな更新

MongoDBの特徴として、RDBMSではおおよそ用いられる以下の方法が使えない。

  1. joinがない
  2. transactionがない

そのかわり、次の特徴がある

  1. 埋め込みドキュメントが使える
  2. ドキュメントの各要素について、atomicな更新ができる

前者は例えば「紐付けて必ず参照するドキュメント」を内包させておくことで、一度のクエリで紐付く情報を一括取得できる。
後者は行ロックしてupdate(ロジック的には行ロック、データコピー、要素の更新、DB上書きの順になるのかな)などを行わず、各要素を上書きしてしまう方法である。

これらについて困っていたことがあって、まず、あるドキュメントの普通の要素を上書きする場合は以下で可能だった。

@parent = Parent.all.first # 既存ドキュメントの取り出し。
puts @parent.name #=> hogehoge
@parent.update_attributes({ :name => "fugafuga" })
puts @parent.name #=> fugafuga
Parent.all.first.name #=> fugafuga # @parent.save してないが上書きされる

一方で、埋め込みドキュメントに対する push (1要素追加するatomicな操作)がわからなかったのだが、公式ドキュメントの最後にちらっと出てくる方法で次のように解決した。

@parent = Parent.all.first # 既存ドキュメントの取り出し。
p @parent.children.size #=> 1
@parent.children.create({ :name => "fuga-chan" })
p @parent.children.size #=> 2
Parent.all.first.children.size #=> 2 # @parent.save してないがDB上も挿入されている

まだpullとか判っていない部分もあるが、とりあえずatomicな操作が可能であることが分かってよかった。

参照のあるモデルをviewから作る

ParentとChildの関係にあるモデルの作り方と簡単な使いかたを前回書いたが、では画面からそれらを登録していくにはどうすればいいか。

考えられる方法はとりあえず二つ

  1. 作成済みのParentのページにChildを作るリンクを作成する
  2. Childを新規作成するページで既存のParentを指定できるようにする

モデルは仮に以下のようにしておく

class Parent
  include Mongoid::Document
  field :name, :type => String
  field :age, :type => Integer
  references_many :child
end

class Child
  include Mongoid::Document
  field :name, :type => String
  field :age, :type => Integer
  referenced_in :parent
end

1.についてはParentのObjectIDを引き渡せば良いと思われる。後は2.の途中からと同じ手順になるはず。
2.について、まずは(scaffoldで作った場合の)newをいじる。

Parentをプルダウンで選択するようにするには、selectを用いる。viewを全部書くと非常に面倒なので部分的に書くと、以下のような感じ。

form_for :child do |f|
  f.label :parent
  f.select :parent, Parent.all.map { |parent| [parent.name, parent.id] }
end

意味は params[:child][:parent]に関するセレクタを[name, id]の組み合わせの配列で作成する、ということになる。
ここで、配列 [ [n1,m1], [n2,m2] ] が渡されたときのselectは「表示"n1"に対してvalue="m1", 表示"n2"に対してvalue="m2"」となり、上記の例で実際にparams[:child][:parent]に渡される値はparent.id(つまり選んだParentのBSON::Objectid)となる。
このままだと、controller側のChild.newで:parentが余計に含まれてしまい失敗してしまうので、:parentを消しつつ、対応するParentを呼び出して参照関係を作る。

@parent = Parent.criteria.id(params[:child][:parent]).first
params[:child].delete(:parent)
@child = Child.new(params[:child])
@parent.child << @child

@child.save

諸々省略したが、これで参照を作る事ができる。

Mongoidのモデル間参照について備忘録

参照関係(Relation)を作るにはMongoidのモデルで以下を指定する。

  • references_one
  • references_mary
  • referenced_in

使い方に注意が必要で、例えば Parent, Childな関係の場合

class Parent
  include Mongoid::Document
  references_many :child
end

class Child
  include Mongoid::Document
  referenced_in :parent
end

という事になる。embed_many, embedded_in の関係と対比するとわかりやすいかも。
この時、普通のRDBだとChildテーブルにparent_idが入る点ではMongoDBも同じだが、Mongoidで違う点は、参照関係を作る際にEmbedを踏襲する点。
上記の例だと、こんな風になる。

@parent = Parent.new
@child = Child.new
@parent.child << @child
# @child.parent = @parent ではない

ちなみに、コメントアウトした方でもエラーにならない。しばらく迷ったのは秘密。

Parentを変更する場合は単純にもう一度実行する。

@parent_other = Parent.new
@parent_other.child << @child
@parent.child.first #=> nil

自動的に以前の @parent からは排除された形になる。