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とタイムスタンプを入れてみましょう。

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ならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。

*1:cygwinrailsに必要なパッケージは、ruby, gcc, make, swig, sqlite3, libsqlite3-develなど