lesscode.org


'Ruby' Archives

Ruby On Rails Is Like Red Wine  32

Cat.: Ruby
08. January 2006

Fifteen, twenty years ago North America was a culinary wasteland, save for the rare pockets scattered along east, west and south coasts. A brief trip to places like New Orleans would shock and jolt the palate of the plain white crustless bread, stake-and-potatoes visitors. Of course, let’s not forget other fine culinary spots, such as Manhattan, San Francisco, and several other islands in the otherwise homogenous sea of mediocrity.

Things started to change rapidly in the nineties, reaching levels of unprecedented sophistication in dinning and wining. Today, we can enjoy the incredibly opulent selection offered to us through stores such as The Whole Paycheck (er, Whole Foods), but things were drastically different only fifteen years ago.

I remember how unpopular wine drinking was not that long ago. Especially in my neck of the woods (I live in Vancouver, British Columbia, where most locals still mentally inhabit the 19th century time zone). Up until very recently, this was a beer country. All normal, regular males were expected to drink beer. Wine was mostly available in those hideous 4 litre carton boxes, and tasted like pee that went bad. Anyone caught preferring or, god forbid, drinking wine was declared gay on the spot. It used to be very hard to smuggle a bottle of import wine into one’s apartment.

At social gatherings, wine drinking was reserved for the female population. Men were either expected to chug beer, or go with some hard booze.

Luckily, things started changing for the better, and now we have pretty decent selection of import wines from all around the world. Plus, straight men are not afraid that they’ll be labelled as ‘faggots’ if they are seen drinking wine in public.

What Does Wine Have to Do With Ruby on Rails?

Programming languages and development platforms are kind of similar to alcoholic beverages. Majority of software developers choose products that belong to the beer category. Sure, there’s plenty of variety there, but in the final analysis, it’s all beer after all. Thus programming languages such as Pascal, Perl, Java, C#, ColdFusion, PHP etc. are basically just different varieties of beer (like, dark beer, pale ale, lager, etc.) Languages such as Assembler and C are more like hard liqueurs (e.g. whiskey, tequila, vodka). Languages such as Smalltalk, Python, Lisp, are like white wine — sophisticated and enchanting.

Finally, languages like Ruby (with all its domain specific flavors, such as Rake and Ruby on Rails) are like red wine. Red wine is that special gamut of products that demand incredibly high level of devotion and finesse, thus creating its own breed of aficionados.

And like the friction that wine drinkers were having with the overruling beer crowd, Ruby and Ruby on Rails users are today experiencing similar friction coming from the ‘beer crowd’ of programming languages. Most of the dissent seems to be coming from the ‘plain vanilla’ lager crowd (the Java consumers). There’s also some dissent coming from the ‘white wine’ crowd, but in a much more civilized fashion.

I wonder if the same cultural evolution, that helped promote the spreading of the red wine culture throughout North America, will happen for the ‘red wine of software development’, that is, for Ruby on Rails? At this point, it doesn’t seem very likely, as anyone who’s using Rails seems to be labelled in a knee-jerk fashion as being ‘queer’. It is obvious that the mainstream crowd of software developers (i.e. the beer drinking crowd) is extremely uncomfortable with the uprising of the red wine drinking crowd (i.e. the Ruby on Rails). The beer drinking crowd confesses that they simply don’t get what could possibly be so enchanting about enjoying the red wine.

Hopefully, cooler heads will prevail, and the medical findings that red wine is actually beneficial for one’s health will pave the way toward adopting the red wine consumption on a larger scale.

Logging microformats  10

Cat.: Python, Ruby, microformats
13. December 2005

Here’s a slightly elided log line from a system I work with:

WARN 2005-09-28 14:38:38 StandardContext[/pmr]  {”mid”:”123″, “type”:”XYZ”, “msg”:”foo”, “T”:”2005-09-28 14:38″, “L”:”warn”}

It identifies an XML document passing through the system using some keys from the envelope. It may seem a strange looking way to log an event, but between the angle brackets lies a useful side effect - it’s a Python dictionary. Hence this (vastly oversimplified) example log processing code, that runs eval() on each log line to load the dictionaries into memory:

Python 2.4 (#60, Nov 30 2004, 11:49:19) [MSC v.1310 32 bit (Intel)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.
>>> dicts =[]
>>> import re
>>> p = re.compile(”.*({.*?}).*”)
>>> file = open(”log.txt”).readlines()
>>> for line in file:
…     m = p.match(line)
…     if m:
…             d = eval(m.group(1))
…             dicts.append(d)
…
>>> for d in dicts:
…    print d
…
{’L': ‘warn’, ‘mid’: ‘123′, ‘type’: ‘XYZ’, ‘T’: ‘2005-09-28 14:38′, ‘msg’: ‘frobnizer  found’}
{’L': ‘warn’, ‘mid’: ‘124′, ‘type’: ‘XYZ’, ‘T’: ‘2005-09-28 14:38′, ‘msg’: ‘frobnizer  found’}
{’L': ‘warn’, ‘mid’: ‘125′, ‘type’: ‘XYZ’, ‘T’: ‘2005-09-28 14:38′, ‘msg’: ‘frobnizer  found’}
{’L': ‘warn’, ‘mid’: ‘126′, ‘type’: ‘XYZ’, ‘T’: ‘2005-09-28 14:38′, ‘msg’: ‘frobnizer  found’}
>>>

What’s nice about doing this is that it’s cheap to write the log line in a way that maximises usefulness later on - logs are often an afterthought, but good logging is critical to operating production systems. And it’s one example where creating metadata is cheap.

As well as programming data structures (changing the above to a Ruby hash is straightforward), you can get log lines to output something like Turtle, which is a greppable RDF format. What’s good about Turtle in particular is that you can dump or stream a log file straight into an RDF query engine, such as ARQ without any transform step and start writing queries against the data. Or how about writing down inferencing rules that help indicate the business consequences of a software error? The log lines themselves don’t carry enough information, but combined with rules and some context as to where the log event came from you can do interesting and useful things - the biggest issue with any monitoring software is gathering up all those machine level events and contextualising them so that data (noise) can be turned it into information (signal). Think of a machine readable data stucture inside logfiles as just another microformat.

Rails / Django Chicago Debate  9

Cat.: Python, Ruby
17. November 2005

On December 3rd, a mess of fine Chicago area Ruby, Python, and Linux user groups are to meet up at DePaul University for a Django / Rails love-fest. I’ve heard there might also be time for debate between songs and hand-holding. Adrian Holovaty, award winning journalist and distinguished gentlemen, will be fielding questions for the Pythonistas while David Heinemeier Hansson, Extraordinary Alien / “one man” trying to save the world (hey, I owe him one), will be answering for the Rubyists.

Even better, the Snakes and Rubies website is collecting questions for Adrian or David or both. Get over there and get your questions in before it’s too late.

If there is any way someone could capture the audio for this event, it would be very much appreciated.

Simplest Possible Plugin Manager For Rails  6

Cat.: Ruby, Rails
27. October 2005

UPDATE: I ended up making some pretty massive changes. You can configure multiple plugin repositories, install, update, remove, and discover plugins. The directions for installation are still valid but you’ll need to run plugin --help to get a feel for the changes in usage.


UPDATE: The plugin manager has been included with Rails 1.0 RC4. Run script/plugin --help from a fresh Rails app for usage information.


Rails 1.0 RC1 shipped with a simple plugin system - drop a directory under vendor/plugins that contains an init.rb file to be executed at configuration time and an optional lib directory to be placed on the path. Do whatever you please from there. It’s a simple hook into the startup cycle and a much needed addition.

About 19 hours ago, David suggested that people link to their plugins from the Rails Wiki as a kind of interim solution to the problem of not having a standard means of packaging and managing these things. They did and with links to their plugins’ subversion repositories.

Here’s a simple (150 line) plugin manager.

Install it like this:

$ cd my-rails-app
$ curl http://lesscode.org/svn/rtomayko/rails/scripts/plugin > script/plugin
$ chmod +x script/plugin

Then see what plugins are available:

$ ./script/plugin
continuous_builder  http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder
asset_timestamping  http://svn.aviditybytes.com/rails/plugins/asset_timestamping
enumerations_mixin  http://svn.protocool.com/rails/plugins/enumerations_mixin/trunk
calculations        http://techno-weenie.net/svn/projects/calculations/
...

Next, install stuff to your vendor/plugins directory:

$ ./script/plugin continuous_builder asset_timestamping

Here’s how it works:

  1. Scrape the Plugin page for things that look like subversion repositories with plugins. (Yes, I’m using regular expressions. Yes, I understand the issues. No, I don’t care.)

  2. If vendor/plugins is under subversion control, the script will modify the svn:externals property on that directory and perform an update. You can use normal subversion commands to keep the plugins up to date.

  3. Or, if vendor/plugins is not under subversion control, the plugin is pulled via svn export.

If you want to use svn:externals, make sure you have your vendor/plugins directory under subversion’s control before installing any plugins . If your not sure, do something like this:

$ svn info vendor/plugins
foo:    (Not a versioned resource)
$ svn mkdir vendor/plugins
$ svn ci -m "adding teh plugins directory so I can use this r0x3ring plugin manager..."

This probably won’t work on Windows at the moment and assumes you have the command line subversion client utilities available (svn).

It’s useful as is, but please, make it better.

Fun with Fixtures  2

Cat.: Ruby, Rails
25. September 2005

My favorite part of Agile Web Development with Rails is the section on testing. I’ve found the framework around testing included with Rails to be a wonderful blend of simplicity and power and this chapter in the book is the perfect compliment. It’s based largely on the core Ruby Test::Unit module but adds some important features on top.

One of those features is Fixtures. Fixtures provide a simple, YAML-based file format for storing database state that should be loaded before test runs. The database is wiped and the fixtures are loaded before each individual test executes, providing consistent state for tests. See the section on fixtures in A Guide to Testing the Rails for more information. Here’s an example fixture from said section:

# low & behold!  I am a YAML comment!
david:
 id: 1 
 name: David Heinemeier Hansson 
 birthday: 1979-10-15 
 profession: Systems development

steve:
 id: 2
 name: Steve Ross Kellock
 birthday: 1974-09-27
 profession: guy with keyboard

The cool thing is that the top level fixture names (in this case, “david” and “steve”) become instance variables in your test case, allowing you to access fixture data in a very intuitively way from tests. The result of this is test code that reads like a story and is often almost humorous.

The book includes a sidebar with a little David head lamenting the importance of “Picking Good Fixture Names”:

Just like the names of variables in general, you want to keep the names of fixtures as self-explanatory as possible. This increases the readability of the tests when you’re asserting that @valid_order_for_fred is indeed Fred’s valid order. It also makes it a lot easier to remember which fixture you’re supposed to test against without having to look up p1 or order4. The more fixtures you get, the more important it is to pick good fixture names. So, starting early keeps you happy later.

But what to do with fixtures that can’t easily get a self-explanatory name like @valid_order_for_fred? Pick natural names that you have an easier time associating to a role. For example, instead of using order1, use christmas_order. Instead of customer1, use fred. Once you get into the habit of natural names, you’ll soon be weaving a nice little story about how fred is paying for his christmas_order with his invalid_credit_card first, then paying his valid_credit_card, and finally choosing to ship it all off to aunt_mary.

Association-based stories are key to remembering large worlds of fixtures with ease.

Taking this advice, I started in on testing a part of an application I’m working on now. The result is worth posting in it’s entirety (hint: it gets interesting towards the middle):

require File.dirname(__FILE__) + ‘/../test_helper’

class EnrolleeTest < Test::Unit::TestCase
  fixtures :coverage_types, :plans, :coverages, :plan_levels, :ppo_options,
           :elections, :enrollees, :enrollments, :tpas, :tpas_users, :users,
           :roles

  def setup
    @joe = Enrollee.find(@joe_the_policy_holder.id)
    @rita = Enrollee.find(@rita_the_spouse.id)
    @billy = Enrollee.find(@billy_the_dependent.id)
    @alice = Enrollee.find(@alice_the_dependent.id)
    @the_family = [@joe, @rita, @billy, @alice]
    @enrollment = Enrollment.find(@test_enrollment.id)
  end

  
  def test_common_attrs
    @the_family.each do |enrollee|
      assert_kind_of Enrollee, enrollee
      assert_equal @enrollment.id, enrollee.enrollment_id
      assert_equal @enrollment, enrollee.enrollment
      assert_equal @joe.id, enrollee.policy_holder_id
      assert_equal @joe, enrollee.policy_holder
    end
  end

  def test_get_gender
    assert_equal :male, @joe.gender, "Joe is a male"
    assert @joe.male?, "Joe is a male"
    assert_equal :female, @rita.gender, "Rita is a female"
    assert @rita.female?, "Rita is a female"
    assert !@billy.female?, "Billy is not a female"
    assert !@alice.male?, "Sally is not a male"
  end

  def test_set_gender
    [:female, ‘F’, 2].each do |tok|
      @joe.gender = tok
      assert_equal :female, @joe.gender, "Joe is now a female"
      assert @joe.save, "Joe could not be saved after sex change"
      @joe.reload
      assert_equal :female, @joe.gender, "Joe is now a female"
    end

    [:male, ‘M’, 1].each do |tok|
      @rita.gender = tok
      assert_equal :male, @rita.gender, "Rita is now a male"
      assert @rita.save, "Rita could not be saved after sex change"
      @rita.reload
      assert_equal :male, @rita.gender, "Rita is now a male"
    end

    # make sure that we can set it to nil
    @joe.gender = nil
    assert_equal nil, @joe.gender
    assert @joe.save(false), "Joe couldn’t be saved after neutering…"
    @joe.reload
    assert_equal nil, @joe.gender

    @joe.gender = ‘’
    assert_equal nil, @joe.gender
    assert @joe.save(false), "Joe couldn’t be saved after neutering…"
    @joe.reload
    assert_equal nil, @joe.gender
  end

  def test_marital_status
    assert_equal :married, @joe.marital_status
    assert @joe.married?, "Joe is married"
    assert !@joe.single?, "Joe is not single"
    assert @billy.single?, "Billy is single"
    assert !@billy.married?, "Billy is not married"
    assert_equal :single, @billy.marital_status
  end

  def test_set_marital_status
    [:married, 2].each do |tok|
      @billy.marital_status = tok
      assert_equal :married, @billy.marital_status
      assert @billy.save, "Billy could not be saved after he got married"
      @billy.reload
      assert_equal :married, @billy.marital_status
    end

    [:single, 1].each do |tok|
      @rita.marital_status = tok
      assert_equal :single, @rita.marital_status
      assert @rita.save, "Rita could not be saved after her divorce"
      @rita.reload
      assert_equal :single, @rita.marital_status
    end
  end
end

:)