DBを読み取り専用で使うWebアプリケーションを作るならSinatraがお手軽

以前、まだQiitaを愛していた頃に、こんな記事を書いたことがある。

qiita.com

社内向けWebアプリで、機能が限定的だったとしても、SinatraじゃなくてRailsで書いたほうが総合的にラクよ、という話。この考え自体は今もそんなに変わっていなくて、なんだかんだでRailsラクだ。

しかし、最近になって、SinatraでWebアプリケーションを書いてみて「これはうまく行った」という例を1つ見つけた。

不具合解析のアシスタント的なアプリケーションだ。

  • DBはもともとあるものに接続して、そこにある情報を読むだけ。書き込みは行わない。
  • 入力フォームは最低限でいい
  • テストを真面目に書く必要がない

ようするに、手でSQLを叩くよりはマシ、くらいのものを作りたければ割とお手軽ということ。

## application.rb ##
# frozen_string_literal: true

require 'bundler'
Bundler.require :default, (ENV['RACK_ENV'] || :development).to_sym

require_relative './config/activerecord'
require_relative './config/zeitwerk'
require 'sinatra/base'

class Application < Sinatra::Base
  # いろいろ...
end
## config.ru ##
# frozen_string_literal: true

require_relative './application'

run Application
## Gemfile ##
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'mysql2'
gem 'puma'
gem 'rack'
gem 'sinatra', require: false
gem 'sinatra-activerecord'
gem 'zeitwerk'

group :development do
  gem 'pry-byebug'
  gem 'rubocop'
end
## config/zeitwerk.rb ##
# frozen_string_literal: true

loader = Zeitwerk::Loader.new
loader.push_dir('./models')
loader.setup
## config/activerecord.rb ##
# frozen_string_literal: true

# ref: https://hai3.net/blog/active-record-readonly/
module ActiveRecord
  class Base
    def readonly?
      true
    end

    def self.readonly_attributes
      attribute_names
    end

    # Disable annoying STI
    self.inheritance_column = :_xxxxxx
  end
end

ActiveRecord::Base.logger = Logger.new($stdout)
ActiveRecord.verbose_query_logs = true

このくらいの下地を整えてあげれば、modelsにDB参照用に振り切ったモデルを定義して、config/database.ymlをRailsのものをコピってくれば、あとはviewsを必要な分だけ置けばいい。

MVCしたい?

SinatraアプリケーションではModelとViewはあるが、コントローラなんてものはない。

とはいえ、わざわざコントローラ"層"を定義する必要がどこまであるだろう?erbにして、冒頭で必要なパラメータを取ればそれでよくないだろうか?

class Application < Sinatra::Base
  get '/customer_info' do
    erb :"guide/customer_info.html"
  end
end
## views/guide/customer_info.html.erb
<%
  customer_id = params[:customer_id].presence
  customer_name = params[:customer_name].presence

  if customer_id.present?
    @customer = Customer.find(customer_id)
  elsif customer_name.present?
    @customer = Customer.find_by!(name: customer_name)
  end
%>

<form method="GET">
  <p>どれか1こ入れてね。</p>
  <div class="mb-3">
    <label for="input_customer_id" class="form-label">ID</label>
    <input
      id="input_customer_id"
      type="text"
      class="form-control"
      name="customer_id"
      value="<%= customer_id %>" />
  </div>
  <div class="mb-3">
    <label for="input_customer_name" class="form-label">NAME</label>
    <input
      id="input_customer_name"
      type="text"
      class="form-control"
      name="customer_name"
      value="<%= customer_name %>" />
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

<% if @customer %>
  <%
    data = [
      ['ID', @customer.id],
      ['NAME', @customer.name],
      ['契約開始日', @customer.contract.started_at],
    ]
  %>
  <table class="table">
    <thead>
      <tr>
        <th scope="col">key</th>
        <th scope="col">value</th>
      </tr>
    </thead>
    <tbody>
      <% data.each do |key, value| %>
      <tr>
        <th scope="row"><%= key %></th>
        <td><%= value %></td>
      </tr>
      <% end %>
    </tbody>
  </table>
<% end %>

まとめ

DBをみて解析が必要なシーンだと、Sinatra+ActiveRecordでWebアプリケーション作ると意外とイイ!

github.com

(コードは、このリポジトリを大いに参考にさせてもらった。)