Sinatra + Haml で MongoDB を使う - Mongoid, MongoMapper で関連とコンポジションを実装
Sinatra で MongoDB を使うために Mongoid と MongoMapper を試してみました。
以下のようなモデル構成を実装する事にします。
環境は以下の通り、テンプレートエンジンに Haml を使っています。
サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20110306/
事前準備
まず、今回使用するパッケージを gem でインストールしておきます。
インストール例
> gem install sinatra > gem install haml > gem install mongoid > gem install mongo_mapper
Mongoid の場合
Mongoid のモデルクラスは以下のようにして定義します。
- Mongoid::Document を include する事でモデルクラスを定義
- field でフィールドを定義
- :type を使って型を指定
- embeds_many と embedded_in でコンポジションを定義(Book と Comment)
- belongs_to_related で一方向への関連を定義(Comment から User へ)
models_mongoid/book.rb(Mongoid)
class Book include Mongoid::Document field :title field :isbn # コンポジションの定義 embeds_many :comments end
models_mongoid/comment.rb(Mongoid)
class Comment include Mongoid::Document field :content field :created_date, :type => Date # コンポジションの定義 embedded_in :book, :inverse_of => :comments # User への関連 belongs_to_related :user end
models_mongoid/user.rb(Mongoid)
class User include Mongoid::Document field :name end
Sinatra による実装は以下です。
Haml テンプレートを使うには haml メソッドにテンプレートのシンボルとオプション、テンプレート内で使用するパラメータを渡します。
以下では全ての Book, User オブジェクト(:books, :users)とコメント追加先のパス(:action)を渡しています。
Mongoid の設定は configure に渡すブロック内で指定できます。以下では使用する DB に book_review を指定しています。
sample_mongoid.rb(Sinatra)
require "rubygems" require "sinatra" require "haml" require "mongoid" require "models_mongoid/book" require "models_mongoid/user" require "models_mongoid/comment" # Mongoid 設定 Mongoid.configure do |config| config.master = Mongo::Connection.new.db('book_review') end # Top ページ get '/' do haml :index, {}, :books => Book.all.order_by([[:title, :asc]]), :users => User.all.order_by([[:name, :asc]]), :action => '/comments' end ・・・ # Book 追加 post '/books' do Book.create(params[:post]) redirect '/books' end # Comment 追加 post '/comments' do b = Book.find(params[:post][:book_id]) b.comments << Comment.new(:content => params[:post][:content], :created_date => Time.now, :user_id => params[:post][:user_id]) b.save #以下でも可 # Book.find(params[:post][:book_id]).comments.create(:content => params[:post][:content], :created_date => Time.now, :user_id => params[:post][:user_id]) redirect '/' end ・・・
Top ページの Haml テンプレートは以下の通りです。
注意点として、= や #{・・・} をそのまま使うとデフォルトでは HTML エスケープしてくれないので、クロスサイトスクリプティングなどの対策に HTML エスケープを行いたい場合、:escape_html オプションを true に設定するか(Sinatra の haml メソッドの第2引数で渡せば良い)、代わりに &= や & #{・・・} を使います。
今回は、後者のやり方で実装してみました。
views/index.haml(Haml)
.menu Menu %ul %li %a(href="/books") Books List %li %a(href="/users") Users List .list Book Comments %form.post(action='#{action}' method='post') %select(name='post[user_id]') - users.each do |u| %option(value='#{u._id}')&= u.name %select(name='post[book_id]') - books.each do |b| %option(value='#{b._id}')&= b.title %input(name='post[content]' type='text') %input(type='submit' value='Add') - books.each do |b| %ul %li&= b.title %ul - b.comments.each do |c| %li & #{c.content} : #{c.user.name}, #{c.created_date}
実行例
MongoDB を実行しておきます。
> mongod -dbpath db
Sinatra を実行します。
> jruby sample_mongoid.rb
実行画面は以下のようになります。
ちなみに、mongo コマンドを使って DB 内の books コレクションの内容を確認してみると、関連とコンポジションの実現方法の違いがよく分かると思います。
mongo コマンドで books コレクションの内容を確認
> mongo ・・・ > use book_review switched to db book_review > db.books.find() { "_id" : "4d7253961875e20940000003", "comments" : [{ "content" : "check", "created_date" : ISODate("2011-03-06T00:00:00Z"), "user_id" : "4d7253811875e20940000001", "_id" : "4d7253b31875e20940000006" }], "isbn" : "1234", "title" : "Railsレシピブック" } ・・・
MongoMapper の場合
MongoMapper のモデルクラスは以下のようにして定義します。
- MongoMapper::Document や MongoMapper::EmbeddedDocument を include する事でモデルクラスを定義
- key でフィールドを定義
- 第2引数で型を指定
- many と MongoMapper::EmbeddedDocument でコンポジションを定義(Book と Comment)
- belongs_to で一方向の関連を定義(Comment から User へ)
なお、以下では _id を String 型で定義していますが、これは Mongoid のサンプルで使った DB をそのまま使えるようにするためです。(MongoMapper の場合、_id はデフォルトの ObjectId 型で定義されるため、このままでは Mongoid 版で使った DB が使えません)
models_mongomapper/book.rb(Mongoid)
class Book include MongoMapper::Document # デフォルトで _id は ObjectId 型になるので String を指定 key :_id, String key :title key :isbn # コンポジションの定義 many :comments end
models_mongomapper/comment.rb(Mongoid)
class Comment include MongoMapper::EmbeddedDocument # デフォルトで _id は ObjectId 型になるので String を指定 key :_id, String key :content key :created_date, Date # User への関連 belongs_to :user end
models_mongomapper/user.rb(Mongoid)
class User include MongoMapper::Document # デフォルトで _id は ObjectId 型になるので String を指定 key :_id, String key :name end
Sinatra による実装は以下。
Mongoid 版とほとんど同じですが、order の指定の仕方が異なります。
なお、Haml テンプレートは Mongoid 版と共通です。
sample_mongomapper.rb(Sinatra)
require "rubygems" require "sinatra" require "haml" require "mongo_mapper" require "models_mongomapper/book" require "models_mongomapper/user" require "models_mongomapper/comment" # MongoMapper 設定 MongoMapper.connection = Mongo::Connection.new('localhost') MongoMapper.database = 'book_review' # Top ページ get '/' do haml :index, {}, :books => Book.all(:order => 'title'), :users => User.all(:order => 'name'), :action => '/comments' end ・・・ # Comment 追加 post '/comments' do b = Book.find(params[:post][:book_id]) b.comments << Comment.new(:content => params[:post][:content], :created_date => Time.now, :user_id => params[:post][:user_id]) b.save redirect '/' end ・・・
実行例
> jruby sample_mongomapper.rb