Hit the books!!

プログラミング学習記録

Sinatraの基本構文

Sinatraとは

Sinatraは、Webサイト、Webサービス、WebアプリケーションをRubyで作成するためのドメイン固有言語(ドメイン特化言語)である。

Ruby on Railsは多機能で難しいので、まずはSinatraの学習からはじめたほうがいいらしい。

環境:macOS Catalina / Ruby 2.7.1

まずはHello world

sinatra.rbという名前で以下のコードを記述。

require 'sinatra'

get '/' do
  "Hello, world!"
end

Terminalでruby sinatra.rbを実行し、ブラウザを確認するとこうなる。

f:id:ud_ike:20200905205219p:plain

メソッド 'ルート' do形式で表現される。この例では、/パスへのHTTP GETリクエストに応答するようアプリケーションに命令している。

4567はSinatraのデフォルトのポート番号。

じゃんけんアプリの例

require 'sinatra'
    
# ルートを処理する前に、レスポンスをプレーンテキストとして設定し
# プレーヤー(とコンピュータ)が出せる有効な手の配列を設定する
before do
  content_type :txt
  @defeat = {rock: :scissors, paper: :rock, scissors: :paper} # キーも値もシンボルのハッシュ
  @throws = @defeat.keys # 全キーの配列を返す
end

get '/throw/:type' do
# params[]ハッシュにはクエリ文字列とフォームデータが格納されている
  player_throw = params[:type].to_sym

# プレーヤーが無効な手を指定した場合は、
# アプリケーションを停止してステータスコード403(Forbidden)を返し、
# 有効な手を指定するよう知らせる
  if !@throws.include?(player_throw)
    halt 403, "この中から選んでね #{@throws}"
  end

# コンピュータの手をランダムに選択する
  computer_throw = @throws.sample

# プレーヤーとコンピュータの手を比較して結果を表示する
  if player_throw == computer_throw
    "あいこだよ。Try again!"
  elsif computer_throw == @defeat[player_throw]
    "勝ち!! #{player_throw} beats #{computer_throw}!"
  else
    "負け... #{computer_throw} beats #{player_throw}. Better luck next time!"
  end
end

f:id:ud_ike:20200907084812p:plain f:id:ud_ike:20200907084835p:plain f:id:ud_ike:20200907084851p:plain

URLからパラメータを受け取る

require 'sinatra'
require 'sinatra/reloader'

get '/hello/:name' do
  "hello #{params[:name]}"
end

ワイルドカードを使う

require 'sinatra'

get '/*' do
  "You passed in #{params[:splat]}"
end

ワイルドカードで渡されたものはすべてparams[:splat]内の配列に格納される。

f:id:ud_ike:20200907085029p:plain

マッチについて

require 'sinatra'

get '/*' do
  "Hi!"
end

get '/hello' do
  "Hello!!"
end

これだとHello!!と表示されない。

f:id:ud_ike:20200907085508p:plain

リクエストの移動

passメソッドを使うと次に有効なマッチを調べる。

require 'sinatra'

get %r{/(sp|gr)eedy} do
  pass if request.path =~ /\/speedy/
  "You got caught in the greedy route!"
end

get '/speedy' do
  "You must have passed to me!"
end

f:id:ud_ike:20200907085801p:plain f:id:ud_ike:20200907085811p:plain

リダイレクト

ステータスコードを使い分けることができる。

require 'sinatra'

get '/redirect' do
  redirect 'http://www.google.com'
end

get '/redirect2' do
  redirect 'http://www.google.com', 301
end

デフォルトでは一時的なリダイレクト(コード302)とみなされる。

f:id:ud_ike:20200907090520p:plain

静的ファイル

publicディレクトリの中に以下の内容でpublic.htmlというファイルを作る。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Static file</title>
  </head>
  <body>
    <h1>This is a static file.</h1>
  </body>
</html>
require 'sinatra'

get '/public.html' do
  'This is delivered via the route.'
end

定義されたルートが静的リソースの名前と衝突する場合、静的リソースが提供される。また、URLからpublicフォルダが削除される。

f:id:ud_ike:20200907091425p:plain

外部ビューファイル

viewsディレクトリの中にindex.erbという名前で以下のファイルを保存する。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>External template</title>
  </head>
  <body>
    <h1>外部ビュー</h1>
  </body>
</html>
require 'sinatra'

get '/index' do
  erb :index
end

f:id:ud_ike:20200907092218p:plain

例えば、viewsの中のuserサブディレクトリでname.erbファイルとprofile.erbファイルを検索する方法。

require 'sinatra'

get '/:user/name' do
  erb '/user/name'.to_sym
end

get '/:user/profile' do
  erb :'/user/profile'
end

シンボルの作成方法はどちらでもOKだが、to_symを利用する方法が一般的のよう。

ビューへのデータ受け渡し

インスタンス変数を使ってビューと共有することができる。

views/home.erb↓

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>instance variables</title>
</head>
<body>
  <h1>Hello, <%= @name %>!</h1>
</body>
</html>
require 'sinatra'
                    
get '/home' do
  @name = 'Random User'
  erb :home
end

評価対象の文(インスタンス変数の表示、変換処理の実施など)は<%= %>タグで囲み、ループなどの制御文は<% %>で囲む。

フィルタ

ルートが実行される前後にリクエストとレスポンスを変更する機能。 以下の例では、beforeフィルタを使用してルートを処理する前にインスタンス変数を設定している。

require 'sinatra'
                
before do
  @before_value = 'foo'
end

get '/' do
  "before_value = #{@before_value}"
end

after do
  puts "After filter called."
end

f:id:ud_ike:20200907092303p:plain f:id:ud_ike:20200907092311p:plain

異なるスコープのself

require "sinatra"

outer_self = self
get '/' do
  content_type :txt
  "outer self: #{outer_self}, inner self: #{self}"
end

f:id:ud_ike:20200907092349p:plain

ルーティングブロック内のすべてのメソッド呼び出しがSinatra::Applicationクラスのインスタンスに送信されることがわかる。また、外側のスコープはmainである。

参考

入門Sinatra(←電子書籍)

ーーー追記ーーー

続いて、課題でSinatraを使ってメモアプリを作った。

ud-ike.hatenablog.com