本を読む

読書やコンピュータなどに関するメモ

Bash on Railsを作る(6) config/database.sh

 くどいようですが、bashの内蔵コマンドでいかにRuby on Railsっぽいことをやるかというパロディ企画です。今回は、「Bash on Railsを作る(1) メタプログラミングでDSL」で触れたconfig/daatabase.shファイルの解説をしてみます。

まず基本方針

 bashはオンメモリーのデータ構造を操作するのは苦手です。そこで、読み込み時にデータをバラして、データは決め打ちで持つようにします。

config/database.shファイル

まずはおさらい。YAML風のconfig/database.shファイルの中身は、以下のとおりです。

# database configuration
development:
    adapter: sqlite3
    database: db/development.sqlite3
    timeout: 5000

test:
    adapter: sqlite3
    database: db/test.sqlite3
    timeout: 5000

production:
    adapter: sqlite3
    database: db/production.sqlite3
    timeout: 5000

これシェルスクリプトじゃね?

 この内容を見てみると、以下のことに気付きます。

  • コメントが「#」で始まってる
  • 第1レベル(インデントなし)と第2レベル(インデント1つ)でボキャブラリが完全に分離してる
  • 第2レベルのボキャブラリが同じ

 あと、こんなことも気付きます。

  • シェルスクリプトって、行頭に空白が入ってても無視されるよね
  • bashってコマンド名に「:」が含まれていてもいいんだよね
  • この通りのフォーマットなら、「:」のあとにスペースが入ってるね
  •  ということで、こんなフラットなシェルスクリプトと同じじゃね? と。

    # database configuration
    development:
    adapter: sqlite3
    database: db/development.sqlite3
    timeout: 5000
    
    test:
    adapter: sqlite3
    database: db/test.sqlite3
    timeout: 5000
    
    production:
    adapter: sqlite3
    database: db/production.sqlite3
    timeout: 5000

    頭を使わない実装

     まずは単純な実装をしてみます。第1レベルにあるコマンドは設定のモードの変更、第2レベルにあるコマンドは設定を実行するものとします。また、事前にSHAILS_ENVという変数に環境(development、test、productionのいずれか)が入っているものとします。

    function development:() {
        conf_env='development'
    }
    
    function testing:() {
        conf_env='testing'
    }
    
    function production:() {
        conf_env='production'
    }
    
    function adapter:() {
        if [ "$SHAILS_ENV" == "$conf_env" ]; then
            dbconf_adapter=$1
        fi
    }
    
    function database:() {
        if [ "$SHAILS_ENV" == "$conf_env" ]; then
            dbconf_database=$1
        fi
    }
    
    function timeout:() {
        if [ "$SHAILS_ENV" == "$conf_env" ]; then
            dbconf_timeout=$1
        fi
    }

     これらの定義の後に、config/database.shをsourceしてやれば、設定をシェル変数に展開できます。

    . config/database.sh

    メタプログラミングでDSLでDRY

     上の実装は、同じようなコードが並んでますね。長ったらしいだけでなく、設定項目が増えたり変更されたりした場合に、コピペしなくちゃならなくて面倒です。

     そこで、メタプログラミングでDSL(笑)。動的にコマンド(関数)を定義してみます。

    DBCONF_ENVIRONMENTS="development test production"
    DBCONF_PARAMETERS="adapter database timeout"
    
    for env in $DBCONF_ENVIRONMENTS; do
        eval "function ${env}:() {
                  conf_env='${env}'
              }"
    done
    
    for param in $DBCONF_PARAMETERS; do
        eval "function ${param}:() {
                  if [ "\$SHAILS_ENV" == "\$conf_env" ]; then
                      dbconf_${param}=\$1
                  fi
              }"
    done

     だいぶDRYでスッキリなものになりました。

    状態を表す変数を閉じ込める

     さて、上のコードだと、conf_envという状態変数がグローバルになっているのが気持ちが悪いところです(私にとって)。そこで、「Bash on Railsを作る(4) bashのlocal変数」で解説した方法によって、conf_envをローカル変数に封じ込めます。

    DBCONF_ENVIRONMENTS="development test production"
    DBCONF_PARAMETERS="adapter database timeout"
    
    function define_dbconf_functions() {
        local env param
    
        for env in $DBCONF_ENVIRONMENTS; do
            eval "function ${env}:() {
                      conf_env='${env}'
                  }"
        done
    
        for param in $DBCONF_PARAMETERS; do
            eval "function ${param}:() {
                      if [ "\$SHAILS_ENV" == "\$conf_env" ]; then
                          dbconf_${param}=\$1
                      fi
                  }"
        done
    }
    
    function load_dbconf() {
        local conf_env
        define_dbconf_functions
        . config/database.sh
    }
    
    load_dbconf

    まとめ

     YAML風の構造を無理やりシェルスクリプトとして解釈する方法と、evalで動的にコマンド(関数)を定義する方法を紹介しました。仕様を見て、そりゃそうだと思った人も多いかと思います。

     次回は、同様にしてdb/schema.shファイルをメタプログラミングでDSL(笑)する方法を解説する予定です。

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://emasaka.blog65.fc2.com/tb.php/351-3ace4cae

 | HOME | 

Categories

Recent Entries

Recent Comments

Recent Trackbacks

Appendix

emasaka

emasaka

フリーター。
連絡先はこのへん

Monthly


FC2Ad