Introducing Wt::Ruby, a Qt-like api for developing web applications

Before going to last years Akademy I had planned to use the week to try and start helping out with Ruby support for KDevelop4. In the end I got sidetracked by two things; playing with the Nokia N810 and finding out about a web application development library called 'Wt'. Koen Deforche gave a talk about Wt and I was impressed the way he described how web development usually sucked, and why a widget based desktop style api was better than the usual web page with embedded code approach.

After the talk, Pau Garcia i Quiles introduced me to Koen and Wim Dumon who are the two main Wt developers. Pau has used QtRuby and Korundum a lot and so I know he likes Ruby, and yet he thinks the best web toolkit is Wt, which usually involves coding in C++. Hmm, WTF - there must be something interesting with this stuff I thought?

They helped my get Wt built using cmake and linking against the boost libs, and I had a go and running the Wt headers through the kalyptus bindings generator. When I found out that Wt uses boost instead of the Qt core classes I was a bit concerned as basically boost is a complete nightmare for producing language bindings against. It has load of compile time generated C++, and pushes the limits of 'C++'ness, which is a big turn off for me compared with the Qt style of api that I found translates very nicely to non-C++ languages.

I think I got the code generation for a 'Smoke' libary for Wt fixed by the end of Akademy. It turned out that although Wt uses boost, mainly for signals and slots, it fortunately doesn't have a very boost-like api itself. Instead, it is modelled on the Qt4 api with very similar classnames, just with 'W's instead of 'Q's in them. When someone asked Koen at his talk why Wt didn't use the QtCore classes instead of boost, he said it was for licensing reasons, rather than technical ones. Wt is dual licensed GPL/commercial and the commercial version sells for about 500 euros I believe. So the extra cost of a Qt C++ license for the customers would just be too much. Maybe the recent Qt license change to add LGPL will mean that we will see more 'niche products' like Wt built on top of Qt.

A week or so after I got back to Gran Canaria my big toe started hurting. It felt like I had bashed it on something, but I couldn't remember where I'd done that. It got more and more painful and after another week I couldn't even walk and had to go and see a doctor. About the second question the doctor asked was 'Do you drink a lot of beer?', like she was telepathic or something. Maybe it was my english accent, I don't know. The problem she diagnosed was that I had gout in my big toe possibly caused by beer drinking. So how an earth do I get some illness, which I thought had gone out of fashion in the 18th century? I thought that to have 'la gota' properly I should really be wearing a three cornered hat and a curly wig. So I ended up stuck in my flat with a seriously painful toe, a bunch of pills to take and no alcohol for a month. I didn't feel like listening to music or reading a book with my toe hurting like hell. Nothing to do but eat 'gout friendly' stuff, which seems to be just bread, cheese and water. What to do? The solution was go into a hacking frenzy of course, and get the Wt::Ruby hello world working after a week of doing absolutely nothing but either hacking or sleeping. Whenever, I woke up in the night in so much pain I couldn't sleep, I'd just get up and start coding.

After this initial burst of activity I've been working on Wt::Ruby on and off, gradually solving the problems of dealing with boost slots, translating the examples to Ruby and so on. But the trouble with language bindings is that they aren't much use until they are pretty much done, and so there isn't a lot of point in telling anyone much about them. Finally, I think the project is pretty much ready for a first release. The code is hosted on 'gouthub' :-). Here is what hello world in Wt::Ruby looks like:

require 'wt'

# A simple hello world application class which demonstrates how to react
# to events, read input, and give feed-back.
class HelloApplication < Wt::WApplication

  # The env argument contains information about the new session, and
  # the initial request. It must be passed to the WApplication
  # constructor so it is typically also an argument for your custom
  # application constructor.
  def initialize(env)
    setTitle("Hello world")                                # application title

    root.addWidget(Wt::WText.new("Your name, please ? "))  # show some text
    @nameEdit = Wt::WLineEdit.new(root) do |e|             # allow text input
      e.setFocus                                           # give focus

    button = Wt::WPushButton.new("Greet me.", root) do |b| # create a button
      b.setMargin(Wt::WLength.new(5), Wt::Left)            # add 5 pixels margin 

    root.addWidget(Wt::WBreak.new)                         # insert a line break
    @greeting = Wt::WText.new(root)                        # empty text

    # Connect signals with slots
    button.clicked.connect(SLOT(self, :greet))
    @nameEdit.enterPressed.connect(SLOT(self, :greet))

  def greet
    # Update the text, using text input into the @nameEdit field.
    @greeting.text = "Hello there, " + @nameEdit.text

 Your main method may set up some shared resources, but should then
 start the server application (FastCGI or httpd) that starts listening
 for requests, and handles all of the application life cycles.

 The block passed to WRun specifies the code that will instantiate
 new application objects. That block is executed when a new user surfs
 to the Wt application, and after the library has negotiated browser
 support. The block should return a newly instantiated application
Wt::WRun(ARGV) do |env|
  # You could read information from the environment to decide whether
  # the user has permission to start a new application

You can run this application from the command line when developing it, and then you can install exactly the same source in an Apache2/Fastcgi based site for the released version.

$ ./hello.rb -- --docroot `pwd` --http-address localhost --http-port 4000

If you are familiar with Qt and especially QtRuby, it should be pretty much self explanatory. A slightly different way to create a new application, a slightly different way to connect signals and slots, but all very comfortable and familiar really. And yet this is writing a web application. In Rails something as simple wouldn't take long, but after you've generated your project you will have an auto-generated mountain of different files. This Wt::Ruby app is just a single very simple source. Another problem with Rails is that it is only really good for a certain sort of web page based app, and once you try and do something heavy with JavaScript widgets like Google's gmail it doesn't really work. With Rails you can add JavaScript/AJAX-y stuff to your basic .html.erb page like seasoning, but you can't build desktop-like JavaScript apps with it.

Now the basics are there, I think there are a whole lot of things we can do fairly easily to combine Wt::Ruby with Rails stuff and create a pretty nice RAD web development environment. For instance, I've just tried out creating a Wt::WStandardItemModel model from ActiveRecord and showing it in a Wt::Ext::TableView and that works beautifully.

If you are going to FOSDEM this year, I'm hoping to give a short 25 minute talk in the Ruby track on Sunday, but it hasn't been confirmed yet. In the meantime, if you want to checkout the code and try it out that would be great. I would really like to hear from someone other than myself who has got it all working OK before the first actual release.


Thanks for these wonderful bindings! Fast development with Ruby + wonderful Wt API = web development 3.0
My main concern with Rails is you need to write and hard-code lots of Javascript and CSS. When Rails started, their main point was you would not need to do so but only a couple of years later developing with Rails was not too different from the old PHP3 days :-( Wt not only clones the Qt API we love but also performs extremely well, much better than Rails, for instance.

By Pau Garcia i Quiles at Fri, 01/16/2009 - 16:43

Thanks, please try it out - I'm really keen to get some feedback. If you want to help with the coding I can add you to the project on github. Once, I know it builds and works for other people, I'll create a project on RubyForge to use for downloads, and a help forum.

By Richard Dale at Fri, 01/16/2009 - 17:14

I also got convinced by Pau to try Wt, very interesting toolkit... and your work pushes it to the next level! I'm not a ruby programmer myself, but this may be a good opportunity to give it a try. How complete/usable are the bindings?

Thanks for your work

By alfredo beaumont at Fri, 01/16/2009 - 17:29

How complete/usable are the bindings?

The coverage of the Wt:: and Wt::Ext apis is pretty much 100%, and they are perfectly usable with FastGCI. They don't work with the multi-threaded version of libwthttp because of the limitations of Ruby threads, and so you can just use the single threaded version via the command line for testing, and FastCGI for production.

Some more documentation is needed, but you can have a look at the examples for a guide as nearly all of the C++ ones have been translated to Ruby. If you know the C++ api and you can learn the basics of Ruby programming, you're there.

By Richard Dale at Fri, 01/16/2009 - 17:35

The coverage of the Wt:: and Wt::Ext apis is pretty much 100%, and they are perfectly usable with FastGCI. They don't work with the multi-threaded version of libwthttp because of the limitations of Ruby threads, and so you can just use the single threaded version via the command line for testing, and FastCGI for production.

Great to hear such coverage. I built a single-threaded Wt and wtruby (without problems). I tried some examples from CLI and they start eating memory till i kill them. They don't get to the point of listening to the port.

I get this after killing with Ctrl-C:

[email protected]:~/software/wtruby/ruby/wtruby/examples/hello$ ./hello.rb --docroot . --http-address --http-port 8000
C-c/usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:2068:in `create_wt_class': Interrupt
	from /usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:2068:in `init_class'
	from /usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:2300:in `init_all_classes'
	from /usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:2298:in `each'
	from /usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:2298:in `init_all_classes'

Any hints? I haven't tried fastcgi yet.

By alfredo beaumont at Fri, 01/16/2009 - 19:32

I get the same output. And ruby eats 50% of my system memory in 5 seconds.

By dpalacio at Fri, 01/16/2009 - 21:59

I got the same output as well.

After some bug hunting, the following patch solves it:

diff --git a/ruby/wtruby/src/wtruby.cpp b/ruby/wtruby/src/wtruby.cpp
index af837c5..dab28d4 100644
--- a/ruby/wtruby/src/wtruby.cpp
+++ b/ruby/wtruby/src/wtruby.cpp
@@ -898,8 +898,8 @@ create_wt_class(VALUE /*self*/, VALUE package_value, VALUE m
VALUE klass = module_value;
std::string packageName(package);

- unsigned int p1 = packageName.find("::", strlen(moduleName));
- unsigned int p2 = 0;
+ std::size_t p1 = packageName.find("::", strlen(moduleName));
+ std::size_t p2 = 0;
while (p1 != std::string::npos) {
p1 += strlen("::");
p2 = packageName.find("::", p1);

By kdeforche at Sat, 01/17/2009 - 10:58

It launches http server now, but it still fails to serve any request, whenever I try to load a page I get a segfault:

[email protected]:~/software/wtruby/ruby/wtruby/examples/extkitchen]$ ruby extkitchenapplication.rb --http-address --http-port 8000 --docroot .
[2009-Jan-17 13:50:24.877212] 19162 - [notice] "Wt: initializing built-in httpd"
[2009-Jan-17 13:50:24.877349] 19162 - [notice] "Reading Wt config file: /etc/wt/wt_config.xml (location = 'extkitchenapplication.rb')"
[2009-Jan-17 13:50:24.877738] 19162 - [notice] "Starting server:"
[2009-Jan-17 13:50:24.877905] 19162 - [warn] "No boost thread support, running in main thread."
[2009-Jan-17 13:50:27.408973] 19162 [/ 5hbmTvYVXVbEkfZ5] [notice] "Session created (#sessions = 1)" - - [2009-Jan-17 13:50:27.410034] "GET / HTTP/1.1" 200 1626
/usr/lib/ruby/site_ruby/1.8/wt/wtruby.rb:327: [BUG] Segmentation fault
ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux]


By alfredo beaumont at Sat, 01/17/2009 - 12:58

That crash is in the Wt::WApplication constructor. Do you think you could try running it under gdb to see exactly where the seg fault is happening?

By Richard Dale at Sun, 01/18/2009 - 17:28

Ooops, it's indeed a problem in single-threaded Wt I built, not WtRuby related it seems. Sorry for the noise. Let's see if I can fix Wt to report whether WtRuby works fine.

By alfredo beaumont at Mon, 01/19/2009 - 10:49