Ruby Web Framework「Hanami」

エンジニアの山田と申します。 Ruby の Web フレームワークといえば Ruby on Rails が圧倒…

エンジニアの山田と申します。

Ruby の Web フレームワークといえば Ruby on Rails が圧倒的な人気ですが、
その他にも、Sinatra や Padrino など、いくつかのフレームワークが存在します。

その中で Hanami というフレームワークがあるのを知って、
少し興味があったので、これを機に触ってみました。

 
公式で挙げている特徴は以下の通りです。

・レスポンスが早い
・メモリの消費量が少ない
・セキュア
・シンプルで生産性が高い

あと個人的には名前がかわいらしいのもポイントが高いです。

 

スクリーンショット 2016-06-13 14.59.06

 

余談ですが、この Hanami というフレームワークは、元々 Lotus という名前でしたが、IBM のソフトウェアブランドと名称がかぶっていたため、Hanami に名称変更した経緯があるようです。
※ Hanami は御察しの通り花見のことです。

 
それでは実際に動かしていきましょう。
公式のチュートリアルが充実していたので、
今回はこのチュートリアルを(省略しつつ)ざっとさらってみたいと思います。

1. インストール〜起動
2. ルートURLの表示を変更
3. アクションの作成
4. モデルの作成
5. DB設定
6. マイグレーション
7. ビュー上でデータを表示

 

1. インストール〜起動

まずは Hanami をインストールします。
ruby, gem, bundler, DB(今回はMySQL) がインストール済みであることが前提です。

% gem install hanami

 
次に新規プロジェクトを作成します。
% hanami new bookshelf --database=mysql

 
プロジェクトの中身はこんな感じです。

% tree bookshelf/ -L 1
bookshelf/
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── apps
├── config
├── config.ru
├── db
├── lib
├── public
└── spec

 
あとは Rails を使ったことのある方ならお馴染みの流れです。
% cd bookshelf
% bundle install

 
終わったら起動をしてみましょう。
% bundle exec hanami server

 
ブラウザから
http://localhost:2300
にアクセスしてみます。

スクリーンショット 2016-06-12 20.03.38

無事にデフォルトの画面が出ました!

 

2. ルートURLの表示を変更

次に、先ほど表示されたデフォルトの画面を変更してみます。

 
まずはルーティングを設定します。

apps/web/config/routes.rb

get '/', to: 'home#index'

 
次にコントーラの設定です。以下のファイルを作成します。

apps/web/controllers/home/index.rb

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

    def call(params)
    end
  end
end

 
ビューを設定します。以下のファイルを作成します。

apps/web/views/home/index.rb

module Web::Views::Home
 class Index
   include Web::View
 end
end

 
最後にテンプレートの設定です。以下のファイルを作成します。

apps/web/templates/home/index.html.erb

<h1>Bookshelf</h1>

 
それでは再度

http://localhost:2300

にアクセスしてみます。

スクリーンショット 2016-06-16 15.02.23

ルートで表示されるページが無事変わりました。

 
ところで、ディレクトリやモジュール定義に度々出てくる Web というのは何でしょうか?

説明を読むと、Hanami では Container という概念があり、デフォルトで Web という Container が使用されるようです。

この Container という構成を利用することで、1つのアプリケーション配下に JSON API や管理パネルなどを分けて管理することができるとのこと。
この辺が Hanami の大きな特徴の一つのようです。

 

3. アクションの作成

前のステップでは手動で色々作成しましたが、Rails と同様に Hanami にも便利なジェネレータが装備されています。
今度はそれを利用して、ビューを表示するだけの簡単なアクションを作成してみます。

 
それでは早速ジェネレータでアクションを作りましょう。

% bundle exec hanami generate action web books#index

create spec/web/controllers/books/index_spec.rb
create apps/web/controllers/books/index.rb
create apps/web/views/books/index.rb
create apps/web/templates/books/index.html.erb
create spec/web/views/books/index_spec.rb
prepend apps/web/config/routes.rb

 
ルーティングを確認してみましょう。

apps/web/config/routes.rb

get '/', to: 'home#index'
get '/books', to: 'books#index'

新しいルーティングが増えているのがわかるかと思います。

 
新しいアクションで使うテンプレートの内容を編集しておきます。

apps/web/templates/books/index.html.erb

<h1>Bookshelf</h1>
<h2>All books</h2>
<div id="books">
  <div class="book">
    <h3>Patterns of Enterprise Application Architecture</h3>
    by <strong>Martin Fowler</strong>
  </div>
  <div class="book">
    <h3>Test Driven Development</h3>
    by <strong>Kent Beck</strong>
  </div>
</div>

 
新しく作成したアクションにアクセスします。
http://localhost:2300/books

スクリーンショット 2016-06-16 15.28.44

ちゃんと新しいアクションが動いて表示されてますね!

 

4. モデルの作成

静的なページを表示する部分はできたので、今度はDBから取得したデータを表示するようにしていきます。

 
まずはモデルの作成です。こちらもジェネレータが用意されています。

% bundle exec hanami generate model book

create lib/bookshelf/entities/book.rb
create lib/bookshelf/repositories/book_repository.rb
create spec/bookshelf/entities/book_spec.rb
create spec/bookshelf/repositories/book_repository_spec.rb

 
生成されたモデルの内容を変更しておきます。

lib/bookshelf/entities/book.rb

class Book
  include Hanami::Entity
  attributes :title, :author
end

 

5. DB設定

モデルが作成できたので、DBの準備をしましょう。

 
Hanami では、プロジェクト直下に
.env
.env_development
.env_test
というファイルが用意されており、使用する環境に応じてそれぞれ編集が必要になります。
今回は開発環境なので、.env_development の内容を編集しておきます。

 
以下のキーの部分をフォーマットに沿って変更してください。

BOOKSHELF_DATABASE_URL=”[ADAPTER]://[DATABASE_USER]:[DATABASE_USER_PASSWORD]@[HOST]:[PORT]/[DATABASE_NAME]”

例えば、今回は MySQL を使用しているので、

BOOKSHELF_DATABASE_URL=”mysql2://dbuser:dbpass@localhost:3306/bookshelf_development”

のような感じになります。

 
設定ができたらデータベースを生成しましょう。

% bundle exec hanami db create

特にエラーが出なければ DB に新しいデータベースが作成されているはずです。

 

6. マイグレーション

さて、データベースを作成しましたが、まだ中身は空の状態です。

 
テーブルなどを用意するためにマイグレーションファイルを作成します。
% bundle exec hanami generate migration create_books

create  db/migrations/20160616014940_create_books.rb

 
生成されたファイルの内容を以下のように編集します。

db/migrations/20160616014940_create_books.rb

Hanami::Model.migration do
  change do
    create_table :books do
      primary_key :id
      column :title,      String,   null: false
      column :author,     String,   null: false
    end
  end
end

 
これで準備は完了です。マイグレーションを実行します。
% bundle exec hanami db migrate

スクリーンショット 2016-06-16 21.48.32

ばっちりできてますね!
Rails の場合はこれで OK なのですが、Hanami ではもう1ステップあるようです。

 
bookshelf.rb の mapping do 〜 end の部分を以下のように変更します。

lib/bookshelf.rb

...
mapping do
  collection :books do
    entity     Book
    repository BookRepository

    attribute :id,         Integer
    attribute :title,      String
    attribute :author,     String
  end
end
...

エンティティとDBのカラムのマッピングの設定とのことですが、柔軟性を持たせるために用意されているのでしょうか?
collection, repository と馴染みない部分も出てきました。
このあたりはもう少し触ってみないとわからないですね。。

 

7. ビュー上でデータを表示

だいぶ長くなってしまいました・・これが今回最後のステップです。

 
あらかじめ表示するためのサンプルデータを用意しておきます。

% bundle exec hanami console
>> BookRepository.all
=> []
>> book = Book.new(title: 'TDD', author: 'Kent Beck')
=> #<Book:0x007fbde5a12818 @id=nil @title="TDD" @author="Kent Beck">
>> BookRepository.create(book)
=> #<Book:0x007fbde5a09678 @id=1 @title="TDD" @author="Kent Beck">
>> BookRepository.find(1)
=> #<Book:0x007fbde5a00a50 @id=1 @title="TDD" @author="Kent Beck">

コンソールを動かしてみて思ったのは、
Rails の Model の役割が entity と repository に分割されているようなイメージでしょうか。
前段のマッピングはその紐付けのために必要だったみたいですね。

 
次に表示するためのビューを編集します。

apps/web/templates/books/index.html.erb

<h1>Bookshelf</h1>
<h2>All books</h2>

<div id="books">
  <% if books.any? %>
    <% books.each do |book| %>
      <div class="book">
        <h2><%= book.title %></h2>
        <p><%= book.author %></p>
      </div>
    <% end %>
  <% else %>
    <p class="placeholder">There are no books yet.</p>
  <% end %>
</div>

 
このままでは、ビューが books という変数を解決できません。
コントローラから教えてあげましょう。

apps/web/controllers/books/index.rb

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

    expose :books

    def call(params)
      @books = BookRepository.all
    end
  end
end

expose というのは @books というインスタンス変数を外側に見せるための設定のようです。
この設定がないとビューから books を参照することができません。

 
それではサーバを起動して
http://localhost:2300/books
にアクセスしてみましょう!

スクリーンショット 2016-06-16 21.42.55

・・若干微妙な表示になりましたが、
先ほど入れたテストデータがちゃんと表示されていますね!

 
チュートリアルはこの後フォームの作成など続いていくのですが、
DB〜ビューまで繋がったので、いったんこの辺にしておきます。

 

最後に

駆け足、かつ途中までですが、チュートリアルを通して Hanami を触ってみました。

ところどころ独特な部分はありましたが、
思っていた以上に Rails に近い感覚で進められたので、とても使いやすい印象です。

バージョンは0.73なので、現時点で案件に導入するのは難しいかもしれませんが、
Containerの概念や柔軟性を持たせている点、公式で言われているパフォーマンス面の強みはとても興味深いものです。

個人的に色々な機能を試して、Rails と比較してのメリット・デメリットを感じることができたらと考えてます!