Railsで挫折した人のためのSinatra -3-
コントローラーっぽいのを作る
前回軽く動かしてみて、かなり簡単にerbファイルを表示できるとわかったと思います。
しかし、最後の部分で課題も見つかりました。
それが、route.rbの肥大化問題です。これを避けるために逃せる記述を機能毎に別ファイルに逃してコントローラーのようなものの実現を目指します。
まず、sinatra-app以下にcontrollersディレクトリを作成してください。
そして、controllers以下に3つのファイルを追加してください
- haiku.rb
- top.rb
- base.rb
これができたらsinatra-app以下にconfig.ruというファイルを追加して、以下の内容を記述してください。
/config.ru↓
require './controllers/haiku.rb'
require './controllers/top.rb'
run Rack::URLMap.new({
'/' => TopController.new,
'/haiku' => HaikuController.new
})
今までroute.rbがルーティングの振り分けを担ってきましたが、config.ruファイルにそれを任せます。Railsでもたまに目にするRackというものを使います。この中で各コントローラーインスタンスを作成することでurlとのマッピングを行ってくれます。
さて、rackを使うことになったので起動コマンドが変わることになります。
起動コマンドを格納しているstart.shを開いて以下の様に修正してください。
/start.sh ↓
#!/bin/bash
bundle install && \
bundle exec rackup config.ru -p 4567 -o 0.0.0.0
rackupというコマンドでconfig.ruファイルを読み込んでいます。そしてrackupを使うと、default状態だとポートを9292にフォーワーディングしてしまうので-pで今まで使っていたポートを指定しています。
以上で設定の部分は終了したのでcontrollersの中を修正していきます。
それぞれ以下のように修正してください。
/controllers/base.rb ↓
require 'sinatra/base'
class Base < Sinatra::Base
set :root, File.join(File.dirname(__FILE__), '..')
set :views, Proc.new { File.join(root, "views") }
end
/controllers/haiku.rb ↓
require './controllers/base.rb'
class HaikuController < Base
get '/' do
@title = "haiku-app"
erb :index
end
end
/controllers/top.rb ↓
require './controllers/base.rb'
class TopController < Base
get '/' do
@title = "sinatra-app"
erb :index
end
end
この修正ではそれぞれのコントローラーにbase.rbファイルのBaseクラスを継承させています。なぜかというとcontrollers以下にコントローラーファイルを振り分けると、viewsを呼び出すときにcontrollersをrootと認識してcontrollers/views/を探してビューファイルを見つけようとするのですが、そんなものは存在しないのでエラーになります。
そのため、最初にプロジェクトのrootとビューファイルの在り処を設定する必要があるのでそれをBaseクラスに任せて、それを各コントローラークラスで継承しています。1章のRubyの復習の章で、Humanクラスを継承したManクラスがgetNameを使えたのと同じことが起きています。
さて、これで晴れてお役御免となったroute.rbファイルは削除していただいてかまいません。今までお疲れさまでした。。。
では、これでちゃんと動くのか確認してみましょう!
http://localhost:4567 と
http://localhost:4567/haiku にアクセスしてそれぞれ以前と同じ画面になっているか確認してください。
easy-haikuの機能の実装
以前と同じ画面が表示されていれば大丈夫です。
やっと、easy-haikuの機能を追加することができるようになったので、
仕様について思い出してみましょう。
haiku-app は top画面、俳句一覧兼俳句投稿画面、俳句詳細画面を持っていて編集はできませんが削除はできます。将来的にログイン機能や評価機能をつけたいですが、今は気にしなくて結構です。
ですのでtop画面、俳句一覧兼俳句投稿画面、俳句詳細画面の3つを作っていきます。
まず、/views内に3つのファイルを新たに作っていただきます。
- show.erb
- top.erb
- layout.erb
そして以下のようにそれぞれ記述していきます。
/views/show.erb ↓
<main>
<p>
show
<%= @title %>
</p>
</main>
/views/top.erb ↓
<main>
<h1>What is easy-haiku?</h1>
<p>
easy-haiku is ...
</p>
<a href="/haiku">to list page</a>
</main>
/views/layout.erb ↓
<html>
<head>
<title>easy-haiku</title>
</head>
<body>
<header>
<a href="/">to top</a>
<a href="/haiku">to list</a>
</header>
<%= yield %>
<footer></footer>
</body>
</html>
続いて各コントローラーも以下の様に書き換えましょう。
/controllers/haiku.rb ↓
require './controllers/base.rb'
class HaikuController < Base
get '/' do
@title = "haiku-app"
erb :index
end
# 追加部分↓
get '/:id' do
@title = "haiku-app" + params[:id]
erb :show
end
end
/controllers/top.rb
require './controllers/base.rb'
class TopController < Base
get '/' do
@title = "sinatra-app"
erb :top # 修正部分
end
end
これで各コントローラーで目当てのビューファイルを呼び出すことができるようになったと思います。大事なのはSinatraはdefaultでlayout.erbをlayoutに設定できるところです。他のファイルをlayoutとして適用させたいときは別途記述が必要です。
それでは、docker-composeを立ち上げ直して http://localhost:4567 と http://localhost:4567/haiku/1
にアクセスして確認してみましょう!
データの挿入
さて画面の用意はできたのですが、DBとの連携がまだできていないため、index.erbで一覧画面を作ったり詳細ページに遷移したりができません。
このようなときは疑似的なデータを用意してあげてそれを使いましょう。sinatra-app以下にmodelsディレクトリを用意して、その中にhaiku.rbを作成して以下の記述をしてください。
/models/haiku.rb ↓
class Haiku
# 疑似データ
@index = [
{ main: "古池や蛙飛こむ水のおと", id: 1 },
{ main: "閑さや岩にしみ入蝉の声", id: 2 },
{ main: "柿くへば鐘が鳴るなり法隆寺", id: 3 },
]
# getterメソッド 俳句一覧取得
def self.getIndex
@index
end
# getterメソッド 俳句取得
def self.getByID(id)
@index.each {|item|
if Integer(id) == item[:id]
return item
end
}
end
end
ここにHaikuというクラスを用意してその中にデータを用意しました。
そのまま@indexをreturnしたいところですが、getterメソッドという読み取り専用のメソッドを通じて他のクラス等でこの@indexを取得します。
詳しくはカプセル化等で調べると良いです。
ちなみにself.関数名はクラスメソッドというもので、インスタンスを生成していない状態でも使えるメソッドです。
さて、実際にこのデータを使ってみましょう。
/controllers/haiku.rb , /views/index.erb , /views/show.erb の3つのファイルを以下の様に修正してください。
/controllers/haiku.rb ↓
require './controllers/base.rb'
require './models/haiku.rb'
class HaikuController < Base
get '/' do
@haikulist = Haiku::getIndex # 修正部分
erb :index
end
get '/:id' do
@haiku = Haiku::getByID(params[:id]) # 修正部分
erb :show
end
end
# 修正部分 ではHaikuのクラスメソッドをHaiku::メソッド名として呼び出しています。それにより@haikulistには俳句の配列が、@haikuには urlの数字部分をもとに適応する俳句を取得してきています。
/views/index.erb ↓
<main>
<h1>Haiku list</h1>
<% @haikulist.each do |haiku| %>
<p><a href=<%="/haiku/#{haiku[:id]}"%> >No.<%= haiku[:id] %> <%= haiku[:main] %></a></p>
<% end %>
</main>
htmlの中でrubyの記述を使いたいときは<%%>で囲まないといけないのでaタグのhref部分でも囲んであげています。
/views/show.erb ↓
<main>
<h1>Haiku details</h1>
<p>
<%= @haiku[:main] %>
</p>
</main>
修正ができたらdockerを再起動して各ページを見てみましょう。
- http://localhost:4567
だいぶアプリケーションらしくなってきたと思います。
かなり色々なファイルやフォルダを作ってきたので最後に過不足を確認してみましょう。以下のようになっていれば大丈夫です。
$ tree .
.
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── config.ru
├── controllers
│ ├── base.rb
│ ├── haiku.rb
│ └── top.rb
├── docker-compose.yml
├── init.sh
├── models
│ └── haiku.rb
├── start.sh
├── vendor
│ └── bundle
│ └── ruby(以下略...)
└── views
├── index.erb
├── layout.erb
├── show.erb
└── top.erb
ファイル数がすごいことになってきました。
それでは次回からDBとの連携になります。
そちらも分厚い章となりますが、最後まで頑張りましょう。
それでは、お疲れ様でした。