roda-sequel-stack を使ってみた

Ruby で ActiveRecord じゃなくて Sequel を使いたい場合のウェブフレームワークは何がいいかなぁ。Hanami かな。まあ Rails で Sequel 使ってもいいんだけども。

とつぶやいたら本人直々に

I recommend Roda+Sequel https://roda.jeremyevans.net

と、roda-sequel-stack を教えてもらったので使ってみる。

初期設定

git clone でローカルにコピー。

% git clone https://github.com/jeremyevans/roda-sequel-stack.git hoge
% cd hoge

roda-sequel-stack 自体の Git 履歴は要らないので削除して新しくリポジトリを作った方がいいかも。

% rm -rf .git
% git init
% git commit --allow-empty -m 'Initial commit'

Hoge アプリとしてセットアップ。

% rake 'setup[Hoge]'
% bundle install

デフォルトだと PostgreSQL だけど MySQL を使いたいので変更。

-gem 'sequel_pg', '>= 1.8', require: 'sequel'
+gem 'ruby-mysql', '>= 4.0', require: 'mysql'

簡単のために MySQL は Docker Compose を使用。

[docker-compose.yml]

services:
  db:
    image: mysql:8.0.33
    ports:
      - "127.0.0.1:13306:3306"
    volumes:
      - db:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
  db:

データベースとユーザーを作成。

% docker-compose up -d
% docker-compose exec db mysql
mysql> create database hoge_production;
mysql> create database hoge_development;
mysql> create database hoge_test;
mysql> create user hoge;
mysql> grant all on `hoge\_%`.* to hoge;

データベース接続情報を設定。

[.env.rb]

...
-  ENV['HOGE_DATABASE_URL'] ||= "postgres:///hoge_test?user=hoge"
+  ENV['HOGE_DATABASE_URL'] ||= "mysql://hoge@127.0.0.1:13306/hoge_test"
...
-  ENV['HOGE_DATABASE_URL'] ||= "postgres:///hoge_production?user=hoge"
+  ENV['HOGE_DATABASE_URL'] ||= "mysql://hoge@127.0.0.1:13306/hoge_production"
...
-  ENV['HOGE_DATABASE_URL'] ||= "postgres:///hoge_development?user=hoge"
+  ENV['HOGE_DATABASE_URL'] ||= "mysql://hoge@127.0.0.1:13306/hoge_development"
...

rackup をインストールしてサービスを起動。

% gem install rackup
% rackup -p 8080

ここでブラウザで http://localhost:8080 にアクセスすると次のようなページが表示される。

よくあるサンプルサービスを作ってみる

マイグレーション

Article モデルを作成する。サンプルのマイグレーションファイルは削除。

% rm migrate/001_tables.rb

新しくマイグレーションファイルを作成。Sequel のマイグレーションファイルの書き方は Sequel の schema_modification.rdoc を参照。

[migrate/20230611001_articles.rb]

Sequel.migration do
  change do
    create_table :articles do
      primary_key :id
      String :title
      String :body, size: 1024
    end
  end
end

マイグレーション実行。

% rake dev_up

モデル

Article モデル作成。

[models/article.rb]

class Article < Sequel::Model
end

ルーティング(コントローラー)

ルートファイル作成。Rails でいうところのコントローラー。 書き方については Roda の README.rdoc を。

[routes/articles.rb]

class Hoge
  hash_branch('articles') do |r|
    r.is do
      @articles = Article.all
      view 'index'
    end
  end
end

ビュー

ビューファイル作成。erb ファイルなので Rails のビューファイルとだいたい同じ。

[views/articles/index.erb]

<table>
    <tr>
        <th>タイトル</th>
        <th>作成日時</th>
    </tr>
    <% @articles.each do |article| %>
        <tr>
            <td><%= article.title %></td>
            <td><%= article.created_at %></td>
        </tr>
    <% end %>
</table>

これで http://localhost:8080/articles にアクセスすると記事の一覧が表示されるはずだけど、まだデータが何も無いので何も表示されない。

IRB(コンソール)

rake dev_irb で irb を起動して Article オブジェクトを作ってみる。Rails の rails console みたいなもの。

% rake dev_irb
irb(main):001:0> Article.create(title: 'ほげほげほげほげ', body: "1行目\n2行目", created_at: Time.now)

表示された。

CSS

CSS をいじってテーブルに枠線をつけてみる。

[assets/css/app.scss]

th {
    border: solid 1px
}
td {
    border: solid 1px
}

リンク

タイトルをクリックすると本文が見れるようにしてみる。

[views/articles/index.erb]

-            <td><%= article.title %></td>
+            <td><a href="/articles/<%=article.id%>"><%= article.title %></a></td>

これでタイトルがリンクになった。でもちょっとアレなんで、link_to プラグインを使ってみる。

Article オブジェクトのパスを設定。

[app.rb]

  plugin :link_to
  path Article do |article|
    "/articles/#{article.id}"
  end

link_to メソッドを呼ぶようにビューを書き換え。

[views/articles/index.erb]

-            <td><a href="/articles/<%=article.id%>"><%= article.title %></a></td>
+            <td><%== link_to(article.title, article) %></a></td>

少し簡単になった。

リンクを押したときに本文を表示するためにルートファイルを変更してビューファイルを作成。

[routes/articles.rb]

class Hoge
  hash_branch('articles') do |r|
    r.is do
      @articles = Article.all
      view 'index'
    end
    r.is Integer do |id|
      @article = Article.with_pk!(id)
      view "show"
    end
  end
end

[views/articles/show.erb]

<dl>
    <dt>タイトル</dt>
    <dd><%= @article.title %></dd>
    <dt>作成日時</dt>
    <dd><%= @article.created_at %></dd>
    <dt>本文</dt>
    <textarea readonly><%= @article.body %></textarea>
</dl>

新規登録

さっきは irb から記事レコードを作ったけどブラウザから登録できるページを作ってみる。

[views/articles/new.erb]

<form method="post" action="/articles">
<dl>
    <dt>タイトル</dt>
    <dd><input type="text" name="title"></dd>
    <dt>本文</dt>
    <dd><textarea name="body"></textarea></dd>
</dl>
<input type="submit">
</form>

ルートファイルはこんな感じ。

[routes/articles.rb]

class Hoge
  hash_branch('articles') do |r|
    r.is do
      @articles = Article.all
      view "index"
    end
    r.is Integer do |id|
      @article = Article.with_pk!(id)
      view "show"
    end
    r.is 'new' do
      view "new"
    end
  end
end

これで http://localhost:8080/articles/new にアクセスすると新規登録画面が表示される。

POST を受け付けるようにルートファイルを変更。

[routes/articles.rb]

class Hoge
  hash_branch('articles') do |r|
    r.is do
      r.post do
        Article.create(title: r.params['title'], body: r.params['body'], created_at: Time.now)
        r.redirect
      end
      r.get do
        @articles = Article.all
        view "index"
      end
    end
    r.is Integer do |id|
      @article = Article.with_pk!(id)
      view "show"
    end
    r.is 'new' do
      view "new"
    end
  end
end

でも実際に POST してみると Invalid Security Token というエラーになってしまう。

ビューファイルに csrf_tag を追加。

[views/articles/new.erb]

<form method="post" action="/articles">
<dl>
    <dt>タイトル</dt>
    <dd><input type="text" name="title"></dd>
    <dt>本文</dt>
    <dd><textarea name="body"></textarea></dd>
</dl>
<%== csrf_tag('/articles') %>
<input type="submit">
</form>

これであたらしい記事を作成できるようになった。

おわり

roda-sequel-stack は gem でもないし、Rails みたいなフルスタックフレームワークというよりは、Roda と Sequel やその他ライブラリを使ってフルスタックフレームワークを作るためのサンプルみたいな感じかも。

Sequel は10年以上使ってるし、Roda もなかなか良さそうなので、Rails よりも好きかも。

この続きはcodocで購入