Thursday, June 12, 2014

Coding by Convention is Great

... except when it isn't.

"Coding by Convention" (a.k.a. "Convention over Configuration") attempts to simplify programming by telling the programmer the preferred way to name or organize things.  It often saves a lot of time and hassle.  Without it, you write extra configuration files, typically in XML.  Spring and J2EE used to require way too much configuration, with lots of stupid redundancies, something like "When I say Foo bean I mean you to use a Foo.class, when they go to myCompany.com/order/books use the com.myCompany.order.books servlet".  On the other hand there can be too much convention - I've never used Maven, but hear that it is particularly dictatorial and hard to modify.

I'm developing a lot in JavaScript / node.js lately, and wanted the ability to save my data as either csv files or iCal (.ics) files.  Searching the NPM registry finds several candidates.

In csv files, the header line contains the name for each column.  If using convention, this would be taken from the property key.  And the property would map directly to the data in the following lines.  Sometimes you would want to change this.  Can you, or is it all done by convention?  As I understand their documentation:

to-csv      convention only
fast-csv    allows for transformation, but over an entire row
json-2-csv  convention only
json-csv    allows for flexible transformations

I ended up using json-csv, though one drawback of it's power is that it takes more work to use.

On the iCalendar side, the question is how to setup or create the complex VEVENT information.  One could use properties named DTSTART, UID, etc.  But it's extremely unlikely that your object has properties with those unusual and capitalized names, and the correct values.  Plus, DTSTART has a complex format with a possible "DATE-TIME" option and a time format of YYYYMMDDTHHMMSSZ.

cozy-iCal       builds VEvent objects programmatically
ical-generator  convention (uses .start .end for DTSTART DTEND)
icalevent       convention (also uses .start, .end etc)
icsjs           builds programmatically
icalendar       builds VEvent objects programmatically

So, it's a mishmash. And convention, while simple and convenient, doesn't always do the trick.  For example, in my CSV data I'd like to include the distance from the user's location.  This is obviously not even a field in the data, since it is calculated on the fly per user from the respective latitudes and longitudes.  The CSV should not include the latitude and longitude - not very useful to the end user.  My start and end dates are also not fields, they are stored in an array.  So I definitely can't use convention.

Unless...

The obvious work-around is to create a new temporary object, that meets the required convention, from the fields and data in the original, "real" object.  In many cases, you would just wrote custom code to do this, especially if speed is a concern.  There are also some modules that vaguely do this.  (Did I miss some???)  But they are pretty limited.   For example object-adapter can only copy values from a source object (renaming the fields), not apply any functions.

So, I wrote my own general-purpose module, remodeler.  For convenience there are copyKeys() and excludeKeys() methods for properties you simply want to copy as-is or ignore.  For the fancy stuff, you provide key / value pairs.  The key is the new property name, and the value is a "transformation".

If the transformation is a String, it means to copy the value from the old object, using the string as property name. For example, "UID", "uid" would mean to create a UID property by copying the previous uid property.

If the transformation is a function, it will be called via function(oldObject, key) and the result used as the new value.  In practice, the key argument is often ignored.  For example,

"SUMMARY", function(o,k) { return 'name:' + o.name + ' date:' + o.date[0]; }

would mean to create a new SUMMARY property by concatenating two existing values.

In many cases, you are still better off writing your own custom code.  On further thought, I'm not sure how useful my module will be, since in JavaScript, it is just so easy to go

var newthing = {
   newkey: oldObject.oldKey,
   ...
}

Other times you can follow the conventions, or use the programmatic interface.  However, if you want a quick way to "remodel" your domain object, this module might meet your needs.  Let me know what you think.  I think I'm going to try using this with ical-generator.