Sinatraでデータを修正、更新する(CRUDのUpdate機能)


Rubyでシステムを構築する際に、Railsだとちょっとボリュームが多いので、そんな場合は軽量フレームワークとしてSinatra(PythonのFlaskみたいなもの)というものがあります。

そして、色々と使い方を解説したページや記事もあるのですが、なぜかデータの修正、更新機能については触れていないことが多く、それではどうやって作るのかをひたすら調べ、ようやく解決したので、備忘録として置いておきます。

まずは、この記事(自分とは別人です)のように登録機能と削除機能を作ってみてください。自分の記事はそこから補足説明となります。

なぜ、データ修正機能の記事が殆どないのか?

日本語サイトでもほとんど見つからなかった上に、MVCの観点を鑑みて、全ての機能を懇切丁寧に解説しているものは皆無だったので、英語サイトも隈なく探してみたのですが、そういったサイトは見つかりませんでした(この記事を作ろうとしたきっかけです)。

その理由は明白で、SinatraはRailsとは異なりコントローラの自動生成が簡単にできない上にRailsと記述が若干異なります。その上、公式ページの解説がかなり不親切なので、実際に機能作成を行った事例が少なく、またそのような例もコントローラ部分の解説にとどまっていることが多く、テンプレート周りの記述事例がほとんどありませんでした。

また、普通にサーバ起動してからデータ更新処理を行うと、処理がもたつきます。したがってRackサーバを使って立ち上げる方が能率的なのですが、そもそもSinatraは軽量が売りでありそこまで大掛かりなものは作らないので、利用目的としては矛盾している(実用的価値が少ない)のもありそうです。このへんも踏まえて解説できたらと思います。

ツリー構造

最低限のデータ構造はこうなっています。Rackを使うので、そのためにはconfig.ruが必要となります。config.ruは自動生成されないので、適宜作成します。

- 任意のプロジェクト名
    - db
       sindb.db #今回使用するDB 
    - public
    - vendor
    - views
        edit.erb  #編集用
        index.erb #リスト
    config.ru #Ruckの設定ファイル
    app.rb #コントローラ

Rackの設定

Rackサーバの設定はconfig.ruで行います。色々記事がありますが、最低限これだけ必要です。

config.ru
require './app' #コントローラ
run Sinatra::Application

また、Rackサーバの起動コマンドは以下の通りとなります(ローカルサーバ接続)。また、rackのデフォルトポートは9292となるので、sinatraと同じように4567にしています(bashを使ってbundle execを省略した場合、rackupコマンドで起動可能)。

# bundle exec rackup -o 0.0.0.0 -p 4567

コントローラのメソッド記述

データの更新作業は以下のプロセスを経由します。

一覧画面から修正ボタンを押下 → 編集画面に遷移 → 編集画面でデータの編集 
→ 編集画面から更新ボタンを押下 →更新処理 → 一覧画面に遷移

このうち、編集画面に遷移するにはgetメソッドを使用し、部分的なデータ修正を反映させるにはpatchメソッドを使用します。また、今回はレコード名がuserとなっているので、以下のディレクトリで処理が行われます。

app.rb
#編集データを編集画面(edit.erb)に遷移
get '/users/:id/edit' do
    @user = User.find_by_id(params[:id]) #該当IDのデータ取得
    erb :edit #編集画面の呼び出し
end

#編集画面での更新処理を反映
patch '/users/:id' do
    @user = User.find_by_id(params[:id]) #該当IDのデータ情報取得
    @user.name = params[:name] #更新データの代入
    @user.save #データ更新の反映
    redirect to "/" #一覧画面へ
end

参考にしたサイト

コントローラの全容はこのようになっています。csrf対策のために任意のトークンを発行する必要があります。

app.rb
require 'sinatra' 
require 'active_record' #モデルからレコード取得
require 'rack/csrf' #csrf制御に不可欠

use Rack::Session::Cookie, secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx" #任意のトークン
use Rack::Csrf, rise:true

ActiveRecord::Base.establish_connection(
    adapter: 'sqlite3',
    database: './db/sindb.db'
)

class User < ActiveRecord::Base
    validates :name, presence: true
end

get '/' do
    @title = "User List"
    @users = User.all #テーブルレコードの全データ取得
    erb:index
end

#新規作成
post '/create' do
    User.create(name: params[:name])
    redirect to('/')
end

#編集
get '/users/:id/edit' do
    @user = User.find_by_id(params[:id])
    erb :edit
end

#編集データの更新
patch '/users/:id' do
    @user = User.find_by_id(params[:id])
    @user.name = params[:name]
    @user.save
    #redirect to 
    redirect to "/"
end

#削除
post '/destroy' do
    User.find(params[:id]).destroy
end

テンプレートの作成

テンプレートの注意点としてはCSRF対策を行わないと403エラーに悩まされることになります(一覧、編集画面の双方で対応が必要で、相当悩まされました)。

一覧

一覧画面はそこまで難しく考える必要ないですが、actionプロパティの値に変数を埋め込む必要があります。

index.erb
<body>
<h1><%= @title %></h1>
<% @users.each do |user| %>
    <form method="get" action="/users/<%= user.id %>/edit">
    <ul>
        <li data-id="<%= user.id %>" data-token="<%= Rack::Csrf.csrf_token(env) %>">
            <%= Rack::Utils.escape_html(user.name) %>
            <button type="submit" >修正</button>
            <button class="delete">削除</button>
        </li>
    </ul>
    </form>
<% end %>

<form action="/create" method="post">
  <%= Rack::Csrf.csrf_tag(env) %> 
  <input type="text" name="name">
  <input type="submit" value="追加">
</form>
</body>

編集画面

編集画面は以下の通りとなります。大事なポイントはpatch処理が必要なので、メソッドをhiddenで埋め込む必要があります。

edit.erb
<form action="/users/<%= @user.id %>" method="post">
    <%= Rack::Csrf.csrf_tag(env) %> 
    <input id="hidden" type="hidden" name="_method" value="patch">
    <input type="text" name="name" value="<%= @user.name %>">
    <button type="submit">更新</button>
</form>

実際の動き

一覧画面(更新前)

ここではshoujiというデータを編集するので、修正ボタンを押します。

192.168.11.xx:4567

編集処理

編集画面に遷移するのでshoujiをshinjiに変更します。usersディレクトリを作る必要はなく、編集画面(edit.erb)もindex.erbと同階層に作成されています。

192.168.11.xx:4567/users/5/edit?

一覧画面(更新後)

データが反映されます。

192.168.11.xx:4567

DBで確認

sqlite3で実際に確認してみましょう。sqlite3の場合は、DBファイルをそのまま呼び出します。

#sqlite3 db/sindb.db

修正前のデータです。

sqlite> select * from users;
1|hamana
2|kawaguchi
3|motosu
4|sai
5|shouji
6|saroma
7|shikotsu
8|touya
9|kussharo
10|akan

修正後のデータです。データはしっかりと入れ替わっていました。

sqlite> select * from users;
1|hamana
2|kawaguchi
3|motosu
4|sai
5|shinji
6|saroma
7|shikotsu
8|touya
9|kussharo
10|akan
sqlite>