This post originated from an RSS feed registered with Ruby Buzz
by Michael Neumann.
Original Post: Wee: My WebFramework
Feed Title: Mike's Weblog
Feed URL: http://www.ntecs.de/blog-old/index.rss?cat=ruby&count=7
Feed Description: Blogging about Ruby and other interesting stuff.
After another complete rewrite of my initial prototype, and after trying
out several different design choices (and asking Avi
Bryant lots of questions), my web framework is now in a very good
shape. And it's got a name: Wee.
The core is now around 600 lines of code. The HTML related libraries put
another 500 lines of code.
The trickiest thing I implemented was the StateRegistry, where you can
register objects for being backtracked, and where you can take snapshots of
all registered objects. All this is done with extensive use of
weak-references and finalizers. The serialization part of the StateRegistry
was tricky too, as you can't simply serialize object-ids.
Features
Fully serializable
If you shut down the application, all state gets stored to disk. You can
then reload the whole application from disk (possibly on another machine)
with exactly the same state where you left off. Of course you could also
store the sessions in a database table. Should be simple to implement any
kind of load-balancing, as each session is completely independent, so that
you could simply transfer sessions to another machine.
Backtracking
If you want, you can make the back-button of your browser work correctly
together with your web-application. Imagine you have a simple counter
application, which shows the current count and two links inc and
dec with which you can increase or decrease the current count.
Starting with an inital count of 0, you increase the counter up to 8, then
click three times the back button of your browser (now displays 5) and
finally decrease by one, then the counter really shows the expected 4,
instead of 7 (as clicking the back button does usually not send a HTTP
request, and the last state of your application was 8).
Only individual objects are backtracked, those that are registered, not the
whole component. That's the easiest (from an application programmers
perspective) and most flexible way. And it's fast and uses little memory.
You can decide yourself whether you want infinite backtracking or only
backtracking up to n pages, with whatever replacement strategy you
want, least recently used (LRU), least frequently used (LFU) etc.
Acceptable Performance and Memory Usage
Memory consumption stayed constant in all of my tests (with Ruby 1.8.x, not
with 1.9!) by around 7 MB. On my Centrino 1300 MHz laptop, I can currently
render 86 pages per second and can handle 97 action requests per second.
That means it can handle 45 complete page views per second. The example
consist of 20 counter components, with backtracking each of them, WEBrick
as webserver (100% pure Ruby).
Reusable Components
Wee components are like widgets in a GUI. Once written, you can use them
everywhere. They are completely independent and do not interfere with other
components. Components encapsulate state, a view and actions. Of course you
can use an external model or use templates for rendering.
Programmatic HTML Generation
You don't have to leave Ruby to generate the HTML code. This is especially
useful for highly dynamic components. But of course, you could also use
templates.
Example
Below is the screenshot and sourcecode of a simple editable counter
component that is encapsulated 10 times in the main component.
require 'wee'
class Counter < Wee::Component
def initialize(cnt)
@cnt = cnt
@show_edit_field = false
session.register_object_for_backtracking(self)
end
def dec
@cnt -= 1
end
def inc
@cnt += 1
end
def render_content_on(r)
r.form.action(:submit).with do
r.anchor.action(:dec).with("--")
r.space
if @show_edit_field
r.text_input.assign(:cnt=).value(@cnt).size(6)
else
r.anchor.action(:submit).with(@cnt)
end
r.space
r.anchor.action(:inc).with("++")
end
end
def submit
@show_edit_field = !@show_edit_field
end
def cnt
@cnt
end
def cnt=(val)
@cnt = val.to_i if val =~ /^\d+$/
end
end
class Main < Wee::Component
def initialize
@counters = (1..10).map {|i| Counter.new(i)}
children.push(*@counters)
end
def render_content_on(r)
r.page.title("Counter Test").with do
@counters.each { |cnt| r.render(cnt) }
end
end
end