July 20th, 2008
The other day I was discussing Rails’ form processing behavior with Ben, when the topic of editing multiple associations in a single form came up. Effectively, he needed to be able to manipulate a collection of records associated with a parent via a has_many association. For each item in this collection, he wanted something like two drop down lists — nothing unreasonable. He said it was similar to recipe 13 from Advanced Rails Recipes — “Handle Multiple Models in One Form” — except that he had to use two fields instead of the one textfield, and it was proving to be surprisingly tough.
I know what you’re thinking: surely this can be extended to more than one form field per item without any problem? Up until I read the code myself a few months ago, I wouldn’t have thought it would be so difficult either — but it’s true. The parameter parsing code is quite quirky and, without reading the detail of the source, borderline unpredictable. I’ve had a few ideas for dealing with more complicated forms in Rails ever since I first ran into this issue. Only recently have I actually gotten around to doing something about it. Here’s an abridged version of an email I just sent to the rails-core mailing list:
Hi,
…
A colleague of mine recently ran into a related problem when dealing with collections of records in Rails forms, which prompted me to finally do something about the parameter parsing behavior. Now, while I’m sure we’re not the only ones hitting this wall, I’m aware that proposals to change to the parameter parsing semantics in Rails is likely to be met with a little caution, hesitation — possibly even terror.
With that in mind, I’ve put up a plugin so everybody can give this a go without having to apply any nasty patches:
http://www.deskchecked.com/git/rails/plugins/form_collections.git
Please see the README (attached) for the full details.
…
Cheers,
Tom
This is a plugin which greatly simplifies the existing Rails parameter parsing code, and makes it a whole lot easier to deal with collections in your forms. I encourage all you Rails developers out there to take a look when you get the opportunity, I’d love to hear your opinions on this one. You can get a copy of the git repository using the following command:
$ git clone \
http://www.deskchecked.com/git/rails/plugins/form_collections.git
UPDATE: Here’s the contents of the README file:
UPDATE 2: Removed the README: looks terrible in the new design due to space restrictions.
Categories: Ruby, Software Development |
11 Comments
July 4th, 2008
In many open source software projects, full backwards compatibility is ensured between minor point releases (for example, 1.2.3 to 1.2.4). Generally speaking, these releases are made mostly to get important defect and/or security fixes to the public in a relatively timely manner. PHP is a notable exception to the rule: I haven’t been following development all that closely of late, but in the past it was not uncommon to break backwards compatibility between point releases.
Another major exception to the rule that hits a little closer to home is Ruby. In the past, backwards-incompatible changes have crept into point release changes — I’m uncertain as to whether or not this was intentional. However, the Ruby project also provides what’s known as a “patchlevel” release for each given point release. The patchlevel counts the number of patches applied to any given point release. Generally speaking, these seem to be bug and security fixes.
If you’re interested, the patchlevel release of your Ruby installation can be seen like so:
[ tom ] ~
$ ruby --version
ruby 1.8.x (YYYY-MM-DD patchlevel XXX) [i486-linux]
Anyway, over the past few days I’ve been watching the discussion leading up to patchlevel releases of Ruby 1.8.6 and 1.8.7. It’s been an interesting experience. You can follow the discussion from message #17499 here.
First there is an announcement that new versions of Ruby will drop in three days’ time. Shortly after this, the announcement that both versions are failing numerous rubyspecs (57 failing for 1.8.7!). Then a report that memory leaks may be present in both versions, followed by a report that one of the tests hang in Win32. Eventually a request to delay the release came through, along with a recommendation by Charles Nutter (the guy who brought us JRuby) to implement continuous integration.
I’m really surprised at a few things:
- The original decision to make a release was based upon “I think current 1.8.6/1.8.7 is [more] stable than p230/p22″. Please, please tell me that the decision that the trunk is more stable than a previous release wasn’t based on a gut feeling. Please.
- No continuous integration. It’s very important for software projects to maintain and continuously run a set of regression test. Otherwise, sooner or later they will break their users’ code.
Ruby is a great language and I applaud the work of the developers — maintaining a beast like the Ruby core for naught but love must be hard work. However, I have to say that I think their fumbling to get a stable release out the door on short notice — especially in the face of their recent security problems — is a concern for anybody relying on the Ruby code base. All that said, it sounds like the project is already taking steps in the right direction based on an earlier email to the list from Matz.
One day in the near future I’m sure that cutting a Ruby release won’t be quite so painful, but the lesson here is clear: Exercise your tests frequently — and ideally, automatically — or getting a stable release out the door might be nigh on impossible.
Categories: Compilers, Ruby, Software Development |
7 Comments
March 12th, 2008
I’ve moved over to Shine’s Ruby on Rails team and, as such, have been exposed to a whole lot of Rails code over the past few weeks. Something I ran into a few weeks back was this bug, relating to a parsing bug in nested forms. I’ve submitted a series of patches, the last of which I hope to see in HEAD sometime over the next few days.
Anyway, the actual bug was caused by weirdo (and non-Hash-like) semantics in HashWithIndifferentAccess that causes certain types (Hash and Array) to be stored as a copy of the value passed in. The following code will give you an idea of the crazy logic that might result from such behavior:
def mktom(); {:name => 'Tom', :age => 23}; end
tom = mktom
people = {:tom => tom}
tom[:name] = 'Thomas'
puts people[:tom][:name] # displays 'Thomas' - cool!
puts "The same object" if tom.object_id == people[:tom].object_id # displays 'The same object'
tom = mktom
people = {:tom => tom}.with_indifferent_access
tom[:name] = 'Thomas'
puts people[:tom][:name] # displays 'Tom' - wtf!
puts "Not the same object!" if tom.object_id != people[:tom].object_id # displays 'Not the same object!'
The lesson learned from tracking down and fixing this bug?
- Do not treat HashWithIndifferentAccess as though it were any old Hash. It has different semantics to Hash and may store copies of the keys and/or values instead of the original objects.
- Do not nest HashWithIndifferentAccess instances in Hash instances unless you enjoy headaches. It’s so easy to think that HashWithIndifferentAccess is semantically identical to Hash (especially thanks to Hash#with_indifferent_access), but this assumption was the cause of two nasty, hard-to-find bugs in this instance.
- When providing an API virtually identical to (and easily mistakable for) core Ruby classes, ensure that semantics are consistent with convention.
I’m not sure why they didn’t just override the reader for #[]. Maybe there’s more to that than I realize. Anyway, I’ve spent enough time thinking about this for tonight, time for bed. Sorry for the blog drought, I’m hoping to finalize a few more installments of the Ocaml series in along with the final part of my Scala/BCEL parser tutorial over the next few months. Stay tuned.
UPDATE Since I wrote this post, I’ve written a plugin that replaces Rails’ parameter parsing implementation to make dealing with complex nested forms easier still. Refer to Taking the Pain Out of Complex Forms in Rails.
Categories: Ruby, Software Development |
No Comments
January 23rd, 2008
I’m still somewhat in holiday mode, so this entry is probably going to feel a little cheap for those of you following my more technical posts. I’m a big fan of GTK+ for user interfaces. If you don’t have the option or desire to use the Java platform and Swing, GTK+ is one of the better cross-platform user interface toolkits out there.
It’s high-level enough that it is easy to build quick, effective GUIs but low-level enough not to get in your way when you need to start messing around at the pixel level. GUIs can be built by hand using code, or designed using Glade and exported to an XML document to be loaded at runtime by any GTK+ binding. Currently it runs on Windows and Linux (the toolkit actually has its roots in GNOME) and has bindings for most popular programming languages. The only major downer is that Mac users are left out in the cold unless they go to the (herculean) effort of getting X11 up and running.
All the little differences between the various GTK+ bindings out there tend to get my goat when I move from one language to another. You would think that a GTK+ example in C could easily be translated to other languages without referencing documentation right? For one reason or another, the GTK+ bindings for other languages tend to diverge from the pleasant consistency of the C API to varying degrees. This post is all about those little differences that crop up even in the simplest applications: GTK’s take on “Hello World” in a few different languages.
C
C, being the language GTK+ is actually written in, is probably the most consistent with function naming across the different objects … the GTK_* and G_* macros are quite ugly though.
#include <gtk/gtk.h>
int main (int argc, char **argv) {
GtkWidget *window;
gtk_init(&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Hello, World");
g_signal_connect (G_OBJECT (window), "delete-event", gtk_main_quit, NULL);
gtk_widget_show_all (window);
gtk_main();
return 0;
}
C#/Mono
GTK# adds stuff like the Application object and delegates to produce a “Hello World” example I had to go digging around in documentation for. Not too bad on the whole, though.
using Gtk;
using GtkSharp;
public class HelloWorld {
public static void Main(string[] args) {
Gtk.Window window = new Gtk.Window();
window.Title = "Hello, World";
window.DeleteEvent += delegate { Application.Quit(); };
window.ShowAll();
Application.Run();
}
}
Ocaml
The Ocaml bindings diverge from the original C API much more so than the other languages listed here, most likely due to design decisions that had to be made to provide a C binding to a functional language. My only real grumble is the inconsistency with window#event#connect vs window#connect. I’m guessing there was a technical reason for that, but it still irks me every time I see it.
let delete_event evt = false
let destroy () = GMain.Main.quit ()
let main () =
let window = GWindow.window in
let _ = window#set_title "Hello, World" in
let _ = window#event#connect#delete ~callback:delete_event in
let _ = window#connect#destroy ~callback:destroy in
let _ = window#show () in
GMain.Main.main ()
;;
let _ = main () ;;
Perl
Although I find writing Perl to be painful for everything but processing text files in a terminal, I found the Perl GTK bindings to be relatively straightforward.
use strict;
use Gtk2 '-init';
my $window = Gtk2::Window->new;
$window->set_title("Hello, World");
$window->signal_connect('delete-event', sub { Gtk2->main_quit; });
$window->show_all;
Gtk2->main;
Python
I’m more familiar with PyGTK than with other bindings, so this was a snap. Why they chose GtkObject.connect over GtkObject.signal_connect is a mystery and the pygtk.require(’…’) crap is a little weird, but aside from that there should be nothing surprising here (this is a good thing!).
import pygtk
pygtk.require('2.0')
import gtk
window = gtk.Window()
window.set_title('Hello, World')
window.connect('delete-event', gtk.main_quit)
window.show_all()
gtk.main()
Ruby
RubyGNOME provides a GTK+ binding for Ruby. Take an almost 1:1 port of the C API, take away the ugly casting macros, mix in closures for handling signals and Ruby really is one of the nicest ways to get intimate with GTK+.
require 'gtk2'
window = Gtk::Window.new
window.title = 'Hello, World'
window.signal_connect(:delete-event) { Gtk.main_quit }
window.show_all
Gtk.main
That’s all for now. There are many more language bindings for GTK+ out in the wild for languages like Lisp/Scheme, C++, Haskell and Erlang. If you’re looking around for a GUI toolkit, be sure to give GTK a go. There’s plenty of documentation available for all the bindings listed here, often with some very detailed and easy to follow tutorials.
UPDATE: Miguel and she suggested some changes to the C#/Mono and RubyGNOME examples.
UPDATE 2: Aristotle suggested an easier way to initialize the Perl GTK bindings.
Categories: C, GTK, Ocaml, Python, Ruby, Software Development |
11 Comments
June 27th, 2007
GtkClipboard is a wonderful (if somewhat recent) addition to GTK. As you would expect, it allows users to pass data between your application and other GTK applications via the X11 clipboard (and vice versa). A common past annoyance with the clipboard under linux is that data stored disappeared from the clipboard when the application which set the data terminated. No longer so, thanks to gtk_clipboard_store and the GNOME Clipboard Daemon.
Now, tutorials on GtkClipboard are a little sparse and the documentation seems to leave a few questions unanswered, so here’s a quick and dirty primer on passing text data between your GTK applications using GtkClipboard.
Python
import pygtk
pygtk.require('2.0')
import gtk
# get the clipboard
clipboard = gtk.clipboard_get()
# set the clipboard text data
clipboard.set_text('Hello!')
# make our data available to other applications
clipboard.store()
Ruby
require 'gtk2'
# initialize Ruby's GTK bindings
Gtk.init
# get the clipboard
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
# set the text. Ruby-Gnome2 also provides a text= setter
clipboard.set_text('Hello, World')
# make the clipboard data available to external applications
clipboard.store
C
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <string.h>
int main (int argc, char **argv) {
const char *message = "Hello, World";
/* initialize GTK */
gtk_init (&argc, &argv);
/* set the clipboard text */
gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), message, strlen(message));
/* store the clipboard text */
gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
return 0;
}
You can also use gtk_clipboard_set_image (for Ruby and Python, there are equivalents) to pass GdkPixbuf data to the clipboard. Check the GTK/PyGTK/Ruby-Gnome2 documentation for more details.
It’s actually all quite easy, but I thought it might be nice to see the code in practice.
Categories: C, GTK, Linux, Python, Ruby, Software Development |
3 Comments
August 2nd, 2006
NOTE: This is an old post which I am merely republishing. The earlier version was lost due to a RAID failure on my former web host’s server.
I had a bit of trouble tracking down decent documentation for Ruby/SDL and Ruby/OpenGL, so I thought I’d put together some sample code in case anybody is interested in it.
require 'sdl'
require 'opengl'
# initialize SDL and OpenGL
SDL.init(SDL::INIT_VIDEO | SDL::INIT_AUDIO | SDL::INIT_TIMER)
screen = SDL.set_video_mode(800, 600, 16, SDL::HWSURFACE | SDL::OPENGL)
GL.ClearColor(0.0, 0.0, 1.0)
GL.ClearDepth(1.0)
finished = false
while not finished
# pull events from the input queue
while event = SDL::Event2.poll
# watch for quit events
if event.kind_of?(SDL::Event2::Quit)
finished = true
break
end
end
# clear the screen
GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT)
# TODO: rendering code goes here!
# display the back buffer
SDL.GL_swap_buffers
end
# terminate SDL
SDL.quit
Obviously, the convention is underscored lower case method names for Ruby/SDL (although camelCase aliases are provided too), while Ruby/OpenGL’s API uses UpperCamelCase and breaks Ruby convention by using a capital letter for the first character.
Update: Here’s documentation for Ruby/SDL .
Categories: Graphics Programming, Ruby, Software Development |
No Comments