|
This document describes how to use amrita with cgi programing using series of sample code.
The sample is a web bookmark system. You can see the demo at...
http://www.walrus-ruby.org/amrita/
At first, we make the model class.
!/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
The class Item is the bookmark items. It has three attributes: group, name, url.
The class + BookmarkList is collaction of Item. It contains Item s separated by groups and is able to save and load the list to/from a file.
The model class has nothing to do with HTML. So, it can be unit-tested easily.
bookmark.cgi displays bookmark list. And it accepts new bookmark entry.
bookmark.cgi uses this template.
<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>
This is the code of 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
class Item include Amrita::ExpandByMember def link e(:a, :href=>url) { url } # <a href="http://www.xxx.com/">http://www.xxx.com/</a> end end
Ruby's class is an open class: it can be edited by user without modifing the original code. The class Item is defined in other source file.
We make this class include Amrita::ExpandByMember and add a method named link so that it's method can be used directly by template.
<tr id=items> <td id="name"></td> <td id="link"></td> </tr>
We will provide Item objects for id items and because Item object is a Amrita::ExpandByMember object,id name and link will be used as method names.
url is a method related to MODEL so it should be defined in model class (bmmodel.rb). And link contains information about VIEW (HTML presentation) so it's better to put it to the view related source(bookmark.cgi).
If you add a new item, the next page displayed contains the selection of groups with default of the selected group.
The model data here....
: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 }, }
generates this 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>
And this HTML is inserted to the element with id 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 is a kind of Amrita::TemplateFile that can reuse compiled code stored in cache file.
If there is the cache data matches to TEMPLATE_PATH in CACHE_PATH and it is younger than template itself, amrita reuse the compiled code automatically.
CAUTION: be careful to prevent users to edit the cache file.
Currently, amrita does not check the cache file weather it was created by amrita nor unmodified . So if someone can edit it, he or she can insert any dangerous code into it to be executed by amrita.
It's YOUR resposibility to protect the cache files from crackers. Don't use TemplateFileWithCache::set_cache_dir if you don't understand this.
This is a viewer of bookmark written in amrita-script.
<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>
How to run in apache.
AddHandler amrita-script ams Action amrita-script /amrita/cgi-bin/amshandler
sample/cgi/bookmark.rb is a script can run under mod_ruby.
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>
LoadModule ruby_module /usr/lib/apache/mod_ruby.so Alias /amrita/cgi-bin/ /home/tnaka/cvswork/amrita/sample/cgi/ RubyRequire amrita/handlers SetEnv AmritaCacheDir /tmp/bookmark <Files *.ams> Options ExecCGI SetHandler ruby-object RubyHandler Amrita::AmsHandler.instance </Files>
code:
output: