Saturday, October 18, 2008

"Fun" with Hook Methods in Ruby

I've been wrestling with what turned out to be a rather interesting issue involving ruby hook methods. I don't yet have a solution to the problem but perhaps the activity of describing the problem in writing will illuminate a solution. Hook methods in ruby are callback methods you can implement in your code to get notified at interesting points in the lifecycle of a ruby object. The one which caused trouble in my case is inherited. By implementing the inherited method in a class you can get called back when another class inherits from it. It's quite useful when you want to do something like record each descendant of a given class, for example.

I ran into an issue with inherited while trying to understand a very strange behaviour in my IDE of choice, NetBeans. When running my rails test cases from NetBeans, I noticed they were not transactional, but when run on the command line they were. After sending a flame-o-gram which I will soon need to retract to the nb ruby mailing list, I decided to poke around in their code. I finally narrowed the problem down to their testrunner code.

In the newest version of NetBeans they have added a nicer ruby testrunner. In order to provide some features this new testrunner keeps track of all descendants of Test::Unit::TestCase. It does this, by, you guessed it, using the inherited method. When I delved into this code the first problem I saw was pretty obvious. The author had violated what I will now credit my friend Jim by referring to as Weirich's Hook Method Implementation Commandment:

Thou Shalt Always Delegate to the Previous Implementation

The reason this is important is that when you implement a hook method, you can easily step on someone else who had implemented it for another reason. Using the rails alias_method_chain will normally take care of this for you, but because this code loads before rails they didn't have that option and had not done the right thing and delegated explicitly.

And sure enough, I noticed that when I commented out the inherited method in the testrunner my tests became transactional. I figured at this point I had it licked. I went ahead and changed the NB testrunner code to delegate correctly and tried again. Sadly, it had no affect on the transactional issue. Much head scratching ensued. After many hours of investigation, I finally tracked down the real problem. I noticed that the use_transactional_fixtures class attribute of Test::Unit::TestCase was not being set. This led me to investigate how class_inheritable_attributes works in rails. Eventually I was able to put together a failing test case which expresses the problem. Here it is:

require File.dirname(__FILE__) + '/inheritable_accessor'
require File.dirname(__FILE__) + '/../test_helper'

class A
class_inheritable_accessor :foo
self.foo = "bar"
end

class B < A

end

class InheritableAttributesTest < Test::Unit::TestCase

def test_foo
assert_equal "bar", B.foo
end
end


And here what's in inheritable_accessor.rb:

class A
class << self
alias_method :a_old_inherited, :inherited

def inherited(base)
puts "A inherited"
a_old_inherited(base)
end
end
end

To try this at home, simply make a new rails project and drop these two files into your test/units directory. It should fail. But why? The inherited method does delegates to the previous implementation correctly, right? Now try switching the order of those two require statements. Poof, it passes.

Turns out the problem happens because of how class_inheritable_accessor is implemented. As you probably have guessed, it uses inherited. And yes, it does correctly delegate to the previous implementation. But it's where its implemented that is the problem: in order to let all classes be able to use this method, it's implemented on Class. However, there is an unfortunate side effect to this decision: it breaks for any class loaded before this code which itself defines inherited. In our example, the A class defines the inherited method before the rails code is loaded. But since the A class extends the Class class it's definition of inherited overrides the version rails adds to Class. The net outcome is that for the A class, and any class loaded before rails that defines inherited, class_inheritable_accessor is broken.

What's the right way to fix this? I'm not honestly sure. It could be that in order to do something like this which effectively adds a feature to the language, you need to be loaded first. On the flip side, it could be considered a bug that class_inheritable_accessor is broken in cases like this. Well, I had hoped that by the time I got this point in the post a clear solution would emerge. I suppose I'll have to leave it, as they say, as an exercise for the reader :)

Update: I was about to post this, and I went to grab some dinner and finally realized the solution: A still does not entirely obey Weirich's law. It correctly delegates to a previous implementation in the same class, but not to the superclass. A call to super at the end of the inherited method causes the test to pass. Adding the super call to the NB test runner code also causes my tests to be transactional again. Yay!

Saturday, September 13, 2008

Ruby in the browser at Rubyconf

Just got the news this week that my submission on ruby in the browser, has been accepted for Rubyconf. This looks like an awesome conference and I am really psyched to be included. It is also inspiring me to do some more work on rubyjs_on_rails and try to finish up an activeresource client. I'll be hacking on that in whatever free time I have available this weekend.

I'm also thrilled that I get to be part of another dialogue session with Joe O'Brien and Jim Weirich. This session will be a similar format to our railsconf dialogue which seemed to go over pretty well. It's always a ton of fun to collaborate with these two phenomenal guys.

Monday, August 25, 2008

rubyjs_on_rails hackfest at erubycon

I had a really fun time hacking on rubyjs at eRubycon. It's amazing how much more fun and productive it is to hack with someone else than by yourself. Thanks to Corey Haines for staying up late at the hotel being my hacking buddy. As a result, we now have what will be the beginning of an ActiveResource client for rubyjs_on_rails. I generated a simple scaffold resource example called Customer with a few fields. I then created a Customer class in the view and started getting it to speak restfully with rails. Here's the result:


require 'dom_element'
require 'json'
require 'rwt/HTTPRequest'

class Customer


attr_accessor :attributes

def initialize(attrs)
@attributes = attrs
end

def method_missing(method, *args)
if method =~ /(.*)=$/
attributes[$1] = args[0]
elsif attributes[method]
attributes[method]
else
super
end
end

def self.main
@name = DOMElement.find("name")
@address = DOMElement.find("address")
find_button = DOMElement.find("choose_customer_button")
save_button = DOMElement.find("save_button")
customer_id_text = DOMElement.find("customer_id")

find_button.observe("click") do |event|
Customer.find(customer_id_text["value"]) do |customer|
@name["value"] = customer.name
@address["value"] = customer.address
@customer = customer
end
end

save_button.observe("click") do |event|
@customer.name = @name["value"]
@customer.address = @address["value"]
@customer.save
end

rescue StandardError => ex
puts ex
end

def self.find(id)
HTTPRequest.asyncGet "/customers/#{id}.json" do |json|
hash = JSON.load(json)
yield Customer.new(hash["customer"])
end
end

def save
request_json = {:customer => attributes}.to_json
HTTPRequest.asyncImpl "/customers/#{id}.json", "PUT", request_json, "application/json" do |json|
self.attributes = JSON.load(json)
end
end

def to_json
attributes.to_json
end
end



The first part I did was find. I've had that done for a couple weeks. This was super easy because rubyjs and rails support json so easily. The only odd bit is that it takes a block instead of returning a result. This is because the find call does an ajax request, and the a in ajax stands for.. you guessed it, asynchronous. Passing a block in makes it nice and simple to handle the result easily though.

The part I got working at rubycon was the save. This proved to be much easier than I thought it would be as well due to rails now supporting json updates natively. The controller code doesn't even need to be aware that the parameters are coming in as json, you can access param[:customer] the same as you would if it was coming from a normal form submission. I had to be sure and specify it was a PUT request when I sent it in, which I didn't realize was as straightforward to do as it is. I monkeyed around with a _method param and all sorts of stuff before realizing I could just tell the HTTPRequest object what kind of request I wanted to make. Duh.

Though this is a specific example right now, I think a large portion of this code should be easy to extract out into an ActiveResouce client base class. I haven't had any more time to hack on this since erubycon, sadly, but this is what I'll be focusing on when next I do. Also, I just now put up a new repo on github to hold this and any other rubyjs_on_rails example code: git://github.com/superchris/rubyjs_rails_example.git. Enjoy!

Wednesday, July 9, 2008

Announcing rubyjs_on_rails: ARAX sans silverlight

So maybe you're like me: sure javascript is nice and all but you really like ruby better. And as we move toward richer web apps, there are times it sure would be nice to write some of the client side code in ruby. Along comes M$ with their sexy silverlight DLR magic to make it happen. But you're suspicious: it requires a browser plugin, and can you really trust that it will be there on all the platforms you want to support. Sure, maybe M$ has learned how to play nice and it will be different this time. Maybe.

Well, you can quit holding your breath and exhale, girls and boys. You can write ruby code that runs in the browser right now. Today. No plugin or nuthin. So here's how. First, you'll need my forked version of the rubyjs ruby to javascript compiler. It's a little like GWT for you Java folk. Get it here. Sorry, until githubs gem building process decides to show me some love you'll need to download and install it locally with:

gem install --local rubyjs-0.8.1.gem

Next, you take standard rails app and install my superfantastic* rubyjs_on_rails plugin like so:

script/plugin install git://github.com/superchris/rubyjs_on_rails.git

So what did you get? Well, basically, one measly helper method. Sad, huh. Hey! Don't judge me! Cool your jets and let's see what it does already. First make a rails controller with a single action. Any old action will do, but let's imagine it's called hello. Go find hello.html.erb and put a single button button that doesn't do anything in it like so:

<input type="button" id="button" value="Say Hello">

Now let's write some ruby code to respond to the button click. Make a file called hello.rb in the same directory as your view and put this in it:

require 'dom_element'

class Hello
def self.main
button = DOMElement.find("button")
button.observe("click") { puts "Hello from rubyjs!"}
end
end

DOMElement is a class provided by the rubyjs gem that makes it easy to, surprise surprise, work with dom elements. In our case we just use it to grab hold of our button and observe the click event with a block of ruby code. Kind of cool I think.

But how to get this code into the brower? You remember earlier when I said all this plugin gives you is one measly helper method and you were all "Meh. I am sooo not impressed." Well now let's see it in action. Add this to your view right below the the button:

<%= rubyjs "Hello", "main" %>

Now hit this action in your browser. Click the button and you'll see your ruby block fire. The rubyjs helper method goes and finds hello.rb, compiles into javascript, and outputs a script tag to serve it up. The arguments to it are the class name and and a class method to invoke. It needs to be a class method; As this is the entry point to the code there is no instance yet to invoke methods on.

So that's it. Ruby running in the browser. You're welcome.

*Your superfasticalness may vary. No actual superfantasticness is either expressed nor implied by this blog post

Thursday, July 3, 2008

eRubyCon: Be There!

Just a quick little post to get the word out to my readers about eRubyCon in Columbus, OH Aug. 15-17th. It's shaping up to be an awesome conference. And the early bird rate makes it the best conference deal I've ever heard of. Heck, even the post early bird rate makes it the best conference deal I've ever heard of. So register already. You know you wanna.

Wednesday, June 4, 2008

Write your javascript in ruby with rubyjs

I was inspired by Nathaniel Talbott's awesome railsconf talk to do some hacking. I had a lot of fun, and I think I came up with something worth sharing, so here it is. My willing victim was rubyjs, Michael Neumann's excellent little project I mentioned in an earlier post. If you want to play along at home, the first thing you'll need to do is fetch my code like so:
git clone git://github.com/superchris/rubyjs.git

There are some things in my repo that haven't made it into the gem version of rubyjs yet. My hack was a little "port" of the hangman example from Tapestry. It's there in examples/hangman. You can try it here. Or if you're so inclined, you can build it your dang self by running:

rake "examples/hangman/hangman.js"

This command uses a rake rule to run the rubyjs compiler which compiles hangman.rb to hangman.js.

So let's see some code how about, hmm?

class DOMElement
def initialize(element)
@dom_element = element
end

def observe(event, &block)
element = @dom_element
`
if (#<element>.addEventListener) {
#<element>.addEventListener(#<event>, #<block>, false);
} else {
#<element>.attachEvent("on" + #<event>, #<block>);
}
`
nil
end

def [](attribute)
element = @dom_element
`return #<element>[#<attribute>]`
end

def []=(attr, value)
element = @dom_element
`#<element>[#<attr>] = #<value>;`
nil
end

def self.find_js_element(element)
`return document.getElementById(#<element>);`
end


def self.find(element)
dom_element = self.find_js_element(element)
DOMElement.new(dom_element)
end

#
# Gets an HTML representation (as String) of an element's children.
#
# elem:: the element whose HTML is to be retrieved
# return:: the HTML representation of the element's children
#
def inner_html
elem = @dom_element
`
var ret = #<elem>.innerHTML;
return (ret == null) ? #<nil> : ret;`
end

#
# Sets the HTML contained within an element.
#
# elem:: the element whose inner HTML is to be set
# html:: the new html
#
def inner_html=(html)
elem = @dom_element
`
#<elem>.innerHTML = #<html>;
return #<nil>;`
end

end

The first class you see here is DOMElement. This gives some basic dom manipulation abilities in a ruby friendly way. I got this working by looking at the work Michael had done on porting GWT to ruby and extracting the bit I wanted and "rubifying" it a little. Basically, you can find elements by id, observe events with ruby blocks, and get/set attributes and inner html. Pretty simple, but has what I need. I should probably extract this into a separate file in rubyjs in case other people want it. You can also see lots of examples of how rubyjs and javascript talk to each other here.

Then it's on to the hangman code. First it's worth looking at the html so you can see what dom elements the code refers to:

And finally here's the hangman class:

class Hangman

attr_accessor :word, :letters, :misses

MAX_MISSES = 6

def initialize
@word = "snail"
@letters = @word.split ""
@guessed_letters = {}
@letters.each { |letter| @guessed_letters[letter] = false }
@misses = 0
@scaffold_div = DOMElement.find("scaffold_div")
@letters_div = DOMElement.find("letters")
@guess_input = DOMElement.find("letter")
@guess_button = DOMElement.find("guess")
@guess_button.observe("click") do
guess(@guess_input["value"])
end
@letters_div.inner_html = display_word
end

def display_word
letters.collect do |letter|
@guessed_letters[letter] ? letter : "_"
end.join
end

def guess(letter)
if letters.include?(letter)
@guessed_letters[letter] = true
@letters_div.inner_html = display_word
puts "You win!" if won?
else
@misses += 1
@scaffold_div.inner_html = "<img src='scaffold-#{@misses}.png' />"
if lost?
puts "You lost!"
@guess_button["disabled"] = true
end
end
@guess_input["value"] = ""
end

def lost?
@misses >= 6
end

def won?
@guessed_letters.values.each do |guessed|
return false unless guessed
end
return true
end

def self.main
hangman = Hangman.new
rescue StandardError => ex
puts ex
end
end

First you see the initialize method. Here is where we lookup our DOM elements, setup a Hash of which letters are guessed yet, and bind a block to the click event of our guess_button. Next comes the display_word method, which displays each letter or a blank if it's not been guessed.

The meat of the matter is in guess_letter, which is pretty simple. If the letter guessed is in the word we mark it, redisplay the word with the guessed letter and check if the user has won. If not, we update our miss count, display the right image, and check to see if the user lost. Won? and lost? are both trivial and not worth talking about.

Well, I had a lot of fun hacking on this. I think rubyjs has a good chance to be really useful as well as fun. It needs some love, certainly, but one of the things I did was start of woefully inadequate port of miniunit I'm calling microunit. It's in rubyjs/lib. This should make it easier and, I think, funner, to flesh out the core library for rubyjs.

For next steps I may expand on this example some. I'm thinking it would be a blast to tie to a Rails backend that gives me random words (right now it is always the same word :( ) or stores scores or some such foolishness. I could use this as an excuse to build an ActiveResource client for rubyjs and maybe a rubyjs on rails plugin.

If anyone cares about this, drop me a comment and let me know. Or if you think it's utterly stupid, tell me why. I promise to read all your comments and do whatever I feel like doing anyways :)

Tuesday, June 3, 2008

Back from Railsconf 2008

Wow, that was fun. Here's a quick recap of my personal highlights of Railsconf 2008.

Our Modelling Dialogue session

So I'm self-centered, but this was the talk I had the most concern about since I was in it. To be honest I had no idea how this was going to be received, so I was relieved and excited by the great response. I had this vision of all our laugh lines receiving the dead silence, crickets chirping response. But gratefully this was not the case. Jim Weirich has a great recap and lists the books we recommend on his blog. The feedback was so positive that we're already cooking up ideas for other talks in this format. To all those who came up and said something to us about our session: Thanks a ton! You have no idea what a help it is to hear from audience members about how it worked or didn't work for them. In short, there's nothing like going out on a limb and having it not break :) Major kudos to Jim and Joe for the idea for this one and for doing a great job pulling it off.

Nathaniel Talbott's hacking session


This was for me the most inspiring session. In a nutshell: hacking is good for you. If it's not fun it's not hacking. Being useful is not the point, enjoying yourself is. It inspired me to hack on rubyjs some more. I had a great time, and managed to come up with something worth sharing I think (tho as I said this was not the point, just a happy accident). More to come.

DHH Keynote

I actually enjoyed this quite a bit. Some of his points I really appreciated: he called BS on the whole IMO messed up american work culture idea that working more and sleeping less makes you supercoderdude. This is total crap, makes us less effective, and it's high time someone said so. Sleep more, and find some hobbies that don't involve computers. Get a life in other words. Great stuff.

Alternative Ruby impls

There was a lot of talk at the conference about non-MRI Ruby interpreters running Rails. I went to the IronRuby session. They run Rails and showed a simple scaffold example working. But I was most impressed by their silverlight demo of ruby running in the browser. Of course, only works in windows and maybe OSX. Allegedly moonlight will catch up. I'd love to really believe MS on that, but I'll believe it when I see it.

I didn't see the Rubinius talk but the did show it running Rails in a keynote. Boy is it slow, but if they can get performance up to snuff it sure is cool to have ruby in ruby.

Maglev IMO got a ton of unwarranted buzz. The part Avi did was cool, but the next part with the gemstone guy ruined it. They are nowhere close to running rails, and the sales guy was casting these sneaky sideways slams of other impls and selling a lot of vaporware. The perf numbers are impressive, but I totally agree with Charles that they mean nothing until they implement all of Ruby. Overall, I thought it kind of FUDDY, and I was quite saddened that the community just went along with it hook line and sinker.

JRuby was there, and had some good talks but since they've been running rails for a long while this wasn't really news and didn't get so much attention. I really like the JRuby rack stuff a lot tho, it opens even more doors. And I had at least one awesome hallway conversation about some big companies adopting JRuby and doing interesting stuff.

Tuesday, April 29, 2008

Ruby in the browser: a crazy idea whose time has come

This point of this blog post is first, to explain why I think the seemingly nutty idea having ruby execute in the web browser is actually a good one, and second, to show how you can actually do it. Today. It might be a little long, but bear with me.

I've been spending a lot of my free time over the past few months with various different approaches for getting ruby to work inside the web browser. The obvious question would be: Why? Let me start by stating this unequivocally: I do not hate Javascript. I've more than once argued that we as programmers have given not javascript nearly the respect that it deserves. In fact, it was only through treating javascript with respect that I arrived at the conclusions that brought me here.

A few months ago I was working on a project where we needed to move some reasonably complex business logic from the server to the client. The requirements were such that we just couldn't call the logic on the server and have the application behave as desired. I decided to try to use it as an excuse to improve my javascript skills. I used the object oriented features of Prototype, and some visual effects and the unit testing framework of Scriptaculous. To the best of my ability, I tried to approach it my javascript the same way I would any of my "main" development languages. I wrote the code using test driven development and attempted to make it as clearly communicative as possible. And you know what? I actually enjoyed it quite a bit. I was pleased with the code and the users seemed to like the result. I came away with a greatly improved opinion of javascript.

But something I observed bothered me: I ended up creating exactly the same classes in javascript as I had in server side language (in this case Java). This really shouldn't be surprising, since I used test driven design on the server side java code and the client side javascript code it only makes sense it would lead to a similar outcome. But duplicate code has always been (for me) the number one code smell that indicates a need to refactor. As far as I can tell, in order to get rid of this kind of duplicate code I need to develop my core business logic in a language which can execute on both the client (web browser) and the server. I mentioned earlier that I don't think javascript is a bad language, it's not very common for server side development right now and not my first choice. Currently my favorite language for server side code is ruby. If only ruby could execute in the browser. Pure fantasy, right? Believe it or not, there are actually at least 3 possible ways to do it.

JRuby

First up in my explorations was JRuby. Quite a while back there was an experiment by Dion Almaer to a JRuby applet to execute ruby code in the web page. While this was a nifty experiment when I tried to push this idea further I hit one brick wall after another. The first is that the current JRuby implementation does a lot of things that require additional privileges which means a signed applet. The second which proved more formidable is that javascript code can't call privileged java code at all. This meant that having javascript interact with my ruby code was out. Not good. Though JRuby is near and dear to my heart, this limitation, along with the potential barrier to entry of requiring the Java plugin seemed to make this not a promising solution for what I'm trying to do.

HotRuby

So I gave up on the idea for awhile, until I recently came across HotRuby. HotRuby is a Ruby VM written in javascript. It's a fascinating idea, and it actually works. Under the covers it's really a javascript interpreter for the YARV instruction set. It requires Ruby 1.9 since it depends on YARV. There is a script which dumps out the YARV instructions for a Ruby file in json format. In the browser you include the HotRuby javscript which creates a HotRuby javascript object which executes the jsonified YARV instructions. I installed 1.9 on my machine and checked out the HotRuby code from SVN. In a few hours of playing around, I was able to get it talking with prototype.js and had a simple example where i assigned a ruby block to an onclick event of a button. Although the project seems fairly experimental at this point, it shows great promise. In fact, I was getting ready to blog about it when I came across:

Rubyjs

Rubyjs is a ruby compiler that outputs javascript. It requires only Ruby 1.8.6 and installs as a gem which made it easy to get up and running with. Be aware, Ruby 1.8.5 will not work with it, which meant I had to upgrade. Rubyjs leverages another gem, ParseTree, which parses ruby code intos-expressions, which are kind of like a syntax tree. Rubyjs then emits javascript based on these. A bit more complex but seems to work well and can support more of Ruby than HotRuby can so far.

So far Rubyjs seems to be the most viable solution to me. And if you've made it this far into the post, you deserve to be rewarded with some code. To get running is easy, just gem install rubyjs. This will give you a rubyjs command which will take your ruby code and output javascript for it. Rubyjs comes with a few examples but none which really did what I was interested in doing. I wanted to show a simple block of ruby code listening to a button's onclick event. It took a bit of delving into the rubyjs code to figure out how to do it but I think the resulting code came out to be fairly understandable. Here it is:


class DOM
def self.find(element)
`return document.getElementById(#<element>);`
end
end

class Button
def initialize(js_element)
@js_element = js_element
end

def onclick(&block)
element = @js_element
`
if (#<element>.addEventListener) {
#<element>.addEventListener("click", #<block>, false);
} else {
#<element>.attachEvent("onclick", #<block>);
}
`
end
end

class Main
def self.main
button = Button.new(DOM.find("button"))
button.onclick { puts "clicked!"}
end
end


I create a simple DOM class to help me find an element by id. This shows how you talk to javascript code in rubyjs: by enclosing your javascript code in backticks. Rubyjs does automatically maps javascript objects to ruby and vice versa. I'm not going to delve into the nitty gritty of this too much, I'll do so in a later post.

The more interesting class here is Button. A Button instance gets pass in a DOM element in initialize and instances of button have a single method onclick which receives a block. As you would expect, this allows you to set the onclick handler of a button using a ruby block. The code in the onclick method of Button is also interesting: it shows how you can pass ruby objects into javascript (the javascript code I ripped of from Prototype to do cross browser event observing). Rubyjs will interpolate the javascript code in backticks and replace # type declarations with local variables converting the ruby objects to javascript as appropriate. What this means here is rubyjs is transforming our block into a javascript function for us. Pretty cool, eh?

To see this action, we'll need to compile this ruby code to javascript. The command to do it:


rubyjs button.rb -m Main -d -o button.js


The options tell rubyjs to compile button.rb to button.js. Rubyjs also needs an entry point, which is what -m Main is about. It expects Main to define a class method called Main. Very javaesque, but this is a minor gripe. You can see this code in action here.

There's a lot more to talk about with rubyjs, and I plan this to be the first in a series of posts about it. As part of the project there is also the beginning of a port of GWT to ruby. Altho the code is a bit to javaesque for my tastes so far (as you might expect for a direct port), the idea is very interesting. Let me also issue a giant thank you to Michael Neumann for writing rubyjs and for being incredibly helpful and responsive while I was experimenting. When I asked a question he posted a new version of the gem to address my concern within a few hours. Impressive.

Saturday, April 26, 2008

I'm presenting at RailsConf

Long overdue to blog about this, as I found out a few weeks ago now. Amazing but true: I'm speaking at Railsconf. I got invited to participate in a very non-conventional presentation with Joe O'Brien and Jim Weirich, two guys I respect enormously. We're presenting a dialogue between several developers on modeling. It's actually proving very challenging to write, as coming up with enough things for the characters to say in 50 minutes is a lot of text. But what an opportunity. All in all, the prospect of sharing the stage with Jim and Joe has me feeling a little like this.

Sunday, March 9, 2008

JRuby at TSSJS

Just a quick post to say I'll be presenting on JRuby at TSSJS. The JRuby core team is all at Scotland on Rails I presume, so I'll be doing my level best to represent. Drop me a line if you're going to be there.

Sunday, February 3, 2008

Rails "components": I do not think that word means what you think it means.

Right now I have 2 gigs. One is a java gig where I am working on an application I helped develop about two years ago using Tapestry. The other is a Rails gig. This has given me a good opportunity to compare and contrast the two. So far my experience is that I love the Ruby language and don't want to go back to doing Java except when/if I need to to pay the bills. But Rails I'm not as sold on. Mind you I'm not here to bash on Rails, there are some great things there and other people have done a fine job of praising them. But there are some things I definitely miss from Tapestry, and the most significant one is components.

Now when I say components I don't mean that abomination they stuck in Rails and then deprecated. I mean "real" components in the style of WebObjects, Tapestry, and other frameworks in that lineage. To qualify as "real" components in my mind means 3 things:

1. Reusable view and controller logic.
This is the easiest and this is I think what the deprecated Rails components thing was attempting to address. I've seen several other attempts to do this in Rails with varying degrees of success. I think the Presenter pattern being bandied about is essentially another attempt to have this same aspect of "componenty-ness".

2. Composability.
Components should be able to be composed out of other components. I haven's seen something in Rails that did this well yet. It could be out there, but if it is I haven't seen it. And this is also where it gets a little controversial. From what I've seen, DHH and rails core seems to believe that "high level" component reuse is either not possible or worthwhile. Having seen it absolutely work and work well in Tapestry I have to respectfully disagree. The Tapestry Palette and Table components are both "high-level" and reusable on multiple projects to great affect. However even the discussion around component reuse seemed to me to be framed incorrectly, as it seemed focused on components being reused between applications. This is certainly possible, but I find much greater levels of component reuse within an application.

3. Binding.
This to me seems to be the most neglected feature of components, but in my mind is perhaps the most important. This is simply the ability to say something like "the value property of this text field is bound to @person.name". This should result in me being able to access @person with the name already set. I shouldn't have to touch the params hash at all. This would get rid of what I see as an imporant non-dry part of Rails. For example, why should I have to say in my view:

text_field "person", "name"

And then in my controller:

@person = Person.new(params[:person])

With binding I wouldn't have to.

The other place binding comes into play in component frameworks is events. Not only properties support binding, but events do as well. For example, a Button component could have a block bound to the "onlick" property. The framework then manages and abstracts away all the details of HTTP, etc.

I guess that's really what I miss: with a good component framework (and not all of them are) I feel like I am working at a higher level of abstraction. When building reasonably sophisticated web applications, I really miss that. With simple web sites, I don't so much.

So where am I going with all this? Well, I've been thinking about these ideas a lot since I started doing Rails and talking about them with my pairmate. Recently the ruby-component-web-frameworks Google group has been getting active again so I thought I'd blog about my ideas. I've started doing a little code experiment to see how it might look to implement some of these ideas as a Rails plugin. It seems not too difficult so far; it never ceases to amaze me how much I can say in so little Ruby code. When we have an SVN repo setup I'll share what I have but it's just an architecture spike right now.