14. Webアプリ開発(ERB編)

ERBとは

ERBとは、HTMLなどのテキストにRubyのスクリプトを埋め込むためのライブラリです。
ここではSinatraと組み合わせて使用していきます。
erbファイル(拡張子 .erb)を使って、HTMLをRubyスクリプトを埋め込んだHTMLを書いていきます。
まずは ~/Documents/ruby_lecture/examples/erb_practice ディレクトリを新たに作成し、Sinatraの準備を行ってください。

  • app.rb は以下のように記述してください。
app.rb
1
2
3
4
5
6
7
8
9
10
11
12
require "sinatra/base"
require "sinatra/reloader"

class SampleApp < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  get "/sample1" do
    erb :sample1
  end
end
  • erbファイルを以下のように記述してください。
    なお、erbファイルは views ディレクトリに配置してください。
    どういう挙動になるか想像してみましょう。
sample1.erb
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <% 3.times do %>
      <div>erbのテスト</div>
    <% end %>
  </body>
</html>
  • config.ru も前章と同様の手順で作成します。

  • アプリケーションを起動し、ブラウザで http://localhost:9292/sample1 にアクセスしてください。

1
2
3
erbのテスト
erbのテスト
erbのテスト

Rubyのtimesメソッドにより、3回の繰り返しが実行されたことが分かります。
このように <% %> で囲んだ部分は、rubyのプログラムとして扱われます。

別の例を見てみましょう。

  • app.rb のクラス内に以下のコードを追加してください。
app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require "sinatra/base"
require "sinatra/reloader"

class SampleApp < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  get "/sample1" do
    erb :sample1
  end

  get "/" do
    @title = "トップページ"
    @message = "ここは一番はじめのページです"
    erb :index
  end

  get "/contents" do
    @title = "コンテンツページ"
    @names = ["taro", "jiro", "saburo"]
    erb :contents
  end
end
  • app.rbに追加したパスに対応するerbファイルをそれぞれviewsディレクトリの中に作成してください。
    ただし、 "/"に対応するerbファイルの名称は、index.erbとしてください。
index.erb
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <%= @title %>
    <br>
    <%= @message %>
  </body>
</html>
contents.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <%= @title %>
    <% @names.each do |name|  %>
      <div><%= name %>だよ</div>
    <% end %>
  </body>
</html>
  • ブラウザでhttp://localhost:9292/にアクセスし、以下のようになることを確認しましょう。
    • トップページ ここは一番はじめのページです」と表示される
    • @title変数と@message変数が index.erb で利用できている
  • ブラウザでhttp://localhost:9292/contentsにアクセスし、同様に確認してください。
    • コンテンツページ」と表示される
    • @title変数が contents.erb で利用できている
    • @names変数(配列)の値が表示されている

このように、インスタンス変数を使用してerbファイルに値を渡すことが可能です。
また、<%= %>で囲うと、rubyのプログラムとして評価された値を表示することができます。

画像・CSS

せっかく作るサイトですから華やかなものにしたいですね。
画像やCSSを使いたい場合は、 public ディレクトリに入れます。

public/images/logo.png に画像を置いた場合は、htmlやerbのbodyタグ内で下記のように書くと表示できます。

画像の表示

1
<img src="/images/logo.png">

CSSの場合も同様で、 public/stylesheets/common.css に置いた場合は、
htmlやerbで下記のように書いてください。

CSSの読み込み

1
<link rel="stylesheet" type="text/css" href="stylesheets/common.css">

章末に演習問題を用意しているので、そこで実践してみましょう。

リダイレクト

リダイレクトとは、ユーザーが特定のURLにアクセスしたときに、自動的に別のURLへとアクセスさせる仕組みのことです。
これを利用することで、あるURLへのアクセスがあった場合に条件判断などを行い、結果に応じて個別のURLにアクセスさせることができます。

それでは、実際にリダイレクトの様子を確認してみましょう。
作業は erb_practice ディレクトリをそのまま使ってください。

app.rb に、以下を追加しましょう。
/judge?age=31 のように、年齢パラメータ(age)をつけてアクセスすると、
30歳以上はおじさんと判断され /ojisan ページに、それ以外は若造と判断され /wakazo ページにリダイレクトされます。

app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require "sinatra/base"
require "sinatra/reloader"

class SampleApp < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  # (省略)

  # 以下を追加
  get "/entrance" do
    @title = "年齢確認ページ"
    erb :entrance
  end

  get "/judge" do
    if params[:age].to_i >= 30
      redirect "/ojisan"
    else
      redirect "/wakazo"
    end
  end

  get "/ojisan" do
    erb :ojisan
  end

  get "/wakazo" do
    erb :wakazo
  end

end

次に、下記にしたがって必要なページを用意しましょう。

entrance.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <%= @title %>
    <form method="GET" action="/judge">
      <label>年齢</label>
      <input type="text" name="age">
      <br>
      <input type="submit" value="送信">
    </form>
  </body>
</html>
ojisan.erb
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    ここはおじさんのページだよ
  </body>
</html>
wakazo.erb
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    ここは若造のページだよ
  </body>
</html>

http://localhost:9292/entranceにアクセスしてみましょう。
テキストフォームがあるので、そこに任意の年齢を入力してenterキーを押してみてください。
年齢に応じてアドレスが変わり、表示されるページが変わったかと思います。

リダイレクト時のパラメータの受け渡し

次に、 /entrance で入力された名前をおじさん、若造のページにも表示できるように、先ほどの erb_practice を拡張しましょう。

まずは、 entrance.erb に名前を入力するフォームを追加してください。

entrance.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <%= @title %>

    <form method="GET" action="/judge">
      <label>名前</label>
      <input type="text" name="username">
      <br>
      <label>年齢</label>
      <input type="text" name="age">
      <br>
      <input type="submit" value="送信">
    </form>
  </body>
</html>

これで、送信ボタンを押した時に、usernameパラメータがつくようになりました。
http://localhost:9292/entranceにアクセスして確認してみましょう。

おじさんページ、若造ページそれぞれに以下を追加して、usernameを表示させましょう。

ojisan.erb / wakazo.erb
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    ここはおじさん / 若造のぺーじだよ
    <%= "#{params[:username]}さん、こんにちは!" %>
  </body>
</html>

この状態で、動かしてみましょう。…..あれ、表示されませんね。

どうやらリダイレクト先にパラメータが引き継げていないようです。
これは、 /entrance のページから渡されたパラメータが /judge のページを経由する際に捨てられてしまうからです。
そこで、 /judge からリダイレクトする際に、クエリを使ってもう一度パラメータを渡すようにします。

app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require "sinatra/base"
require "sinatra/reloader"

class SampleApp < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  # (省略)

  get "/judge" do
    if params[:age].to_i >= 30
      redirect "/ojisan?username=#{params[:username]}" # <= 変更
    else
      redirect "/wakazo?username=#{params[:username]}" # <= 変更
    end
  end

  # (省略)

end

それではもう一度試してみましょう。
リダイレクト先にパラメータが受け渡せたでしょうか。

Step Up! : リダイレクト時のパラメータエンコード

先ほどのアプリケーションで、名前のフォームに日本語など全角文字を入力するとエラーになってしまうことに気づいたでしょうか?
これは、URLに日本語を使うときにはエンコードという変換が必要だからです。
(Chromeでアドレスを直接入力した時や、単純な画面遷移では、勝手にエンコードされています。)
以下のように修正すると、日本語も使えるようになります。

app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require "sinatra/base"
require "sinatra/reloader"
require "uri" # <= 追加

class SampleApp < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  # (省略)

  get "/judge" do
    if params[:age].to_i >= 30
      redirect "/ojisan?#{URI.encode_www_form(params)}" # <= 変更
    else
      redirect "/wakazo?#{URI.encode_www_form(params)}" # <= 変更
    end
  end

  # (省略)

end

URI.encode_www_form は、Hashで値を受けて、エンコードしながら username=名前(エンコード済)&age=15 のようなクエリ文字列に変換してくれます。


これを利用することで、日本語が使えるようになっただけでなく、パラメータがどんどん増えても、修正する必要がなくなりました。
しかも、見た目もすっきり。万々歳ですね。

ERBのインクルード(部分テンプレート)

HTMLのヘッダーやフッターなど、各ページで同じ記述にしたい場合があります。
そのような場合には、ERBの 部分テンプレート機能 を利用することで、共通部分を別のERBに切り出しておくことができます。
例えば、 先の項目 でおじさんページ・若造ページの両方に記述したusernameを、部分テンプレート機能を利用してフッターとして表示してみましょう。
部分テンプレートを使用する際のファイル構成の例を示します。

1
2
3
4
5
6
7
8
/erb_test
:
├/views                 : テンプレートファイルを置くディレクトリ
│   :
│  ├ ojisan.erb         : 部分テンプレートを読み込むerbファイル
│  ├ wakazo.erb         : 部分テンプレートを読み込むerbファイル
│  └ footer_content.erb : 部分テンプレートとして使用するerbファイル
└ app.rb                : メインアプリケーションファイル

ojisan.erb / wakazo.erbfooterタグの中に、以下のように <%= erb :footer_content %> と記述することで、部分テンプレートを取り込みます。

ojisan.erb / wakazo.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    ここはおじさん / 若造のぺーじだよ
    <%= "#{params[:username]}さん、こんにちは!" %>

    <footer>
      <%= erb :footer_content %>
    </footer>
  </body>
</html>

また、部分テンプレートとなる footer_content.erb の中身を以下のように記述します。

footer_content.erb
1
<p><%= "#{params[:username]}さん、こんにちは!" %></p>

この状態でおじさんページか若者ページにアクセスすると、フッターとしてusernameを表示できるようになります。
今回の例では部分テンプレートの中身が小さいため、あまり恩恵を感じることができませんが、部分テンプレートとしてくくり出せる共通部分が増えるほど、メンテナンスや可読性の面で大きな効果が得られます。


演習問題

問1

~/Documents/ruby_lecture/excercises/ に、任意の名称でSinatra演習用のディレクトリを作成し、以下の問に進んでください。

問1-1

http://localhost:9292/users/{ユーザ名} にアクセスすると、そのユーザのアイコン画像を表示するページを作成することを考えます。
以下のURLにアクセスした時に、user1のアイコン画像 user1.png (用いる画像は問わない)を表示するようにしてください。
表示内容は画像のみで構いません。

問1-2

http://localhost:9292/users/user1 と同様に、

にアクセスした時に、それぞれのユーザのアイコン画像を表示するように修正してください。
ただし、それぞれのユーザに用いるアイコン画像はすべて異なるものとし、使用してよいrbファイルおよびerbファイルは一つまでとします。

問1-3

上記の各ページに、アクセスした時の日時を最終アクセス日時として表示してください。

Note : 共通処理の記述

複数のパスで同じ処理が必要な時、毎回 app.rb に同じことを書くのは大変です。
下記のように書くと、各パスで共通の処理をひとまとめにしておけます。
共通の処理をひとまとめにするという点で部分テンプレートと似ていますが、以下の違いがあります。


  • 部分テンプレート:「異なるerbファイル間」で共通部分をまとめるもの
  • before:「異なるパス間」で共通処理をまとめるもの
app.rb
1
2
3
4
5
before do
  # 共通処理
end

get "***" do

URI.encode_www_form は、Hashで値を受けて、エンコードしながら username=名前(エンコード済)&age=15 のようなクエリ文字列に変換してくれます。
これを利用することで、日本語が使えるようになっただけでなく、パラメータがどんどん増えても、修正する必要がなくなりました。
しかも、見た目もすっきり。万々歳ですね。

問1-4(チャレンジ問題)

トップページ(http://localhost:9292/)でユーザ名とパスワードを入力して、
認証の結果、登録済みのユーザかつ正しいパスワードだった場合にそのユーザのページを表示し、誤りの場合はトップページに戻るような仕組みを作って下さい。

※ パスワードは自由に設定して構いません。
※ パスワード認証のためのユーザ情報の保持は変数で行って構いません。

時間が余ったら、次のような課題を設定してチャレンジしてみましょう。
認証に失敗した場合ことがわかるようにトップページに表示を追加してみましょう。
JSONファイルなどに保存したユーザデータからの認証に挑戦してみましょう。
現状だと、各ユーザのURLを知っている人間であれば誰でもパスワード入力を介さずにそのページにアクセスできてしまいます。
そこで、何らかの工夫を加えてパスワードを知っている人間でない限りユーザのページにアクセスできないような仕組みを入れてみましょう。