merbを使ってみる
rails風にwebアプリを作成できる話題のmerbを使ってみました。
インストール
すでにgem,rails,mongrelが入っているcygwin上にいれてみました*1。
まずはgemの更新
gem update gem clean
そしてmerbを入れる
gem install merb
入ったのはmerb-1.0.1でした。デフォルトでmerb_datamapper、do_sqlite3が入るようです。しかし、riの生成段階でmerb-slicesが失敗しました。
そこでmerb-slicesを削除して、rdoc生成しなおし
gem uninstall merb-slices gem rdoc --all
そしてmerb-slicesを入れなおす
gem install merb-slices
プロジェクト作成
インストールすると、コード生成用のmerb-genと実行用のmerbコマンドが入ります。
アプリケーション生成では、rails風なディレクトリ構成であるappと、より単純化したflatなどがあるようです。今回はappでやってみます。
merb-gen app myhello cd myhello
実行
merbコマンドでwebサーバが立ち上がります
merb
cygwinの場合、/home/xxxがrwxrwxrwxだとグループ書き込みが危険だよエラーが出て、停止します。そこでchmodしておき、再度merbを実行しました。
chmod 755 ~ merb
メッセージががーっとでてポート4000番で立ち上がったようです。
そこでブラウザでhttp://localhost:4000/にアクセスすると、「Flesh Merb App」ページが表示され、
Exception: No routes match the request: /
となってました。
いろいろ作成
最初のモデルオブジェクトとしてブックマークアプリ風なlinkをresourceで作ってみます。
$ merb-gen resource link Loading ... [ADDED] spec/models/link_spec.rb [ADDED] app/models/link.rb [ADDED] spec/requests/links_spec.rb [ADDED] app/controllers/links.rb [ADDED] app/views/links/index.html.erb [ADDED] app/views/links/show.html.erb [ADDED] app/views/links/edit.html.erb [ADDED] app/views/links/new.html.erb [ADDED] app/helpers/links_helper.rb resources :links route added to config/router.rb
というファイルが追加されます。表示メッセージが色つきなのも面白い。
動かしてるmerbサーバも自動で適用するようで、そのままで http://localhost:4000/links/ にアクセスすると、index.html.erbの内容が表示されます( http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb みて編集しろという素っ気ないものですけど)。
model編集
resourceで生成されたlink.rbの内容は以下のようになってました。
class Link include DataMapper::Resource property :id, Serial end
url、titleとタイムスタンプを入れてみましょう。
- DataMapperのドキュメント: http://datamapper.org/doku.php
class Link include DataMapper::Resource property :id, Serial property :url, URI, :nullable => false, :writer => :protected property :title, Text, :nullable => false, :default => "no title", :lazy => false property :timestamp, DateTime, :nullable => false validates_is_unique :url end
単一モデルでなるべく制約を入れるようにやってみました。
ソースコードの更新は自動で反映するようですが、エラーが出るコードを保存してしまうと、merbは落ちるようです(svn upとかで更新する場合なら有効かも)。
対話環境でデータを入れてみましょう。
merb -i
webratを入れろと出て、終了しました。で、webratを追加しよう
gem install webrat
依存関係の中にネイティブライブラリnokogiriがあり、そのビルドにcygwinでは、libxml2-develとlibxslt-develが必要のようです(setup.exeで入れたらxorgまで入ってしまったorz)。
コンソールを立ち上げる前に一度dbを作ります。
rake db:create merb -i
以下のようにデータを作ってみます。
link = Link.new :url => "http://www.yahoo.com", :title => "Yahoo!", :timestamp => DateTime.now link.save link.id
idが振られていれば成功です。同じurlを持つ別のlinkを作ってsaveして失敗する(falseが返る)のを試してみたりました。
検索はidで検索する場合はget、一つだけならfirst、全部ならallです。
Link.get(1) Link.first(:url => "http://www.yahoo.com") Link.all(:url.like => "%yahoo%", :url.like => "%com%") # and 結合
Controller編集
ブラウザでこのLinkオブジェクトの表示や編集を可能にするために、erbファイルを修正することになります。
その前に、timestampの処理をcontrollerに追加しておきます。
生成されたデフォルトのコントローラlinks.rbは以下のようなものです
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
ここのcreateとupdateにlink.timestamp = DateTime.nowを入れました。
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) @link.timestamp = DateTime.now if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link @link.timestamp = DateTime.now if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
良くわからないけど、providesについてるコメントアウトもはずしました。
Viewの編集
erbファイルを、http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb を見ながら修正することになります。
erbファイルは4つ(index.html.erb, show.html.erb, new.html.erb, edit.html.erb)あり、最初から埋めておいてもいいんじゃね?という感じではありますが。
index.html.erb
<h1>Links controller, index action</h1> <table> <tr> <th>URL</th> <th>Title</th> <th colspan="3">Actions</th> </tr> <% @links.each do |link| %> <tr> <td><%=h link.url %></td> <td><%=h link.title %></td> <td><%= link_to 'Show', resource(link) %></td> <td><%= link_to 'Edit', resource(link, :edit) %></td> <td><%= delete_button(link, "Delete") %></td> </tr> <% end %> </table> <%= link_to 'New', resource(:links, :new) %>
show.html.erb
<h1>Links controller, show action</h1> <h3><%=h @link.title %></h3> <ul> <li><%=h @link.url %></li> <li><%=h @link.timestamp %></li> </ul> <%= link_to 'Back', resource(:links) %>
new.html.erb
<h1>Links controller, new action</h1> <%= form_for(@link, :action => resource(:links) ) do %> <p> <%= text_field :url, :label => "URL" %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Create" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
edit.html.erb
<h1>Links controller, edit action</h1> <%= form_for(@link, :action => resource(@link) ) do %> <p> <%=h @link.url %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Update" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
ブラウザで編集ができるようになりました。
感想
自動テストにあたるものがRSpecだったりと、railsとは若干セマンティクスが違う部分があります。けど、手順としては大体同じようにできるので、ドキュメントを探しながらやっていけば、railsの経験があればかんたんに使えるようになるのではないでしょうか(実際、上に上げた二つのドキュメントのリンクとrake --tasksくらいの情報だけでここまでできたし)。
ドキュメントに関しては、railsよりはよいのではないかなとも思います。とくにActiveRecordとくらべると、DataMapperはいろいろ探しやすい感じでした。
merbの機能としては、appのように作ってappに組み込むことができるようにするsliceとかあって、いろいろ面白そうです。
タグやコメント、アカウント管理のようなシステムはpluginとかでは(コードの更新等で)かなり苦しいのですが、sliceならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。