nanoc でSQLite3のデータからページを作る

同じようなコンテンツが大量にあるけれど,
静的なHTMLのみで作らなければならないサイトがあったので
nanoc*1 というRuby製のコンテンツビルドツールを使ってみました.

nanocってなにさ?

コンテンツのパーツを作っていき,一気に「コンパイル」してサイトを構成してくれる逸品です.
作ったパーツから同じ「タグ」要素の一覧ページを生成したり,
投稿日ごとのアーカイブを生成するなど,
ブログっぽいサイトを静的ページのみで作れます :-)

ブログサイトを作る過程は以下の記事がとても参考になりました.

前フリ

一日1ページのコンテンツを用意するにも,記事のファイルを一つ一つ書いて....
というのがとても面倒大変で,
しかもHTML書けない人(エンドユーザ)にデータを用意してもらうことも難しいので,
Rails で入力フォームをざっくり作り*2
SQLite3のデータベースへコンテンツの要素を登録してもらいました.

で,ここからがnanocの話.
「静的なWebサイト」の制約があるのでRailsで公開コンテンツを作れないので
登録してもらったデータを読み込んでnanocでページを生成してみたわけです.

nanocのData Source

Nanoc3::DataSource というクラスを継承して
取得したデータからnanoc用のオブジェクト*3を作ることで,
「コンパイル」の際にデータを埋め込んだページを生成することができます.

Rails のフォームではSQLiteを使っていたので,ActiveRecord*4を使って実装してみました.
ここでは,日報を表す「report」テーブルから1レコード1ページで作る例を示します.

# ./lib 以下に用意
class ReportSource < Nanoc3::DataSource

  require 'sqlite3'
  require 'active_record'
  require 'yaml'

  # Data Sourceにつける名前.設定ファイルで利用
  identifier :reports

  # nanoc用オブジェクトを配列で返すためのメソッド(オーバーライド)
  def items
    items_list = []
    
    # Railsでの接続設定用ファイルを流用
    # 作るサイトのディレクトリ直下にYAMLファイルを配置
    ActiveRecord::Base.configurations = YAML.load_file("database.yml")

    # ActiveRecordでDBに接続
    ActiveRecord::Base.establish_connection('development')
    
    # 全件取得
    Reports.find(:all).each do |report|

      # HTMLファイルを作るためのテンプレート
      content = "<%= render('_report_page') %>"

      # ページに埋め込む属性(データ)のHash
      # {:フィールド名 => 値} というHashが.attributesメソッドで取得できる
      attributes = report.attributes

      # metaのtitle要素用に加工
      attributes[:title] = "日報: " + report.titile

      # URLを決めるオブジェクト名
      identifier = "/#{report.id}/"

      # ページの更新日付(デフォルトはnil)
      mtime = report.updated_at

      # nanocのコンテンツ用オブジェクトを用意
      items_list << Nanoc3::Item.new(content, attributes, identifier, mtime)
    end
    
    items_list
  end

end

# ActiveRecord用にデータモデルの空クラスを用意
class Reports < ActiveRecord::Base
end

各ページのテンプレート(上記の_report_page)は以下のように,erb形式でデータを埋め込んでいます.

<h2><%= item[:title] %><h2>
<p>
  <b>Reporter:</b>
  <%= item[:reporter] %>
</p>

<p>
  <b>Report at:</b>
  <%= item[:report_at] %>
</p>
....
Nanoc3::Item.new(content, attributes, identifier, mtime)

でセットしたattributesにはDBのレコードの各フィールドが埋めこまれていて,
「item」に格納されているので,上のようにitem[:フィールド名] で値を取り出しています.

「コンパイル」の時にこのDetaSourceを呼び出すために,
config.yamlの以下の部分に追記します.

....
data_sources:
  -
    # デフォルトの設定...
  -
    # 上記のReportSourceにある"identifier"
    type: reports
    # URLのルート.http://hogehoge/reports/id番号/ というページ構成にする
    items_root: /reports/
    # レイアウトの配置
    layouts_root: /

ここまでの設定で、コンパイル後に ./output/ ディレクトリに

./output/reports/1/index.html
./output/reports/2/index.html
./output/reports/3/index.html
./output/reports/4/index.html
....

とレコードの数だけコンテンツが生成されるわけです.(しかもRestぽい)

なぜActiveRecord

SQLite::Databaseを使ってDBに接続しても良かったのですが,
取得したレコードをテンプレートに埋め込むHashを作る際に
以下のようにレコードの配列の番号を指定して...が面倒で短調でバグになりそうな臭いがするので
ActiveRecordでサッとHashを用意しました.

...
db = SQLite3::Database.new("hogehoge.db")

db.execute("select * from report") do |row|
...
  attributes = {
     :reporter => row[1],
     ....
  }
end


ActiveRecordの使い方は以下を参考にしました.

*1: nanoc (3.1.6)

*2:scaffoldのそっけないもの

*3:Nanoc::Item オブジェクト

*4:activerecord (3.0.7)