2018年5月31日木曜日

Sinatra でコントローラを分離する方法

概要

app.rb など一つのファイルにいろいろなルーティングを実装するとファイルを分けたくなると思います
今回はその方法を紹介したいと思います
おそらく王道の方法かなと思います

環境

  • macOS 10.13.4
  • Ruby 2.5.1p57
  • Sinatra 2.0.1

各種ファイルの準備

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • touch config.ru
  • mkdir controllers
  • touch controllers/home.rb
  • touch controllers/admin.rb
  • mkdir views
  • touch views/home.erb
  • touch views/admin.erb

今回は分離するコントローラは controllers というディレクトリで管理することにします
全体の構成は以下のとおりです

.
├── .bundle
│   └── config
├── Gemfile
├── Gemfile.lock
├── config.ru
├── controllers
│   ├── admin.rb
│   └── home.rb
└── views
    ├── admin.erb
    └── home.erb

3 directories, 8 files

config.ru の作成

Sinatra でコントローラを分ける場合、パスごとに処理するコントローラを Rack::URLMap という機能を使って定義します
今回は 2 つのルーティングを違うコントローラを使って処理します

require './controllers/home.rb'
require './controllers/admin.rb'

run Rack::URLMap.new({
        '/' => Home,
        '/admin' => Admin
})

作成した Rack::URLMap を run することを忘れないようにしてください
home.rb に Home クラスを admin.rb に Admin クラスを後で定義します

home.rb の作成

先程定義した / ルーティングを処理するクラスを定義します

  • vim controllers/home.rb
require 'sinatra/base'

class Home < Sinatra::Base
        set :root, File.join(File.dirname(__FILE__), '..')
        set :views, Proc.new { File.join(root, "views") } 

        get '/' do
                erb :home
        end

        get '/users' do
                'users'
        end
end

今回は 2 つの処理を記載しました
/ に対する処理と /users に対する処理です
ついでにテンプレートも作成します

  • vim views/home.erb
home

とりあえず文字列を返すようにします
作成したクラスは必ず Sinatra::Base を継承します
また今回のようにコントローラをアプリケーションの直下ではなく controllers といったサブディレクトリに配置する場合には Sinatra の :root:views の書き換えが必要になります

set :root, File.join(File.dirname(__FILE__), '..')
set :views, Proc.new { File.join(root, "views") } 

何をしているかというと home.rb の一つ上位のディレクトリを :root に設定しかつ :root 配下にある views というディレクトリをテンプレートを定義するディレクトリとして設定しています
デフォルトだとこれがコントローラクラスの直下になっています

admin.rb の作成

今度は /admin 配下を処理する Admin クラスを定義します

  • vim controllers/admin.rb
require 'sinatra/base'

class Admin < Sinatra::Base
        set :root, File.join(File.dirname(__FILE__), '..')
        set :views, Proc.new { File.join(root, "views") } 

        get '/' do
                erb :admin
        end

        get '/users' do
                'admin users'
        end
end
  • vim views/admin.erb
admin

先程の home.rb とほぼ同じように定義されていることがわかります
が、admin.rb/admin 配下のルーティングを処理します
なので上記の場合、処理するパスとしては /admin/admin/users が対象になります
このように URLMap で定義したパスはコントローラ内の実装では省略することができます

動作確認

  • bundle exec rackup config.ru

で起動しましょう
localhost:9292 で起動します
確認するパスとしては以下の 4 つです

  • / -> home が返ってくる
  • /users -> users が返ってくる
  • /admin -> admin が返ってくる
  • /admin/users -> admin users が返ってくる

となれば OK です
上記以外のパスにアクセスした場合は Sinatra のデフォルトの 404 ページが返ってくると思います

ちょっとしたテクニック

「404 や 500 ページは共通のページを出したい」
「コントローラクラスに毎回 :root:views を定義するのは面倒」
という場合には Base クラスを作成しましょう

  • vim 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

そして home.rb で継承するクラスを Base クラスに変更すれば OK です

  • vim controllers/home.rb
require './controllers/base.rb'

class Home < Base
        get '/' do
                erb :home
        end

        get '/users' do
                'users'
        end
end

こうすることで毎回定義する必要がなくなります
また、先程少し出てきた 404 ページの処理などもこの base.rb に記載すれば継承した home.rb などで実装する必要がなくなります

また rspec と組み合わせたテストを行いたい場合は spec_helper.rb で app メソッドを定義する際に Rack::URLMap を返すようにすれば OK です
各種コントローラクラスの require も忘れないようにしてください

module RSpecMixin
  include Rack::Test::Methods

  def app
    Rack::URLMap.new({
      '/' => Home,
      '/admin' => Admin
    })
  end
end

最後に

Sinatra でコントローラを分離する方法を紹介しました
おそらくこれが王道の方法だと思います

このあたりの処理は Sinatra では自分で実装するしかありません
が、rails などを使えばフレームワーク側で全部やってくれるものもあります
ただ、rails の場合はルーティングを追加すると実装するべき関数も勝手にいろいろ定義されるのでやりすぎちゃう感もあります
この辺りは自分のやりたいことに合わせて選択すれば良いかなと思います

参考サイト

0 件のコメント:

コメントを投稿