Sinatra を試してみる
Ruby 製のウェブフレームワークというと Rails が有名だが、もっとミニマムに始められるモノはないかというと、Sinatra というフレームワークがある。Python の Flask や Node.js の Express.js などに似たような感じで使える。
今回は Sinatra のサンプルプロジェクトを作りつつ、RubyGems 周りの概念の整理を、Node.js・npm と比較しながら進めていく。また、作成したファイルは全て以下の GitHub リポジトリに格納している。
目次
- Node.js・npm のコマンド整理
- Ruby におけるコマンドの対比
- Sinatra 用のプロジェクトディレクトリを作る
- Puma をインストールする
example-1
: クラシックスタイル・単一 Ruby ファイルで起動するシンプル構成example-2
: モジュールスタイル・Rack を使って起動する- 今回はココまで
Node.js・npm のコマンド整理
まずは Node.js・npm に慣れている人達にとっては当たり前なコマンド類を列挙する。
# Node.js 本体のバージョン管理ツール・ココでは NodeBrew を用いている
$ nodebrew
nodebrew 1.2.0
# システムが利用している Node.js のバージョンを確認する
$ nodebrew list
v18.12.1
current: v18.12.1
# Node.js 本体のバージョン確認
$ node -v
v18.12.1
# パッケージ管理ツールである npm のバージョン確認
$ npm -v
8.19.2
# グローバルインストールされる npm パッケージは以下に格納される
$ npm ls -g
/Users/Neo/.nodebrew/node/v18.12.1/lib
Ruby におけるコマンドの対比
続いて、Ruby における各種コマンドを対比して見てみる。
# Ruby 本体のバージョン管理ツール
$ rbenv -v
rbenv 1.2.0
# システムが利用している Ruby のバージョンを確認する
$ rbenv version
3.2.0 (set by /Users/Neo/.rbenv/version)
# Node.js 本体相当
$ ruby -v
ruby 3.2.0 (2022-12-25 revision a528908271) [x86_64-darwin21]
# npm 相当
$ gem -v
3.4.1
# グローバルインストールされる RubyGems の置き場・この下の gems/ 配下にある
$ gem environment gemdir
/Users/Neo/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0
$ gem list
# Bundler : npm における「ローカルインストール」相当
# Bundler 自体は gem コマンドでグローバルインストールする
$ gem install bundler
# どちらのコマンドも同じモノ
$ bundle -v
Bundler version 2.4.13
$ bundler -v
Bundler version 2.4.13
npm における「ローカルインストール」相当の機能は、gem
コマンドではなく Bundler という RubyGems が担う。Bundler 自体は gem コマンドでグローバルインストールしておく。bundle
と bundler
、どちらも同じコマンドのエイリアスとして動いている。
- 参考 : bundle install先ってどこになるの? - Qiita
- Bundler のパッケージはデフォルトで RubyGems と同じ場所に配置される
- npm のようにプロジェクトディレクトリごとに
node_modules/
を作るのではなく、Deno っぽい感じがデフォルトの動き
- 参考 : bundlerで非推奨になった --path --binstubs - Qiita
bundle install --path vendor/bundle
といったオプションを付ければ、プロジェクトディレクトリ配下にnode_modules/
のような感じでインストールできる- そのオプションが非推奨になった
- 参考 : bundle install時に--path vendor/bundleを付ける必要性は本当にあるのか、もう一度よく考えてみよう - Qiita
bundle install
時に--path vendor/bundle
オプションを付ける必要は基本的になさそう
Sinatra 用のプロジェクトディレクトリを作る
次はプロジェクトのディレクトリを作ってみる。
$ mkdir practice-sinatra && cd $_
# npm init 相当・Gemfile を作る
$ bundle init
Writing new Gemfile to /Users/Neo/Documents/Dev/Sandboxes/practice-sinatra/Gemfile
# npm install --save 相当・Gemfile が更新され Gemfile.lock が生成される
$ bundle add sinatra
# npm install 相当・Gemfile と Gemfile.lock を基に必要な RubyGems がインストールされる
$ bundle install
bundle
(bundler
) コマンドを用いて、npm init
・npm install
相当のことを行っている。前述のとおり、bundle install
コマンドはデフォルトではグローバルな RubyGems と同じ場所にモジュール類を格納しているので、プロジェクトディレクトリ配下に .gitignore
などを書かなくても良い状態。もしパスを自分で設定している場合は、vendor/bundle/
ディレクトリを .gitignore
に入れると良いだろう。
Gemfile
の中身はこのようになっている
# frozen_string_literal: true
source "https://rubygems.org"
gem "sinatra", "~> 3.0"
Puma をインストールする
簡単な Sinatra のコードを書いて実行してみたのだが、エラーが出てしまった。その原因と対策を記す。
アプリケーションサーバを何もインストールしていない場合、$ bundle exec rackup
コマンドなどを叩くと Couldn't find handler for: puma, thin, falcon, webrick.
というエラーが出る。コレは以下の記事が詳しく、Ruby v3 系では WEBrick が標準ライブラリから削除されたため、自分で何らかのアプリケーションサーバ用のモジュールをインストールする必要があるということである。
- 参考 : Ruby 3系 で Sinatra を使う時は WEBrick が標準ライブラリから削除されたので、別でアプリケーションサーバーを入れる必要がある | 高木のブログ
- 参考 : Sinatra: README
Sinatra の公式ガイドでは puma
が推奨されているので、Puma をインストールする (= Gemfile
に記述してやる)。
# Sinatra 推奨のアプリケーションサーバ Puma を入れる
$ bundle add puma
# Gemfile には以下が追記された
# gem "puma", "~> 6.3"
example-1
: クラシックスタイル・単一 Ruby ファイルで起動するシンプル構成
Sinatra のルーティング・コントローラ部分は大きく2種類の書き方がある。最もシンプルなのは「クラシックスタイル」というモノ。
example-1/app.rb
require 'bundler/setup' # Bundler 使用時にパスを調整するための require
require 'sinatra' # ← この部分、`Bundler.require` で一括 require もできるが、ココでは明示的に require する
# いきなり get や post を書けるのがクラシックスタイル
get '/' do
# Ruby の場合コレで文字列を return できている。`/` にアクセスすると以下の文字列がレスポンスされる
'Hello World! Example 1'
end
このようなコードを書いて、$ bundle exec ruby app.rb
で起動できる。
- 参考 : Bundler.setup vs. Bundler.require - Anti-pattern
Bundler.setup
はパスのロードのみでrequire
は自分で行う。Bundler.require
はパスのロードとともにrequire
も一括で行う
- 参考 : bundle installしたgemを通常のrubyコードでrequireする方法 - Qiita
- 参考 : 橋本商会 » Ruby書くならBundler使え
- 参考 : 橋本商会 » Bundler.requireがエラー出すので、プロジェクトローカルにgemを入れた
Bundler.require
で一括ロードできるのは便利そうではあるが、依存モジュールがパッと見で分かりにくいと感じたので、Node.js 脳な人間は都度 require
を書いてやるのが良さそう。w
example-2
: モジュールスタイル・Rack を使って起動する
Express.js でいうミドルウェアをファイル分割したい場合に、方法がいくつかある。
Sinatra.register
を使う方法と、Rack::URLMap
を使う方法もあるが、一番明示的なのは use
でミドルウェアを登録する方法だと思いコレを採用した。
example-2/app.rb
require 'bundler/setup'
require 'sinatra/base' # example-1 とはロードしているモジュールが違う
# モジュール (モジュラー) スタイル
class App < Sinatra::Base
get '/' do
'Hello World! Example 2'
end
end
example-2/controllers/users.rb
- ココでは
controllers/
というディレクトリに複数のルーティング・コントローラを配置するテイ
- ココでは
class UsersController < Sinatra::Base
get '/users' do
'Users'
end
end
example-2/config.ru
.ru
は Rack・rackup
コマンド用の設定ファイル。use
でモジュールを登録し、run
で指定したアプリケーションサーバを起動する
# ルートコントローラ
require './app'
# 必要なコントローラを require → use で登録する
require './controllers/users'
use UsersController
# ルートコントローラを起動する
run App
というワケで、class
化して書けるのがモジュールスタイル。起動には Rack 用の config.ru
ファイルを書くので、コレが実質的なエントリポイントとなる。
- 実行コマンドは
$ bundle exec rackup config.ru
とする
app.rb
は require
の解決とルートパスの定義だけにしておき、配下の各種パスは controllers/
ディレクトリ配下に分割したモジュールたちに任せる、というワケ。コレは Node.js における Express.js や NestJS っぽい感じだと思う。
- 参考 : Sinatra Pattern 20130415 … P14
- 参考 : Sinatra で Controller を分割したくなったら Rack::URLMap を使うとよさそう - Studio3104::BLOG.new
今回はココまで
Sinatra は ERB にもデフォルトで対応しているようなのでビュー層も作れるし、JSON を返すようなサーバも構築しやすいだろう。ActiveRecord との連携も可能なようなので、DB を繋いで Rails っぽい使い方に発展させていくのも容易だ。