7
3

More than 3 years have passed since last update.

Sinatraで社内サービスを作るためのいろいろ

Last updated at Posted at 2020-10-07

自分用めも。

予め書いておく。
Sinatraで画面をもつ&ログインが必要な&Herokuデプロイ可能なアプリケーションを書くのは結構めんどくさいので、たとえ単機能であってもRailsを使ったほうがいい。間違いない。

前提

フォーム入力したらそれに沿って外部サービスのAPIを叩きに行って、結果を表示するだけのアプリ。一応ActiveRecordは使用する。

ただし社内サービスなので、フォームバリデーションとかは適当でいい。あとは

  • Docker, docker-composeで開発環境はサッと作れるのがよい。
  • Herokuにデプロイしたい。
  • GitHub認証で、なおかつ知らないユーザは弾きたい。

開発用のDockerファイルを作る。

Railsに比べると依存は少ない。

docker-compose.yml
version: "3"
services:
  web:
    build: .
    volumes:
      - .:/sinatra-inhouse-app
      - ruby-bundle:/usr/local/bundle
    working_dir: /sinatra-inhouse-app

volumes:
  ruby-bundle:
    driver: local
FROM ruby:2.7-alpine

Gemfileを作る

$ docker-compose build
$ docker-compose run --rm web sh
/sinatra-inhouse-app # bundle init

SinatraでHello Worldするまで

このへんまでは楽勝。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "sinatra", require: false

↑sinatraに require: false をつけないと、at_exitフックでサーバーが起動してしまうので、地味にハマる。注意。

inhouse_app.rb
require 'sinatra/base'

class InhouseApp < Sinatra::Base
  get '/' do
    '<h1>It works!</h1>'
  end
end
config.ru
require "bundler/setup"
Bundler.require
require_relative './inhouse_app'

run InhouseApp
docker-compose.yml
version: "3"
services:
  web:
    build: .
    volumes:
      - .:/sinatra-inhouse-app
      - ruby-bundle:/usr/local/bundle
    working_dir: /sinatra-inhouse-app
    ports:
      - 4567:4567
    command: bundle exec rackup --host 0.0.0.0 --port 4567

volumes:
  ruby-bundle:
    driver: local

起動してみる

$ docker-compose up
Creating sinatra-inhouse-app_web_1 ... done
Attaching to sinatra-inhouse-app_web_1
web_1  | [2020-10-07 14:19:22] INFO  WEBrick 1.6.0
web_1  | [2020-10-07 14:19:22] INFO  ruby 2.7.0 (2019-12-25) [x86_64-linux-musl]
web_1  | [2020-10-07 14:19:22] INFO  WEBrick::HTTPServer#start: pid=1 port=4567

image.png

GitHub認証に対応する

今はまだハローワールド。
それでも、社内サービスなので世の中に見られてはいけない。(とする。)

OAuthアプリケーション登録

https://github.com/settings/developers からアプリケーション登録をして、Client IDとSecretをもらってくる。

image.png

Authorization callback URL だけが重要で、あとはてきとうでいい。

貼り付けた画像_2020_10_07_23_24.png
↑これをメモる。

.envrc にメモる

direnv が便利なので、それを使う。↑でメモったやつをそのまま書く。

.envrc
export GITHUB_CLIENT_ID=eb12427ce9d12604f813
export GITHUB_CLIENT_SECRET=04d6af0463f16a807d7faca9ead406e76ad8192992c022ee

ちなみに、このファイルは絶対にGit管理してはいけない。
(もちろん、Qiitaに本物のIDを書いちゃダメなので、↑はサンプル。)

.gitignore
/.envrc
$ direnv allow .

あとは、docker-compose.ymlに environment を追加すれば、コンテナ側で管渠変数が見れる。

docker-compose.yml
diff --git a/docker-compose.yml b/docker-compose.yml
index b317508..2d75301 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,6 +6,9 @@ services:
       - .:/sinatra-inhouse-app
       - ruby-bundle:/usr/local/bundle
     working_dir: /sinatra-inhouse-app
+    environment:
+      - GITHUB_CLIENT_ID
+      - GITHUB_CLIENT_SECRET
     ports:
       - 4567:4567
     command: bundle exec rackup --host 0.0.0.0 --port 4567
$ docker-compose run --rm web sh
/sinatra-inhouse-app # echo $GITHUB_CLIENT_ID
eb12427ce9d12604f813

omniauth-githubを入れる

OmniAuthをSinatraに組み込む方法はここに書いてある。
https://github.com/omniauth/omniauth/wiki/Sinatra-Example

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "sinatra", require: false

gem "omniauth"
gem "omniauth-github"
inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index b2621c7..1716520 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -1,7 +1,18 @@
 require 'sinatra/base'

 class InhouseApp < Sinatra::Base
+  use Rack::Session::Cookie
+  use OmniAuth::Builder do
+    provider :github, ENV['GITHUB_CLIENT_ID'], ENV['GITHUB_CLIENT_SECRET']
+  end
+
   get '/' do
     '<h1>It works!</h1>'
   end
+
+  get '/auth/github/callback' do
+    auth_hash = request.env['omniauth.auth']
+    puts auth_hash
+    redirect to('/')
+  end
 end

動作確認

image.png

貼り付けた画像_2020_10_07_23_41.png

貼り付けた画像_2020_10_07_23_42.png

image.png

こんなかんじで、あとはログを見ると

I, [2020-10-07T14:45:12.104513 #1]  INFO -- omniauth: (github) Request phase initiated.
I, [2020-10-07T14:45:12.531651 #1]  INFO -- omniauth: (github) Callback phase initiated.
#<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=false token="???????????????????"> extra=#<OmniAuth::AuthHash all_emails=#<Hashie::Array []> raw_info=#<OmniAuth::AuthHash avatar_url="https://avatars2.githubusercontent.com/u/11763113?v=4" bio=nil blog="http://yusukeiwaki.hatenablog.com/" company=nil created_at="2015-04-02T00:02:12Z" email=nil events_url="https://api.github.com/users/YusukeIwaki/events{/privacy}" followers=9 followers_url="https://api.github.com/users/YusukeIwaki/followers" following=19 following_url="https://api.github.com/users/YusukeIwaki/following{/other_user}" gists_url="https://api.github.com/users/YusukeIwaki/gists{/gist_id}" gravatar_id="" hireable=nil html_url="https://github.com/YusukeIwaki" id=11763113 location="Fukuoka, Japan" login="YusukeIwaki" name="Yusuke Iwaki" node_id="MDQ6VXNlcjExNzYzMTEz" organizations_url="https://api.github.com/users/YusukeIwaki/orgs" public_gists=32 public_repos=147 received_events_url="https://api.github.com/users/YusukeIwaki/received_events" repos_url="https://api.github.com/users/YusukeIwaki/repos" site_admin=false starred_url="https://api.github.com/users/YusukeIwaki/starred{/owner}{/repo}" subscriptions_url="https://api.github.com/users/YusukeIwaki/subscriptions" twitter_username=nil type="User" updated_at="2020-10-07T14:42:15Z" url="https://api.github.com/users/YusukeIwaki"> scope=""> info=#<OmniAuth::AuthHash::InfoHash email=nil image="https://avatars2.githubusercontent.com/u/11763113?v=4" name="Yusuke Iwaki" nickname="YusukeIwaki" urls=#<OmniAuth::AuthHash Blog="http://yusukeiwaki.hatenablog.com/" GitHub="https://github.com/YusukeIwaki">> provider="github" uid="11763113">

こんなのがでているはず。

ハローワールドを認証つきにする

ここに詳しく書いてある。
http://sinatrarb.com/faq.html#auth

認証されていなかったらGitHubログインの方に飛ばし、
許可のないユーザであればForbidden表示をする。

inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index 1716520..29726af 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -6,13 +6,38 @@ class InhouseApp < Sinatra::Base
     provider :github, ENV['GITHUB_CLIENT_ID'], ENV['GITHUB_CLIENT_SECRET']
   end

+  helpers do
+    def authorize!
+      unless authenticated?
+        redirect to('/auth/github')
+      end
+      unless authorized?
+        halt 403, 'Forbidden. <a href="/logout">Logout</a>'
+      end
+    end
+
+    def authenticated?
+      session[:user]
+    end
+
+    def authorized?
+      session[:user] == 'YusukeIwaki'
+    end
+  end
+
   get '/' do
+    authorize!
     '<h1>It works!</h1>'
   end

   get '/auth/github/callback' do
     auth_hash = request.env['omniauth.auth']
-    puts auth_hash
+    session[:user] = auth_hash['extra']['raw_info']['login']
+    redirect to('/')
+  end
+
+  get '/logout' do
+    session[:user] = nil
     redirect to('/')
   end
 end

ためしに他のユーザでアクセスしてみると、こんなかんじ。

image.png

Herokuにデプロイする

認証が最低限かけれたところで、Herokuに上げてみる。

$ heroku create sinatra-inhouse-app-sample
Creating ⬢ sinatra-inhouse-app-sample... done
https://sinatra-inhouse-app-sample.herokuapp.com/ | https://git.heroku.com/sinatra-inhouse-app-sample.git
$ git push heroku master
Enumerating objects: 21, done.
Counting objects: 100% (21/21), done.
Delta compression using up to 12 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (21/21), 3.04 KiB | 3.04 MiB/s, done.
Total 21 (delta 7), reused 3 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Ruby app detected
remote: -----> Installing bundler 2.1.4
remote: -----> Removing BUNDLED WITH version in the Gemfile.lock
remote: -----> Compiling Ruby/Rack
remote: -----> Using Ruby version: ruby-2.6.6
remote: -----> Installing dependencies using bundler 2.1.4
remote:        Running: BUNDLE_WITHOUT=development:test BUNDLE_PATH=vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
remote:        Fetching gem metadata from https://rubygems.org/.......
remote:        Using bundler 2.1.4
remote:        Fetching hashie 4.1.0
remote:        Fetching multipart-post 2.1.1
remote:        Fetching jwt 2.2.2
remote:        Installing jwt 2.2.2

Herokuはとても賢いので、GemfileがあればRubyビルドを開始してくれる。

あとは、環境変数をセットして

$ heroku config:set GITHUB_CLIENT_ID=eb12427ce9d12604f813
$ heroku config:set GITHUB_CLIENT_SECRET=04d6af0463f16a807d7faca9ead406e76ad8192992c022ee

OAuthのアプリケーション設定でコールバックURLをHerokuのものに書き換えて、

image.png

これだけで、Herokuにアクセスしたら認証もしっかり動いて、いいかんじ。

image.png

おまけ: bin/consoleを使えるようにする

SinatraではRailsコンソールは使えないが、それに近いものは使えるようにしておく。

bin/console
#!/usr/bin/env ruby

require "bundler/setup"
Bundler.require
require_relative '../inhouse_app'

Pry.start
Gemfile
diff --git a/Gemfile b/Gemfile
index 4fa2b7b..aef2284 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,6 +4,7 @@ source "https://rubygems.org"

 git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

+gem "pry"
 gem "sinatra", require: false

 gem "omniauth"
$ docker-compose run --rm web sh
/sinatra-inhouse-app # bundle install
Fetching gem metadata from https://rubygems.org/.......
Resolving dependencies...
Using bundler 2.1.2
Using coderay 1.1.3
Using multipart-post 2.1.1
Using faraday 1.0.1
Using hashie 4.1.0
Using jwt 2.2.2
Using method_source 1.0.0
Using multi_json 1.15.0
Using multi_xml 0.6.0
Using ruby2_keywords 0.0.2
Using mustermann 1.1.1
Using rack 2.2.3
Using oauth2 1.4.4
Using omniauth 1.9.1
Using omniauth-oauth2 1.7.0
Using omniauth-github 1.4.0
Fetching pry 0.13.1
Installing pry 0.13.1
Using rack-protection 2.1.0
Using tilt 2.0.10
Using sinatra 2.1.0
Bundle complete! 4 Gemfile dependencies, 20 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

/sinatra-inhouse-app # bin/console
[1] pry(main)> InhouseApp
=> InhouseApp
[2] pry(main)> InhouseApp.run
=> false
[3] pry(main)> exit

中間おさらい

ここまでで

  • Docker, docker-composeで開発環境はサッと作れるのがよい。
  • Herokuにデプロイしたい。
  • GitHub認証で、なおかつ知らないユーザは弾きたい。

については比較的簡単に作れた。

ここまでだと「あれ?Sinatraって結構べんりじゃね?」と錯覚する。

そう、錯覚。

なぜなら、ここまではRailsで作ったとしても、ほとんどRailsのフレームワークの機能を使わないからだ。

フォーム入力したらそれに沿って外部サービスのAPIを叩きに行って、結果を表示するだけのアプリ。一応ActiveRecordは使用する。
ただし社内サービスなので、フォームバリデーションとかは適当でいい。あとは...

後半は、本体を作り始める。

ActiveRecordを使う

ここから徐々にSinatraのつらみがでてくる。

Railsであればconfig/database.ymlをいじって、app/models/ にクラスを追加すれば、DBアクセスができる状態になるが、Sinatraはsinatra-activerecordなどの設定が必要だ。

docker-composeにpostgresqlを追加する

ローカル環境でもpostgresqlを動かす。手順は「postgresql docker-compose」とかでググれば出てくるだろう。

docker-compose.yml
diff --git a/docker-compose.yml b/docker-compose.yml
index 2d75301..26f4207 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,12 +7,28 @@ services:
       - ruby-bundle:/usr/local/bundle
     working_dir: /sinatra-inhouse-app
     environment:
+      - POSTGRES_HOST=postgresql
+      - POSTGRES_USER=dbuser
+      - POSTGRES_PASSWORD=dbpassword
       - GITHUB_CLIENT_ID
       - GITHUB_CLIENT_SECRET
+    depends_on:
+      - postgresql
     ports:
       - 4567:4567
     command: bundle exec rackup --host 0.0.0.0 --port 4567

+  postgresql:
+    image: postgres:12-alpine
+    restart: always
+    environment:
+      POSTGRES_USER: dbuser
+      POSTGRES_PASSWORD: dbpassword
+    volumes:
+      - postgresql-data:/var/lib/postgresql/data
+
 volumes:
   ruby-bundle:
     driver: local
+  postgresql-data:
+    driver: local
$ docker-compose pull

これで、postgresqlがdocker-compose upやdocker-compose run で起動するようになる。

sinatra-activerecordを入れる

Gemfileに追記。
あと、postgresql関連のライブラリを入れるために(bundle installを通すために)Dockerfileで build-baseとpostgresql-devのインストールプロセスを追加する必要がある。
lessはPryの標準出力が長くなってもバグらないように追加。

Gemfile追加
diff --git a/Dockerfile b/Dockerfile
index 5ac8708..ab0beb4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1 +1,6 @@
 FROM ruby:2.7-alpine
+
+RUN apk add --no-cache \
+    build-base \
+    less \
+    postgresql-dev
diff --git a/Gemfile b/Gemfile
index aef2284..19d0a62 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,8 +4,12 @@ source "https://rubygems.org"

 git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

+gem "activerecord"
+gem "pg"
 gem "pry"
+gem "rake"
 gem "sinatra", require: false
+gem "sinatra-activerecord"

 gem "omniauth"
 gem "omniauth-github"

rake db:create

sinatra-activerecordのREADMEにある説明から、雰囲気で設定ファイルなどを追加する。

config/database.yml については、Railsのものをそのまま使える。

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV.fetch("POSTGRES_HOST") { '127.0.0.1' } %>
  port: 5432
  username: <%= ENV["POSTGRES_USER"] %>
  password: <%= ENV["POSTGRES_PASSWORD"] %>

development:
  <<: *default
  database: inhouse_app  

test:
  <<: *default
  database: inhouse_app_test  

production:
  url: <%= ENV['DATABASE_URL'] %>
Rakefile
require "sinatra/activerecord/rake"

namespace :db do
  task :load_config do
    require "bundler/setup"
    Bundler.require
    require "./inhouse_app"
  end
end
/sinatra-inhouse-app # bundle exec rake -T
rake db:create              # Creates the database from DATABASE_URL or config/database.yml for the current RAILS_E...
rake db:create_migration    # Create a migration (parameters: NAME, VERSION)
rake db:drop                # Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV...
rake db:environment:set     # Set the environment value for the database
rake db:fixtures:load       # Loads fixtures into the current environment's database
rake db:migrate             # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
rake db:migrate:status      # Display status of migrations
rake db:prepare             # Runs setup if database does not exist, or runs migrations if it does
rake db:rollback            # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rake db:schema:cache:clear  # Clears a db/schema_cache.yml file
rake db:schema:cache:dump   # Creates a db/schema_cache.yml file
rake db:schema:dump         # Creates a db/schema.rb file that is portable against any DB supported by Active Record
rake db:schema:load         # Loads a schema.rb file into the database
rake db:seed                # Loads the seed data from db/seeds.rb
rake db:seed:replant        # Truncates tables of each database for current environment and loads the seeds
rake db:setup               # Creates the database, loads the schema, and initializes with the seed data (use db:re...
rake db:structure:dump      # Dumps the database structure to db/structure.sql
rake db:structure:load      # Recreates the databases from the structure.sql file
rake db:version             # Retrieves the current schema version number

/sinatra-inhouse-app # bundle exec rake db:create
Created database 'inhouse_app'
Created database 'inhouse_app_test'

rake db:migrate

Railsジェネレータがない。 rails g migration できないので、代わりに rake db:create_migration を使う。

/sinatra-inhouse-app # bundle exec rake db:create_migration NAME=create_audit_log 
db/migrate/20201007162814_create_audit_log.rb

作ってしまえばRailsと同じなのだが、いざcreate_tableを手で書くと、割と迷う。

db/migrate/20201007162814_create_audit_log.rb
class CreateAuditLog < ActiveRecord::Migration[6.0]
  def up
    create_table :audit_logs do |t|
      t.string :subject, null: false
      t.text :description
      t.datetime :created_at, index: true, null: false
    end
  end

  def down
    drop_table :audit_logs
  end
end

db:migrateはRailsと同じ。

/sinatra-inhouse-app # bundle exec rake db:migrate
== 20201007162814 CreateAuditLog: migrating ===================================
-- create_table(:audit_logs)
   -> 0.0090s
== 20201007162814 CreateAuditLog: migrated (0.0091s) ==========================

モデルの作成

app/models/audit_log.rb
class AuditLog < ActiveRecord::Base
  validates :subject, presence: true
end

作っておしまい...ではない。Railsと違って、Sinatraはオートロードみたいな仕組みを持たないので、明示的にrequireしてあげないといけないのだ。

inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index 5cd6ed5..a3ab821 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -1,4 +1,8 @@
 require 'sinatra/base'
+Dir['./app/models/**/*.rb'].each { |f| require_relative f }
+
+ActiveRecord::Base.logger = Logger.new($stdout)
+ActiveRecord::Base.verbose_query_logs = true

 class InhouseApp < Sinatra::Base
   use Rack::Session::Cookie

ここまでくれば、あとは自由にActiveRecordが使える

/sinatra-inhouse-app # bin/console
[1] pry(main)> AuditLog.last
D, [2020-10-07T16:35:50.957634 #239] DEBUG -- :   AuditLog Load (0.7ms)  SELECT "audit_logs".* FROM "audit_logs" ORDER BY "audit_logs"."id" DESC LIMIT $1  [["LIMIT", 1]]
D, [2020-10-07T16:35:50.957900 #239] DEBUG -- :   ↳ (pry):1:in `__pry__'
=> nil
[2] pry(main)> AuditLog.count
D, [2020-10-07T16:35:54.177493 #239] DEBUG -- :    (0.9ms)  SELECT COUNT(*) FROM "audit_logs"
D, [2020-10-07T16:35:54.177788 #239] DEBUG -- :   ↳ (pry):2:in `__pry__'
=> 0

アクセスカウンターを作ってHerokuにデプロイする

DBとの結合動作確認のためだけに、とりあえずアクセスカウンタを付けてみる。

inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index a3ab821..498ec31 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -31,7 +31,13 @@ class InhouseApp < Sinatra::Base

   get '/' do
     authorize!
-    '<h1>It works!</h1>'
+
+    AuditLog.create!(
+      subject: "#{session[:user]} accessed",
+      description: "#{session[:user]} accessed on '/'",
+    )
+
+    "<h1>It works!</h1> -- #{AuditLog.count}"
   end

   get '/auth/github/callback' do

image.png

リロードするたびにカウンタが増えることを確認したら、

$ git push heroku master

で、デプロイ。データベースは勝手に追加されたりはしないので、手動で追加。

$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on ⬢ sinatra-inhouse-app-sample... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-curved-40316 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

最初はDBがマイグレーションされていないので、エラー画面になるが、驚く必要はない。

image.png

$ heroku run bundle exec rake db:migrate

上記のように、heroku runでマイグレーションを実行すれば直る。

image.png

フォームを作る

actionview まで入れてしまうと、bundle install でNokogiriが入ってくるので、「それはもはやRailsでは?」みたいな状況になる。
素のSinatraでもerbは使えるので、erbで頑張ってみる。

これが、フォームヘルパーもタグヘルパーも使えないので、割と苦行だ。

index.html.erbを読み込むには?

ERBを使うには erb っていうメソッドを使うわけだが、READMEのように erb :index と指定するとviews/index.html.erbではなく、views/index.erbを探しに行こうとする。
かといって、 erb "index.html" と指定すると、index.htmlという文字列をERBとして解釈しようとする。
なので、無理矢理でも :'index.html' のようなシンボルを指定する必要がある。

inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index 498ec31..882dc60 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -37,7 +37,7 @@ class InhouseApp < Sinatra::Base
       description: "#{session[:user]} accessed on '/'",
     )

-    "<h1>It works!</h1> -- #{AuditLog.count}"
+    erb :'index.html'
   end

   get '/auth/github/callback' do
views/index.html.erb
<html>
<body>
<h1>It works!</h1> -- <%= AuditLog.count %>

from erb
</body>
</html>

image.png

フォームを作る

フォームオブジェクトはRailsと同様に書けるのだけど、form_for, form_withが無いので、愚直に <form action="..." method="POST"> から書かないといけない。
フォームのHTMLを手で書きたい人にはいいかもしれないが、そこまでフォームの見た目は気にしない社内サービスだと、これは結構めんどくさい。

inhouse_app.rb
diff --git a/inhouse_app.rb b/inhouse_app.rb
index 882dc60..75ecdf1 100644
--- a/inhouse_app.rb
+++ b/inhouse_app.rb
@@ -40,6 +40,22 @@ class InhouseApp < Sinatra::Base
     erb :'index.html'
   end

+  get '/profile' do
+    @form = ProfileForm.new
+    erb :'profile.html'
+  end
+
+  post '/profile' do
+    profile_form_params = params.slice(:name, :age, :address)
+    @form = ProfileForm.new(profile_form_params)
+    if @form.valid?
+      "Profile登録しにいく"
+      redirect to('/profile')
+    else
+      erb :'profile.html'
+    end
+  end
+
   get '/auth/github/callback' do
     auth_hash = request.env['omniauth.auth']
     session[:user] = auth_hash['extra']['raw_info']['login']
app/models/profile_form.rb
class ProfileForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :age, :integer
  attribute :address, :string

  validates :name, presence: true
  validates :age, presence: true
end
views/profile.html.erb
<html>
<body>
<h1>Profile</h1>

<% if @form.errors.present? %>
<ul class="errors">
<% @form.errors.full_messages.each do |error_message| %>
  <li><%= error_message %></li>
<% end %>
</ul>
<% end %>

<form action='/profile' method='POST'>
  <div>
    name: <input type="text" name="name" value="<%= @form.name %>" />
  </div>
  <div>
    age: <input type="number" name="age" value="<%= @form.age %>" />
  </div>
  <div>
    address: <input type="text" name="address" value="<%= @form.address %>" />
  </div>

  <input type="submit" value="OK" />
</form>
</body>
</html>
GET /profile POST /profile バリデーションエラー
image.png image.png

まとめ

ActiveRecordを使う・画面(とくにフォーム)がある・HerokuにデプロイするようなWebアプリケーション書くなら、たとえ単機能であってもSinatraではなくRailsを選択したほうが、フレームワークの恩恵を受けやすいです。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3