Octopressにsinatraでアプリケーションを組み込む

木内智史之介(シャッチョー)
ミンカさんけっこんしてくださいおねがいします(ズザー
SEGAさん、DIVAの筐体ください(ズザー

TOP

Octopressの課題

弊社ブログはOctopressを活用して配信されています。

  • 複数人で執筆することができる(要plugin)
  • ローカルで気軽に編集可能
  • デザインのカスタマイズ性/機能の拡張性

などが気に入り、採用を決めました。
Octopressがあまりに使い心地がいいために、結果的に、ブログだけでなく、弊社のHPすべてがOctopressを用いてmarkdownで記述されることになりました。

ただ、そんなOctopressにも最大の弱点があります。

「動的なコンテンツ」にとにかく弱いことです。

そう、例えば「フォームでの情報送信、およびエラー処理」などです。

エントリーフォームがほしかった

弊社デザイナーの「ZOMIO」さんは

「エントリーしたのですがなかなか連絡をいただけません」

とわざわざ凸電いれていただけるだけの強者だったので幸いにも弊社で働いていただける事になりましたが、 それもそのはず、弊社HPのエントリーフォームは、実はまったく動いていなかったのです。
markdownで記述された、ただの張りぼてでした。

これではいかん!ということで、急遽システムを組み込む必要性がでてきました。

いくつかある選択肢

エントリーフォームをシステム化するにあたって、いくつかの方法論があるとは思います。

  1. PHPスクリプトで実装する
  2. rubyスクリプトで実装する
  3. ruby on railsで実装する
  4. sinatraで実装する

いずれであっても対応は可能ですが、今回のケースでは「Octopressのpreview機能がsinatraを利用している」ことに目をつけて、 sinatraで実装することを決めました。 「rake preview」でいつもブログを書いているのと同じ手順でアプリケーションを作成できることを重視した感じです。

Octopressのpreviewアプリケーションに独自実装を加える

Sinatraはrackに対応したフレームワークですので、まず見るべきファイルは「config.ru」です。
Octopress標準のconfig.ruファイルを確認してみることにしましょう。

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
require 'bundler/setup'
require 'sinatra/base'

# The project root directory
$root = ::File.dirname(__FILE__)

class SinatraStaticServer < Sinatra::Base

  get(/.+/) do
    send_sinatra_file(request.path) {404}
  end

  not_found do
    send_file(File.join(File.dirname(__FILE__), 'public', '404.html'), {:status => 404})
  end

  def send_sinatra_file(path, &missing_file_block)
    file_path = File.join(File.dirname(__FILE__), 'public',  path)
    file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i
    File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
  end

end

run SinatraStaticServer

とてもスッキリしていてわかりやすいですね。
さあここに実装していけばいいんでしょ!と、このconfig.ruの編集に乗り出す前にちょっと考えてみましょう。
そうです、このconfig.ruファイルはdeploy対象ではないのです。
このファイルはあくまでもpreview時のローカル確認のためのものなので、ここに直接手を入れても、おそらく目標を達成できないような気がしますよね?

というわけで、自分はこんな感じに2行ほど追加してみることにしました。

config.ru

1
2
3
require './source/app/app'

use App::Server

octopressではsourceディレクトリ以下がdeploy対象ですので、まずそこにsinatraアプリケーションとして実装するためのコードを配置します。

source/app/app.rb

1
2
3
4
5
6
7
8
9
10
# coding: utf-8
require 'sinatra/base'

module App
  class Server < Sinatra::Base
    get '/hello/app' do
      'Hello App !!'
    end
  end
end

この状態でpreviewしてアプリにアクセスすると、きっちり動いているのが分かりますね!
いい感じです。
hello app

これで、アプリケーションを実装するための箱ができあがりました。
後は煮るなり焼くなり好きにしろ!というやつです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module App
  class Server < Sinatra::Base
    # エントリー:入力
    get '/recruit/entry' do
      initEntryForm
      haml :recruit_entry_input, :locals => @locals
    end

    # エントリー:確認
    post '/recruit/entry/confirm' do
      initEntryForm

      # 入力チェック
      # ...snip

      unless @errors.empty?
        haml :recruit_entry_input, :locals => @locals
      else
        haml :recruit_entry_confirm, :locals => @locals
      end
    end
  end
end

色々端折っていますが、こんな感じでさくっと実装できます。
続いて、deploy後のサーバーで動かすための設定を施すことにします。

unicornの設定

unicornと言えばrack。rackといえばunicorn。
というわけで、何も考えずにunicornの設定ファイルを作成しましょう。

source/app/unicorn.rb

1
2
3
4
5
6
7
8
9
10
11
12
@dir = "/path/to/app"

worker_processes 3
working_directory @dir

timeout 300
listen 8081
listen "#{@dir}/tmp/unicorn.sock", :backlog => 1

pid "#{@dir}/tmp/unicorn.pid"
stderr_path "#{@dir}/log/unicorn.error.log"
stdout_path "#{@dir}/log/unicorn.stdout.log"

source/app/config.ru

1
2
3
4
5
6
# coding: utf-8
require 'bundler/setup'
require 'sinatra/base'
require './app'

run App::Server

unicornの起動

1
2
$ cd /path/to/app
$ unicorn -c unicorn.rb -D

nginxの設定

nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
location / {
    root   /path/to/homepage;
    try_files $uri $uri.html $uri/index.html $uri/index.htm @homepage_app;
}
location @homepage_app {
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://homepage_app;
}
upstream homepage_app {
    server  unix:/path/to/app/tmp/unicorn.sock;
}

存在しないファイルへのアクセスを、別途起ち上げているunicornに投げることで設定完了です。
これで、Octopressを、Octopressのまま、rake previewで本番環境と同じように全ての内容を確認可能になりました。

Octopressでいけてないその他

Octopressにおけるpagination処理って、今自分たちが活用しているバージョンでは、「all posts」に対するもののみだったんですよ。 つまり、カテゴリー毎のページ処理とか、author毎のページ処理とかやる際に、かなりいじる必要性がありました。
そのあたりの記事は別の機会にでもあげようと思います。

それではよいOctopress Lifeを!