What follows is another basic solution using ActiveRecord only and CGI.
There is minimal CSS styling, but you could add some very easily.

What is interesting about this solution is that it's very small (a
single file and a single table), does not need users and sets itself
up. The solution uses sqlite3 (via gems) and checks if the database
file exists as part of the startup. If it doesn't exist, it creates the
file and the table that it needs.

Listing, searching, viewing details, creating new posts and closing
posts are supported. Each new post generates a 'secret' that the person
posting can then use to close the post with latter, such that users are
not required. Posts can also be 'administratively' closed.

Additionally, although this solution is a single file, all the
interfaces are templated using ERB. Each template is a separate entry
after the __END__ marker, with the first non-whitespace line being the
name and all lines after until the separater line as the file contents.
DRY principles are also in place as the header/footer are seperate
templates and included into each page rather then being repeated.

----------------------
Copy the following and paste it into a .cgi file. It has been tested
with lighttpd and apache.
----------------------

#!/usr/bin/env ruby

## Proposed solution to http://www.rubyquiz.org/quiz047.html
## Written by Paul Vaillant (paul.vaillant / gmail.com)
## Permission granted to do whatever you'd like with this code

require 'digest/md5'
require 'cgi'
require 'erb'

## gems are required for sqlite3 and active_record
require 'rubygems'
require 'sqlite3'
require 'active_record'

## Check if the database exists; create it and the table we need if it
doesn't
DB_FILE = "/tmp/jobs.db"
unless File.readable?(DB_FILE)
	table_def = <<-EOD
CREATE TABLE postings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        posted INTEGER,
	title VARCHAR(255),
        company VARCHAR(255),
	location VARCHAR(255),
        length VARCHAR(255),
        contact VARCHAR(255),
        travel INTEGER(2), -- 0%, 0-25%, 25-50%, 50-75%, 75-100%
        onsite INTEGER(1),
        description TEXT,
        requirements TEXT,
        terms INTEGER(2), -- C(hourly), C(project), E(hourly), E(pt),
E(ft)
        hours VARCHAR(255),
        secret VARCHAR(255) UNIQUE,
        closed INTEGER(1) DEFAULT 0
);
EOD
	db = SQLite3::Database.new(DB_FILE)
	db.execute(table_def)
	db.close
end

## Setup ActiveRecord database connection and the one ORM class we need
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile
=> DB_FILE)
class Posting < ActiveRecord::Base
	TRAVEL = ['0%','0-25%','25-50%','50-75%','75-100%']
	TERMS = ['Contract(hourly)','Contract(project)','Employee(hourly)',
		'Employee(part-time)','Employee(full-time)']
end

class Actions
	ADMIN_SECRET = 's3cr3t'
	@@templates = nil
	def self.template(t)
		unless @@templates
			@@templates = Hash.new
			name = nil
			data = ''
			DATA.each_line {|l|
				if name.nil?
					name = l.strip
				elsif l.strip == '-=-=-=-=-'
					@@templates[name] = data if name
					name = nil
					data = ''
				else
					data << l.strip << "\n"
				end unless l =~ /^\s*$/
			}
			@@templates[name] = data if name
		end
		return @@templates[t]
	end

	def self.dispatch()
		cgi = CGI.new
		begin
			## map path_info to the method that handles it (ie controller)
			## ex. no path_info (/jobs.cgi) goes to 'index'
			##	/search (/jobs.cgi/search) goes to 'search'
			##	/create/save (/jobs.cgi/create/save) goes to 'create__save'
			action = if cgi.path_info
					a = cgi.path_info[1,cgi.path_info.length-1].gsub(/\//,'__')
					(a && a != '' ? a : 'index')
				else
					"index"
				end
			a = Actions.new(cgi)
			m = a.method(action.to_sym)
			if m && m.arity == 0
				resbody = m.call()
			else
				raise "Failed to locate valid handler for [#{action}]"
			end
		rescue Exception => e
			puts cgi.header('text/plain')
			puts "EXCEPTION: #{e.message}"
			puts e.backtrace.join("\n")
		else
			puts cgi.header()
			puts resbody
		end
	end

	attr_reader :cgi
	def initialize(cgi)
		@cgi = cgi
	end

	def index
		@postings = Posting.find(:all, :conditions => ['closed = 0'], :order
=> 'posted desc', :limit => 10)
		render('index')
	end

	def search
		q = '%' << (cgi['q'] || '') << '%'
		conds = ['closed = 0 AND (description like ? OR requirements like ?
OR title like ?)', q, q, q]
		@postings = Posting.find(:all, :conditions => conds, :order =>
'posted desc')
		render('index')
	end

	def view
		id = cgi['id'].to_i
		@post = Posting.find(id)
		render('view')
	end

	def create
		if cgi['save'] && cgi['save'] != ''
			post = Posting.new
			post.posted = Time.now().to_i
			['title','company','location','length','contact',
			  'description','requirements','hours'].each {|f|
				post[f] = cgi[f]
			}
			['travel','onsite','terms'].each {|f|
				post[f] = cgi[f].to_i
			}
			post.secret =
Digest::MD5.hexdigest([rand(),Time.now.to_i,$$].join("|"))
			post.closed = 0
			if post.save
				@post = post
			end
		end
		render('create')
	end

	def close
		## match secret OR id+ADMIN_SECRET

		secret = cgi['secret']
		if secret =~ /^(\d+)\+(.+)$/
			id,admin_secret = secret.split(/\+/)
			post = Posting.find(id.to_i) if admin_secret == ADMIN_SECRET
		else
			post = Posting.find(:first, :conditions => ['secret = ?', secret])
		end

		if post
			post.closed = 1
			post.save
			@post = post
		else
			@error = "Failed to match given secret to your post"
		end

		render('close')
	end

	## helper methods
	def link_to(name, url_frag)
		return "<a href=\"#{ENV['SCRIPT_NAME']}/#{url_frag}\">#{name}</a>"
	end

	def form_tag(url_frag, meth="POST")
		return "<form method=\"#{meth}\"
action=\"#{ENV['SCRIPT_NAME']}/#{url_frag}\">"
	end

	def select(name, options, selected=nil)
		sel = "<select name=\"#{name}\">"
		options.each_with_index {|o,i|
			sel << "<option value=\"#{i}\" #{(i == selected ? "selected=\"1\"" :
'')}>#{o}</option>"
		}
		sel << "</select>"
		return sel
	end

	def radio_yn(name,val=1)
		val ||= 1
		radio = "Yes <input type=\"radio\" name=\"#{name}\" value=\"1\"
#{(val == 1 ? "checked=\"checked\"": '')}/> / "
		radio << "No <input type=\"radio\" name=\"#{name}\" value=\"0\"
#{(val == 0 ? "checked=\"checked\"" : '')} />"
		return radio
	end

	def textfield(name,val)
		return "<input type=\"text\" name=\"#{name}\" value=\"#{val}\" />"
	end

	def textarea(name,val)
		return "<textarea name=\"#{name}\" rows=\"7\" cols=\"60\">" <<
CGI.escapeHTML(val || '') << "</textarea>"
	end

	def render(name)
		return ERB.new(Actions.template(name),nil,'%<>').result(binding)
	end
end

Actions.dispatch

__END__
index
<%= render('header') %>

<h1>Postings</h1>
<% if @postings.empty? %>
<p>Sorry, no job postings at this time.</p>
<% else %>
<% for post in @postings %>
	<p><%= link_to post.title, "view?id=#{post.id}" %>, <%= post.company
%><br />
	<%= post.location %> (<%= Time.at(0).strftime('%Y-%m-%d') %>)</p>
<% end %>
</table>
<% end %>

<%= render('footer') %>

-=-=-=-=-
create
<%= render('header') %>

<h1>Create new Post</h1>
<% if @post %>
<p>Your post has been successfully added. Please note the following
information, as you will need it
to close you post once it has been filled; <br /><br />
Close code: <%= @post.secret %></p>
<p>Thank you</p>
<% else %>
<% if @error %><p class="error">ERROR: <%= @error %></p><% end %>
<%= form_tag "create" %>
<label for="title">Title</label> <%= textfield "title", cgi['title']
%><br />
<label for="company">Company</label> <%= textfield "company",
cgi['company'] %><br />
<label for="location">Location</label> <%= textfield "location",
cgi['location'] %><br />
<label for="length">Length</label> <%= textfield "length",
cgi['length'] %><br />
<label for="contact">Contact</label> <%= textfield "contact",
cgi['contact'] %><br />
<label for="travel">Travel</label> <%= select 'travel',
Posting::TRAVEL, cgi['travel'] %><br />
<label for="onsite">Onsite</label> <%= radio_yn "onsite", cgi['onsite']
%><br />
<label for="description">Description</label> <%= textarea
"description", cgi['description'] %><br />
<label for="requirements">Requirements</label> <%= textarea
"requirements", cgi['requirements'] %><br />
<label for="terms">Employment Terms</label> <%= select 'terms',
Posting::TERMS, cgi['terms'] %><br />
<label for="hours">Hours</label> <%= textfield "hours", cgi['hours']
%><br />
<input type="submit" name="save" value="create" />
</form>
<% end %>

<%= render('footer') %>

-=-=-=-=-
view
<%= render('header') %>

<% if @post %>
<h1><%= @post.title %></h1>
<table>
<tr><td>Posted</td><td><%=
Time.at(@post.posted.to_i).strftime('%Y-%m-%d') %></td></tr>
<tr><td>Company</td><td><%= @post.company %></td></tr>
<tr><td>Length of employment</td><td><%= @post.length %></td></tr>
<tr><td>Contact info</td><td><%= @post.contact %></td></tr>
<tr><td>Travel</td><td><%= Posting::TRAVEL[@post.travel] %></td></tr>
<tr><td>Onsite</td><td><%= ['No','Yes'][@post.onsite] %></td></tr>
<tr><td>Description</td><td><%=
CGI.escapeHTML(@post.description).gsub(/\n/,"<br />\n") %></td></tr>
<tr><td>Requirements</td><td><%=
CGI.escapeHTML(@post.requirements).gsub(/\n/,"<br />\n") %></td></tr>
<tr><td>Employment terms</td><td><%= Posting::TERMS[@post.terms]
%></td></tr>
<tr><td>Hours</td><td><%= @post.hours %></td></tr>
</table>
<% else %>
<p>ERROR: failed to load given post.</p>
<% end %>

<%= render('footer') %>

-=-=-=-=-
close
<%= render('header') %>

<h1>Close Post</h1>
<% if @post %>
<p>Successfully closed post '<%= @post.title %>' by <%= @post.company
%>.</p>
<% elsif @error %>
<p>ERROR: <%= @error %></p>
<% else %>
<p>ERROR: post not successfully closed, no further description of
error.</p>
<% end %>

<%= render('footer') %>

-=-=-=-=-
header
<html>
<head>
	<title>Simple Job Site</title>
	<style>
	form { display: inline; }
	</style>
</head>
<body>
<%= link_to "Home", "index" %> |
<%= link_to "Create new Post", "create" %> |
<%= form_tag "close" %>
	<input name="secret" type="text" size="16" />
	<input type="submit" value="close" />
</form> |
<%= form_tag "search" %>
	<input name="q" type="text" size="15" /> <input type="submit"
value="search" />
</form><br />

-=-=-=-=-
footer
</body>
</html>