特定のURL以下にマウントする rack middleware を sinatra で

特定のURL以下にマウントする rack の middleware を作ってみたかった（←こっちが本題）ので、
ActiveRecord の model とかリレーションとかを見るやつを sinatra で書いてみた。
＃昔突貫でざっくり作るだけ作ってどっかの会社にコードまるごと放置してきたやつを思い出しながら

`ruby 2.3.0` + `activerecord (4.2.6)` で確認。

urlのパスにマウントするやり方は `sidekiq/web` を参考に、こんな感じで追記。

```rb:config.ru
require './lib/adminar' 
map('/adminar') { run AdminAr }
```

model が autoload でちゃんと読めてれば、
これで `rackup` すると `http://localhost:9292/adminar` で動くはず。

styleとか最低限なのでものすごい殺風景。

* エラー処理とか全くやってないし、
* 保存時の validate とか NULL の考慮とかも全然入ってないので、

更新系叩く場合は自己責任で。

`gem "composite_primary_keys"` 使ってても大丈夫だとは思うけど、
model に primary_key が指定してないとおかしなことになるかも。

ちなみに、名前は[これ](https://www.adminer.org/)リスペクトな感じで。
まぁ別になんでもいいんだけど。

```rb:./lib/adminar.rb
require "sinatra/base"
require "uri"

class AdminAr < Sinatra::Base
  enable :inline_templates

  DEFAULT_RECORDS_LIMIT = 50

  helpers do
    def root_path
      env['SCRIPT_NAME']
    end

    def has_primary_key?(record)
      not record.class.primary_key.nil?
    end

    def table_url_for(*args, **kwargs)
      uri = URI("%s/%s" % [root_path, args.join("/")])
      uri.query = URI.encode_www_form (kwargs) unless kwargs.empty?
      uri.to_s
    end

    def record_url_for(verb, record)
      uri = URI("%s/%s/%s" % [root_path, record.class.table_name, verb])
      params = if record.class.primary_key.is_a? Array
                  record.class.primary_key.each.with_object({}) { |k, o|
                    o[k] = record.send(k)
                  }
              else
                  { record.class.primary_key => record.send(record.class.primary_key) }
              end
      uri.query = URI.encode_www_form(params) unless params.empty?
      uri.to_s
    end
  end

  before do
    @models = ActiveRecord::Base.connection.tables.each.with_object([]) { |t, o|
      o.push(t.classify.constantize) if t != "schema_migrations"
    }
  end

  get '/' do
    erb :table_index
  end

  get '/:table_name/scheme' do
    @model = params["table_name"].classify.constantize
    @assoc = @model.reflect_on_all_associations
    @indexes = ActiveRecord::Base.connection.indexes(@model.table_name)
    erb :table_scheme
  end

  get '/:table_name/records' do
    @page = params.key?("page") ? params["page"].to_i : 1
    @per  = params.key?("per")  ? params["per"].to_i : DEFAULT_RECORDS_LIMIT

    @model = params["table_name"].classify.constantize
    @records = @model.offset((@page-1) * @per).limit(@per)
    erb :table_records
  end

  get '/:table_name/show' do
    pk = request.GET
    @model = params["table_name"].classify.constantize
    @record = @model.find_by(pk)
    erb :record_show
  end

  get '/:table_name/edit' do
    pk = request.GET
    @model = params["table_name"].classify.constantize
    @record = @model.find_by(pk)
    erb :record_edit
  end

  post '/:table_name/edit' do
    pk = request.GET
    @model = params["table_name"].classify.constantize
    @record = @model.find_by(pk)

    # 雑
    request.POST.each { |k, v|
      unless @record.send(k).nil? and v.empty?
        @record.send("%s=" % k, v)
      end
    }
    @record.save!

    redirect table_url_for @model.table_name, :edit
  end

  post '/:table_name/delete' do
    pk = request.GET
    @model = params["table_name"].classify.constantize
    @record = @model.find_by(pk)
    @record.destroy

    redirect table_url_for @model.table_name, :records
  end
end

__END__

@@ layout
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>AdminAR</title>

  <style>
.adminar {
  background-color: #DDFFFF;
  overflow-x: scroll;
  white-space: nowrap;
}
.adminar span.warn {
  color: #F00;
}

.adminar .navi {
  display: inline-block;
  vertical-align: top;
}
.adminar .navi ul {
  padding-left: 0;
  list-style:none;
}

.adminar .contents {
  display: inline-block;
  vertical-align: top;
}

.adminar .contents table {
  width: 100%;
}
  </style>
</head>

<body class="adminar">
  <%= yield %>
</body>
</html>

@@ _navi
<div class="navi">
  <ul>
<% @models.each do |model| %>
    <li>
      <a href="<%= table_url_for model.table_name, :scheme %>">
        <%= model.table_name %>
      </a>
    </li>
<% end %>
  </ul>
</div>

@@ table_index
<%= erb :_navi %>

@@ table_records
<%= erb :_navi %>

<div class="contents">
  <table class="records">
    <caption>
      records of <%= @model.table_name %>
      (<a href="<%= table_url_for @model.table_name, :scheme %>">
        scheme
      </a>)

      <% if @page > 1 %>
        <a href="<%= table_url_for @model.table_name, :records, page: @page-1, per: @per %>">
          ⇚
        </a>
      <% else %>
        ⇚
      <% end %>
      </a>
      <a href="<%= table_url_for @model.table_name, :records, page: @page+1, per: @per %>">
        ⇛
      </a>
    </caption>

    <tr>
      <th>
      </th>
<% @model.columns.each do |attr| %>
      <th>
        <%= attr.name %>
      </th>
<% end %>
    </tr>

<% @records.each do |record| %>
    <tr>
      <td>
  <% if has_primary_key?(record) %>
        <form action="<%= record_url_for :show, record %>" method="GET">
          <input type="submit" value="show">
        </form>
        <form action="<%= record_url_for :edit, record %>" method="GET">
          <input type="submit" value="edit">
        </form>
        <form action="<%= record_url_for :delete, record %>" method="POST" onsubmit='return confirm("まじで？");'>
          <input type="submit" value="delete">
         </form>
  <% else %>
    <span class="warn">
      no primary_key!!
    </span>
  <% end %>
      </td>
<% @model.columns.each do |attr| %>
      <td>
      <%= record.send(attr.name) %>
      </td>
<% end %>
    </tr>
<% end %>
  </table>
<div>

@@ table_scheme
<%= erb :_navi %>

<div class="contents">
  <table class="scheme">
    <caption>
      scheme of <%= @model.table_name %>
      (<a href="<%= table_url_for @model.table_name, :records %>">
        records
      </a>)
    </caption>

    <tr>
      <th>
        name
      </th>
      <th>
        type
      </th>
      <th>
        sql_type
      </th>
      <th>
        null
      </th>
      <th>
        default
      </th>
      <th>
        extra
      </th>
    </tr>

<% @model.columns.each do |attr| %>
    <tr>
      <td>
        <%= attr.name %>
      </td>
      <td>
        <%= attr.type %>
      </td>
      <td>
        <%= attr.sql_type %>
      </td>
      <td>
        <%= attr.null %>
      </td>
      <td>
        <%= attr.default %>
      </td>
      <td>
        <%= attr.extra %>
      </td>
    </tr>
<% end %>
  </table>

<% unless @assoc.empty? %>
  <table class="assoc">
    <caption>
      assoc of <%= @model.table_name %>
    </caption>

    <tr>
      <th>
        macro
      </th>
      <th>
        table
      </th>
      <th>
        foreign_key
      </th>
    </tr>

<% @assoc.each do |assoc| %>
    <tr>
      <td>
        <%= assoc.macro %>
      </td>
      <td>
        <a href="<%= table_url_for assoc.name, :scheme %>">
          <%= assoc.name %>
        </a>
      </td>
      <td>
        <%= assoc.foreign_key %>
      </td>
    </tr>
<% end %>

  </table>
<% end %>

<% unless @indexes.empty? %>
  <table class="index">
    <caption>
      index of <%= @model.table_name %>
    </caption>

    <tr>
      <th>
        column
      </th>
      <th>
        name
      </th>
      <th>
        unique
      </th>
      <th>
        using
      </th>
    </tr>

<% @indexes.each do |idx| %>
    <tr>
      <td>
        <%= idx.columns.join(",") %>
      </td>
      <td>
        <%= idx.name %>
      </td>
      <td>
        <%= idx.unique %>
      </td>
      <td>
        <%= idx.using %>
      </td>
    </tr>
<% end %>

  </table>
<% end %>

<div>


@@ record_show
<%= erb :_navi %>

<div class="contents">
  <table>
    <caption>
      <a href="<%= table_url_for @model.table_name, :scheme %>">
<%= @model.name %>
      </a>
    </caption>
<% @model.columns.each do |attr| %>
    <tr>
      <th>
      <%= attr.name %>
      </th>
      <td>
      <%= @record.send(attr.name) %>
      </td>
    </tr>
<% end %>
  </table>
<div>

@@ record_edit
<%= erb :_navi %>

<% if has_primary_key?(record) %>
<div class="contents">
<form action="<%= record_url_for :edit, @record %>" method="POST">
  <table>
    <caption>
      <a href="<%= table_url_for @model.table_name, :scheme %>">
<%= @model.name %>
      </a>
    </caption>
<% @model.columns.each do |attr| %>
    <tr>
      <th>
      <%= attr.name %>
      </th>
      <td>
      <input type="text" size="200" name="<%= attr.name %>" value="<%= @record.send(attr.name) %>">
      </td>
    </tr>
<% end %>
  </table>
  <input type="submit" value="save">
</form>
<div>
<% end %>
```