SinatraとHamlとScssとCoffeeScriptでモダンなWeb制作環境を構築する #2

ruby

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

前回はRubyのインストールから簡単に各メタ言語の使い方を紹介させて頂きました。今回は応用編を紹介したいと思います。

コンパイルする記述を一つにまとめる

前回紹介した方法は一番シンプルな方法でした。GETリクエストとパスの組み合わせに一つのhamlファイルを紐付けているため、このままではファイルが増える毎に記述をコピペしなくてはいけません。

get '/' do
  haml :index
end

# aboutページが欲しくなったら…?
get '/about' do
  haml :about
end

これではあまりモダンなとは言いがたいですよね。というわけで記述を省略できる方法ですが、getメソッドにパスを与えている部分を正規表現にすれば実現出来ます。
要するに以下のように記述すればいいのです。

  get %r{^/(.*)\.html$} do
    haml :"#{ params[:captures].first }"
  end

ひとつずつ説明していきます。
まずは正規表現でパスとマッチさせる部分ですが、パスの中に /(スラッシュ) が含まれるのは避けられないので %r{ } という記法を使っています。正規表現の囲みにスラッシュを使っていないためエスケープを省略できます。

%r{^/(.*)\.html$}

そしてこの正規表現の中身ですが、スラッシュから始まり、間を省略して、.html で終わるパスにマッチするようになっています。
この () で囲んだ部分は下のブロック内で params[:captures] という記述で取り出すことができます。しかしここで取り出せる形式は配列のため Array#first でさらに先頭の要素を取り出しています。

:"#{ params[:captures].first }"

最後にhamlメソッドにコンパイルするファイルの場所を教えているのですが、文字列ではなくシンボルという形式で渡したいので : で始まる文字列に似た記述になっています。ダブルクォートの中に #{ } で囲むことによってその位置に結果の値が展開されます。
これは他の文字列等と連結する場合に便利な記法ですので上記のように特に連結する相手がいない場合は以下のように記述することもできます。

haml params[:captures].first.to_sym

to_sym でシンボルに変換されます。

scssとcoffeescriptもまとめてみる

hamlと基本は同じと考えていいでしょう。ただ大体cssとjsを同じディレクトリに格納することは無いと思うので2つのディレクトに分けることを前提として、ファイル名と拡張子の組み合わせでコンパイルの記述を組み立てるようにしました。

get %r{^/(stylesheets|javascripts)/(.*)\.(css|js)$} do
  dir = params[:captures][0] == 'stylesheets' ? 'scss' : 'coffee'
  file = params[:captures][1]
  method = params[:captures][2] == 'css' ? :scss : :coffee
  send(method, :"#{ dir }/#{ file }")
end

こちらもひとつずつ説明していきます。
まずは正規表現の部分はスラッシュで始まり、格納するディレクトリの名称が2パターン(stylesheets、javascripts)あること、その次にスラッシュで区切り、ファイルやディレクトリの部分、最後に cssかjs の拡張子という構成です。

%r{^/(stylesheets|javascripts)/(.*)\.(css|js)$}

それぞれ () で囲んだ部分は params[:captures] のインデックス0〜2に順番に入っていますので、dir file method という変数に取り出しました。

  dir = params[:captures][0] == 'stylesheets' ? 'scss' : 'coffee'
  file = params[:captures][1]
  method = params[:captures][2] == 'css' ? :scss : :coffee

最後に send というRubyのメソッドで、呼び出したいメソッドを呼び出す ということをしています。sendの第二引数以降に値を渡すことで呼び出したいメソッドの引数にしてくれます。

  send(method, :"#{ dir }/#{ file }")
  
  # scss :"scss/hoge" といった記述を抽象化出来る

レイアウトを切り替える

例えば管理画面を /admin 以下で作成したい場合、レイアウトを変えられるとすごく便利ですよね。そんな時は sinatra-contribSinatra::Namespace というプラグインと組み合わせると簡単に記述できます。

具体的には以下の様なコードになります。

class App < Sinatra::Base
  register Sinatra::Namespace
  
  namespace '/admin' do
    get %r{^/(.*)\.html$} do
      haml :"admin/#{ params[:captures].first }", layout: :admin_layout
    end
  end
  
  # 先に記述したhaml用のコード
  get %r{^/(.*)\.html$} do
    haml :"#{ params[:captures].first }"
  end
end

まずクラス定義の頭で register というメソッドを使い Sinatra::Namespaceプラグイン を登録しています。このモジュールはGemfileで gem 'sinatra-contrib', require: 'sinatra/contrib/all' としているので(前回参照)特にrequire文を書く必要はありません。
次に namespace メソッドを使い /admin という括りの中で動作するようにブロックを渡し、中にgetのブロックを記述しています。
hamlメソッドの引数の最後に layout: :admin_layout が渡されているところに注目してください。
こうすることで views/admin_layout.haml を通常のレイアウトファイル(views/layout.haml)と切り替えてくれるようになります。

haml :"admin/#{ params[:captures].first }", layout: :admin_layout

しかしなんとなくこれは冗長です。getメソッドに渡している正規表現や、hamlメソッドの記述などが単なるコピペになっていまっています。
なので以下のようにリファクタリングしておきました。

class App < Sinatra::Base
  register Sinatra::Namespace
  
  HTML_PATTERN = %r{^/(.*)\.html$}
  
  def haml_render(file, option = {})
    admin_dir = option[:admin] ? 'admin/' : ''
    layout = option[:admin] ? :admin_layout : :layout
    haml :"#{ admin_fir }#{ file }", layout: layout
  end
  
  namespace '/admin' do
    get HTML_PATTERN do
      haml_render params[:captures].first, admin: true
    end
  end
  
  get HTML_PATTERN do
    haml_render params[:captures].first
  end
end

getメソッドの記述がかなりすっきりしました。cssやjsも同様の方法でadminという名前空間とそれ以外で出しわけのルールを作ることも可能だと思います。

まとめ

今回はRubyのコードをガッツリ書いて手間を省けるようにしてみました。アイデア次第でいろんなことが出来ると思うので是非自分にあったルールを作ってみてください。
現状のままだとブラウザからのリクエストがあった時にのみコンパイルするようになっていますので、次回はまとめて静的ファイルに保存出来るようにしてみたいと思います。

AWS Cloud Roadshow 2017 福岡