RubyのWebフレームワークHanamiをさわってみた。

What is Hanami?

Hanamiは、Ruby製のMVCフレームワークである。
多くの小さなライブラリから構成される。
シンプルさと安定したAPI、最小限のDSLを持っている。
Lotusという名前から変更されたみたい。
http://hanamirb.org/guides/


経緯

Qiitaで見かけ、気になったので少し触ってみた。
ちなみに沖縄はさくらの花見シーズンです。7分咲らしいです。


開発環境

Ruby: 2.3.0
PostgreSQL: 9.4.9
Hanami: 0.9.2


Getting Started

まずはGemでHanamiのインストールをします。
そして今回使用する、新しいアプリケーションを作成します。

# Hanamiのインストール.
gem install hanami

# 新しいHanamiアプリケーションの作成. PostgreSQLを指定.
hanami new myapp --db=postgresql
cd myapp



初期設定

アプリケーション実行までの流れです。

# ライブラリをインストール.
bundle install

# データベースの作成.
bundle exec hanami db create

# データベースの削除.
# bundle exec hanami db drop

# アプリケーションの実行.
bundle exec hanami server

http://localhost:2300/
Hanamiアプリケーションのトップページが表示されれば成功です。



CRUD機能を作ってみる.

モデルの作成.
# Postモデルの作成.
bundle exec hanami generate model post

# マイグレーションファイル作成.
bundle exec hanami generate migration create_posts


作成されたマイグレーションファイルにカラムを記述。
今回は、IDとタイトル、内容、作成・更新日時をもつテーブルにします。

# db/migrations/************_create_posts.rb

Hanami::Model.migration do
  change do
    create_table :posts do
      primary_key :id
      column :title, String, null: false
      column :body, String, null: false
      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false
    end
  end
end


マイグレーションを反映させる。

bundle exec hanami db prepare


データを挿入してみる。

# コンソールを開く.
bundle exec hanami console

# 適当に5件挿入します.
irb(main):001:0> 1.upto(5) do |i|
irb(main):002:1* PostRepository.new.create(title: "title#{i}", body: "body#{i}")
irb(main):003:1> end

# 全件表示される.
irb(main):004:0> PostRepository.new.all

PostRepositoryのallメソッドで全件表示できれば成功です。


一覧表示のアクションを作成する。
bundle exec hanami generate action web posts#index


postsのコントローラを編集する。

# apps/web/controllers/posts/index.rb

module Web::Controllers::Posts
  class Index
    include Web::Action

    expose :posts

    def call(params)
      @posts = PostRepository.new.all
    end
  end
end

exposeメソッドは、インスタンス変数@postsの値をViewに渡すらしい。
Viewではインスタンス変数ではなく、ローカル変数postsで受け取れるらしい。

# apps/web/templates/posts/index.html.erb

<h1>Posts Listing.</h1><br />
<table cellpadding="5">
  <tr>
    <th>id</th>
    <th>title</th>
    <th>body</th>
    <th>created_at</th>
    <th>updated_at</th>
  </tr>
  <% posts.each do |post| %>
  <tr>
    <td><%= post.id %></td>
    <td><%= post.title %></td>
    <td><%= post.body %></td>
    <td><%= post.created_at %></td>
    <td><%= post.updated_at %></td>
  </tr>
  <% end %>
</table>

postsテーブルの一覧を表示するページ完成。
http://localhost:2300/posts で表示できれば成功。


新規作成のアクションを作成する。
# 新規作成画面表示のnewアクションを作成.
bundle exec hanami generate action web posts#new

# 送られたデータから作成するcreateアクションを作成.
bundle exec hanami generate action web posts#create --method=post


formhelperを使って新規画面を作成します。

# apps/web/templates/posts/new.html.erb

<h1>Add Post.</h1><br />
<%=
  form_for :post, '/posts' do
    div class: 'input' do
      label      :title
      text_field :title
    end

    div class: 'input' do
      label      :body
      text_field :body
    end

    div class: 'controls' do
      submit 'Create Post'
    end
  end
%>


formから渡ってきたパラメータを保存するアクションです。

# apps/web/controllers/posts/create.rb

module Web::Controllers::Posts
  class Create
    include Web::Action

    expose :post

    def call(params)
      @post = PostRepository.new.create(params[:post])
      redirect_to '/posts'
    end
  end
end

Railsは、モデルのインスタンスを作成してsaveメソッドで保存しますが、
Hanamiは、createメソッドに引数が渡った時点で保存されるみたいです。


新規投稿画面と保存するページ完成。
http://localhost:2300/posts/new で表示でき、
フォーム送信で一覧に追加されれば成功。

削除のアクションを作成する。

次に、一覧からpostを消すことができるようにします。

# HTTPメソッドはdeleteを指定.
bundle exec hanami generate action web posts#destroy --method=delete


コントローラは、パラメータからdeleteメソッドに渡してあげれば消えます。
リダイレクトは/postsでいいと思います。

# apps/web/controllers/posts/destroy.rb

module Web::Controllers::Posts
  class Destroy
    include Web::Action

    expose :post

    def call(params)
      @post = PostRepository.new.delete(params[:id])
      redirect_to '/posts'
    end
  end
end


特に削除用のページは作らず、posts/indexのページ端に削除ボタンを作ります。

# apps/web/templates/posts/index.html.erb

<h1>Posts Listing.</h1><br />
<table cellpadding="5">
  <tr>
    <th>id</th>
    <th>title</th>
    <th>body</th>
    <th>created_at</th>
    <th>updated_at</th>
  </tr>
  <% posts.each do |post| %>
  <tr>
    <td><%= post.id %></td>
    <td><%= post.title %></td>
    <td><%= post.body %></td>
    <td><%= post.created_at %></td>
    <td><%= post.updated_at %></td>
    <td><%= form_for :post, "posts/#{post.id}", method: :delete do submit 'DELETE' end %></td>
  </tr>
  <% end %>
</table>



更新のアクションを作成する。

更新画面と更新用のアクションを作ります。

# editは編集画面を表示します.
bundle exec hanami generate action web posts#edit

# HTTPメソッドはputを指定.
bundle exec hanami generate action web posts#update --method=put


# apps/web/templates/posts/edit.html.erb

<h1>Edit Post.</h1><br />
<%=
  form_for :post, "/posts/#{params[:id]}", method: :put do
    div class: 'input' do
      label      :title
      text_field :title
    end

    div class: 'input' do
      label      :body
      text_field :body
    end

    div class: 'controls' do
      submit 'Update Post'
    end
  end
%>

更新用のformもヘルパーを使って、メソッドをputにしただけです。

# apps/web/controllers/posts/update.rb

module Web::Controllers::Posts
  class Update
    include Web::Action

    def call(params)
      @post = PostRepository.new.update(params[:id], params[:post])
      redirect_to '/posts'
    end
  end
end

更新処理は、引数にidとフォームデータ渡すだけでできました。
詳細ページはないので、リダイレクトは/postsに飛ばします。


感想

RailsSinatra触ったりしてると使いやすいと思う。
個人的にはアクション毎にClassがあるのがかっこいいと思いました。
また簡単なもの作ってみたいと思います。