File: Cgi_ja

Cgi_ja |
Path: | docs/Cgi_ja
Modified: | Thu Nov 07 16:33:20 JST 2002

# amritaをCGIで使う

## 概略

このドキュメントは、いくつかのサンプルを示してamritaをCGIで使う方法を説明します。 サンプルとしてブックマークのシステムを使います。

このサンプルは下記のURLでデモしています。

www.walrus-ruby.org/amrita/

## モデルクラス

最初にモデルクラスを作成します。

      !/usr/bin/ruby

      require 'amrita/template'

      class Item
        include Amrita::ExpandByMember
        attr_reader :group, :name, :url

        def initialize(group, name, url)
          @group, @name, @url = group, name, url
        end

        def to_s
          %Q[#{@group}|#{@name}|#{@url}]
        end

        def link
          e(:a, :href=>@url) { @url }
        end
      end

      class BookmarkList
        attr_reader :groups

        def initialize
          @groups = {}
        end

        def load_from_file(path)
          File::open(path) do |f|
            f.each do |line|
              begin
                add_new_item(*line.chomp.split('|'))
              rescue
              end
            end
          end
        end

        def save_to_file(path)
          File::open(path, "w") do |f|
            @groups.each do |k, v|
              v.each do |data|
                f.puts data.to_s
              end
            end
          end
        end

        def add_new_item(group="", name="", url="", *x)
          item = Item.new(group, name, url)
          @groups[group] ||= []
          @groups[group] << item
        end
      end

      if __FILE__ == $0
        require 'runit/testcase'
        require 'runit/cui/testrunner'

        class TestBMModel < RUNIT::TestCase
          def test_item
            item = Item.new("aa", "bb", "http://www.xxx.com/")
            assert_equal("aa", item.group)
            assert_equal("bb", item.name)
            assert_equal("http://www.xxx.com/", item.url)
          end

          def test_bookmarkmodel
            bm = BookmarkList.new
            assert_equal(0, bm.groups.size())
            assert_equal({}, bm.groups)

            bm.add_new_item("g", "nm", "http://www.xxx.com/")
            assert_equal(1, bm.groups.size())
            assert_equal(1, bm.groups["g"].size())
            assert_equal("nm", bm.groups["g"][0].name)
            assert_equal("http://www.xxx.com/", bm.groups["g"][0].url)
          end

          def test_load
            bm = BookmarkList.new
            bm.load_from_file("bookmark.dat.sample")
            assert_equal(3, bm.groups.size())

            assert_equal(3, bm.groups["BBS"].size())
            assert_equal("2ch", bm.groups["BBS"][0].name)
            assert_equal("http://www.ruby-lang.org/", bm.groups["Script Languages"][0].url)
          end

          def test_save
            tmp = "/tmp/bmtest#{$$}"
            bm = BookmarkList.new
            bm.load_from_file("bookmark.dat.sample")
            bm.add_new_item("html", "amrita", "http://kari.to/amrita/")
            assert_equal(4, bm.groups.size())

            assert_equal(3, bm.groups["BBS"].size())
            assert_equal("2ch", bm.groups["BBS"][0].name)
            assert_equal("http://www.ruby-lang.org/", bm.groups["Script Languages"][0].url)
            assert_equal(1, bm.groups["html"].size())
            assert_equal("amrita", bm.groups["html"][0].name)

            bm.save_to_file(tmp)

            bm = BookmarkList.new
            bm.load_from_file(tmp)
            assert_equal(4, bm.groups.size())
            assert_equal(3, bm.groups["BBS"].size())
            assert_equal("2ch", bm.groups["BBS"][0].name)
            assert_equal("http://www.ruby-lang.org/", bm.groups["Script Languages"][0].url)
            assert_equal(1, bm.groups["html"].size())
            assert_equal("amrita", bm.groups["html"][0].name)
          ensure
            File::unlink tmp
          end
        end

        if ARGV.size == 0
          RUNIT::CUI::TestRunner.run(TestBMModel.suite)
        else
          ARGV.each do |method|
            RUNIT::CUI::TestRunner.run(TestBMModel.new(method))
          end
        end
      end

`Item` というクラスはブックマークの項目に対応します。 このクラスには、group, name, url. という3つのアトリビュートが存在します。

`BookmarkList` は `Item` の集まりです。 グループごとにいくつかの `Item` を保持していて、 ファイルに保存したりロードしたりできます。

このモデルクラスはHTMLに関する処理を含んでいません。 そのため、添付のテストスクリプトのように簡単にユニットテストをすることができます。

## bookmark.cgi

bookmark.cgi はブックマークを表示して、新しいエントリーの入力を受けつけます。

### テンプレートファイル

bookmark.cgi はこのテンプレートを使用します。

      <html>
      <body>

      <h1>amrita bookmark sample</h1>
        <div id="groups">
          <h1 id="group_name"></h1>
          <table border="1">
            <tr><th>name</th><th>url</th></tr>
            <tr id=items>
              <td id="name"></td>
              <td id="link"></td>
            </tr>
          </table>
        </div>

        <hr>
        <form  id="form" method="post">
          <table>
            <tr>
              <th>group:</th>
              <td id=group_sel></td>
            </tr>
            <tr>
              <th>new_group:</th>
              <td><input name="group" type="text">
            </td>
            </tr>
            <tr>
              <th>title:</th>
              <td><input name="title" type="text">
            </td>
            </tr>
            <tr>
              <th>url:</th>
              <td><input name="url" type="text">
            </td>
            </tr>
              <tr><th>
              <td><input value="newitem" type="submit">
            </td>
            </tr>
          </table>
        </form>

      </body>
      </html>

### コード

これが bookmark.cgi のコードです。

      !/usr/bin/ruby

      require 'cgi'
      require 'amrita/template'
      require 'bmmodel'
      include Amrita

      DATAFILE_PATH="bookmark.dat"
      TEMPLATE_PATH="bookmark.html"
      CACHE_PATH="/tmp/bookmark"

      def make_model_data(bm, selected_group)
        groups = bm.groups.keys.sort

        data = {
          :groups => groups.collect do |k|
            {
              :group_name=>k,
              :items=>bm.groups[k]
            }
          end ,
          :form => {
            :group_sel=>e(:select, :name=>"group_sel") {
              groups.collect do |g|
                if g == selected_group
                  e(:option, :value=>g, :selected=>"selected") { g }
                else
                  e(:option, :value=>g) { g }
                end
              end
            },
          }
        }

        data
      end

      def generate_output(bm, group)
        Amrita::TemplateFileWithCache::set_cache_dir(CACHE_PATH)
        tmpl = Amrita::TemplateFileWithCache[TEMPLATE_PATH]
        tmpl.use_compiler = true
        tmpl.expand($stdout, make_model_data(bm,group))
      end

      def main
        bm = BookmarkList.new
        bm.load_from_file(DATAFILE_PATH)
        cgi = CGI.new
        url = cgi['url'][0]
        group = ""
        if url
          group = (cgi['group'][0]).to_s
          group = (cgi['group_sel'][0]).to_s if group == ""
          name = (cgi['title'][0]).to_s
          name = url if name == ""
          bm.add_new_item(group, name, url)
          bm.save_to_file(DATAFILE_PATH)
        end
        puts cgi.header
        generate_output(bm, group)
      end

      main

### フォームの要素を作成する

新しい項目を入力した後のグループの選択肢は、 最後に入力したグループ名がデフォルトに設定されています。 その処理を行なっているのはここです。

        :form => {
          :group_sel=>e(:select, :name=>"group_sel") {
            groups.collect do |g|
              if g == selected_group
                e(:option, :value=>g, :selected=>"selected") { g }
              else
                e(:option, :value=>g) { g }
              end
            end
          },
        }

このコードが以下のHTMLを生成します。

       <td>
         <select name="group_sel">
           <option value="BBS">BBS</option>
           <option value="Script Languages" selected="selected">Script Languages</option>
           <option value="TestXSS">TestXSS</option>
         </select>
       </td>

このHTMLがテンプレート内の `group_sel` に対応する所に挿入されます。

### コンパイラを使用する

      Amrita::TemplateFileWithCache::set_cache_dir(CACHE_PATH)
      tmpl = Amrita::TemplateFileWithCache[TEMPLATE_PATH]
      tmpl.use_compiler = true
      tmpl.expand($stdout, make_model_data(bm,group))

Amrita::TemplateFileWithCache は Amrita::TemplateFile に コンパイルされたコードをキャッシュして再利用する機能を追加したものです。

`CACHE_PATH` の中に`TEMPLATE_PATH` に対応するキャッシュデータが存在し、 テンプレートより新しければ、そのコンパイルされたコードを自動的に再利用します。

注意： このディレクトリは他のユーザが修正できないように確実に設定してください。

現状のバージョンでは、amritaはキャッシュの内容をチェックしません。 もし、ここを修正されるとamritaに任意のコードを実行させることが可能になって非常に危険です。

ここの記述が理解できない場合は、`TemplateFileWithCache::set_cache_dir`は使用しないでください。

## Amrita Script をCGIで使用する

これはAmritaScriptで書かれたブックマークの表示プログラムです。

      <html>
      <body>

      <amritascript> <!--
        require "bmmodel"
        include Amrita

        bm = BookmarkList.new
        bm.load_from_file("bookmark.dat")
        groups = bm.groups.keys.sort

        data = {
          :groups => groups.collect do |k|
            {
              :group_name=>k,
              :items=>bm.groups[k].collect do |item|
              {
                :name=>item.name,
                :link=>a(:href=>item.url) { item.url }
              }
            end
            }
          end
        }
      //--></amritascript>

        <div id="groups">
          <h1 id="group_name"></h1>
          <table border="1">
            <tr><th>name</th><th>url</th></tr>
            <tr id="items">
              <td id="name">name</td>
              <td><a id="link">url with link</a></td>
            </tr>
          </table>
        </div>

      </body>
      </html>

apache配下で実行するには

      * httpd.confを修正して<tt>AllowOverride FileInfo</tt> と <tt>Options ExecCGI</tt> を+cgi-bin+ ディレクトリに設定してください

      * bin/amshandlerをそのディレクトリにコピーしてください

      * .htaccess に以下の記述を追加します。

        AddHandler amrita-script ams
        Action amrita-script /amrita/cgi-bin/amshandler

## bookmark.cgi を mod_ruby で使用する

bookmark.cgi は mod_ruby配下で実行することもできます。 httpd.confに下記の指定を行なってください。

      LoadModule ruby_module /usr/lib/apache/mod_ruby.so
      RubyRequire apache/ruby-run

      Alias /amrita/cgi-bin/ /home/tnaka/cvswork/amrita/sample/cgi/

      <Location /amrita/cgi-bin>
        Options ExecCGI
        SetHandler ruby-object
        RubyHandler Apache::RubyRun.instance

        SetEnv AmritaCacheDir /tmp/bookmark # be careful
      </Location>