|
|||||||
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: