Michael Kovacs' photos More of Michael Kovacs' photos
Recommend Me Cable Car Software logo

Thursday, March 23, 2006

Rails realities part 10 (rails plugins and riff)

A quick timeout from TSSS conference for a quick rails posting....

As of the rails 14.x series and the 1.0 release rails has introduced the notion of plugins to allow functionality to be added to rails applications more easily. I recently had need for some code that would give me a diff of model objects against the saved version or another instance of that type. So for example I have:

crazy_dog = Dog.find_by_name('CJ')
crazy_dog.temperment = 'excitable'
good_dog = Dog.find_by_name('Cody')
good_dog.temperment = 'calm'

I'd like some way of being able to easily diff these two objects' properties. I wasn't looking forward to writing something to take care of this and I figured that this is common enough of a problem that someone has to have already complained or written something to do this for me. Luckily technoweenie, a prolific contributor to the RoR framework, pointed me to "riff". Riff is a rails plugin that performs exactly what I need, a diff of model objects' properties.

Installing the riff plugin into my app was as simple as going to the root of my rails app (foo) and typing:

michael-kovacs-computer:~/foo mkovacs$ svn export http://tfletcher.com/svn/rails-plugins/riff/ vendor/plugins/riff
A vendor/plugins/riff
A vendor/plugins/riff/test
A vendor/plugins/riff/test/test_setup.rb
A vendor/plugins/riff/test/riff_test.rb
A vendor/plugins/riff/init.rb
A vendor/plugins/riff/lib
A vendor/plugins/riff/lib/riff.rb
A vendor/plugins/riff/README
Exported revision 8.

Here's the part of the post where I rant about how the rails core team is adding needless complexity by modifying the syntax of the ruby language.
Unless you're running edgerails this plugin will not work out of the box. If you try calling "diff" on your model objects you'll get an exception at the following line (line 30 of riff.rb):

diffable_attributes = content_columns.map(&:name)

The problem is with the &:name portion. That isn't standard ruby syntax. The rails core team apparently thinks that the following code is too verbose:

diffable_attributes = content_columns.map { |c| c.name }

The difference between the two is that the Proc object that is passed to the map method is created by rails implicitly while the "old fashioned" way is to create it yourself. To non-ruby folks the "&" is a reference to a Proc object which is just a snippet of code to be executed. The ":name" is a symbol. In this instance "&:name" in English means "Create a Proc object and call 'name' on each object contained within the content_columns array." So the second snippet of code is how you would write it with standard ruby syntax. Nice, huh? I can see how saving those extra keystrokes and adding another level of abstraction adds so much value and is totally worth the extra complexity. *sigh*

Anyhow, replacing the line above in riff.rb and continuing my code example above I can now perform diffs on my model objects by simply restarting my server and adding the following code:

crazy_dog = Dog.find_by_name('CJ')
crazy_dog.temperment = 'excitable'
good_dog = Dog.find_by_name('Cody')
good_dog.temperment = 'calm'

good_dog.diff(crazy_dog) -> { :name => ['Cody', 'CJ'], :temperment => ['calm', 'excitable'] }

And that's it. In 5 minutes I have the ability to diff my model objects. Great stuff. This works fantastic for all property level diffs that are built in types. For complex types, like related objects this won't work but this is a good building block.

Note to core rails dev team members. Don't get crazy adding syntactic sugar to rails that adds needless complexity without much gain. It's already difficult enough to learn a new language and framework without the added fun of trying to figure out all these extensions to the language that aren't documented anywhere. I only knew WTF was going on here because I talked to technoweenie and he told me what this was.

Most plugins are version specific. You needed a newer version of Rails to run riff. That the version-specific part was an extension to Symbol doesn't seem to matter that much in that connection (it could just as well have been a new method to ActiveRecord).

But we should indeed make it easier to version-bind plugins. If you try to install riff and do not have the Rails version it requires, it should fail with a proper error message. I'll get on that and see what I can do. Thanks for the reminder.
BTW, you're right about Symbol#to_proc not being documented. I just checked in documentation for it, which will be part of the forthcoming 1.1.1 release.

Thanks for helping to improve Rails!

Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?