avatarharuki zaemon

Drag & Drop Prioritizable Lists

By

Yes, it’s true, Scriptaculous already provides a Sortable that makes it almost trivial to enable drag’n’drop sorting of your HTML lists. Whenever an item is moved an onUpdate() event is called (if provided) allowing you to inspect the new order and presumably perform an AJAX request to record the change. In principle, this sounds great but I’ve never really liked it for a couple of reasons.

For a start, if you have any appreciable number of items updating each in the database just to re-order one seems somewhat unnecessary. Not withstanding the fact that we need to send all those ids to the server in the first place.

Secondly, if you’re doing any kind of filtering, it’s difficult at best to take the newly constructed ordering and apply that at the back-end; what happens to all the items that may be lurking in between that aren’t presently displayed?

Enter Prioritizable (itself built on top of Sortable).

You use it in much the same way as Sortable with the major difference being that the onUpdate() event is called with threearguments: the item that was moved, the sibling relative to which it was moved, and the relative position ("higher" or "lower"). And, if like me, you’re feeling a bit RESTful, it’s pretty easy to turn these arguments into a nice semantic URL and parameters as shown:

Prioritizable.create($("chores"), {
    onUpdate: function(item, position, sibling) {
        id = item.substring(6);                           // "chore_17" => "17"
        sibling_id = to.substring(6);                     // "chore_2" => "2"
        url = "/chores/" + sibling_id + "/" + position;   // "/chores/2/higher"
        new Ajax.Request(url, {
            method: "post",parameters: { id: id }
        });
    }
});

When the onUpdate() event is called we POST the id of the item to be moved to a path constructed from the id of the sibling and the relative position. Assuming the the user moves chore_17 just above chore_2 we would POST "id=17" to /chores/2/higher.

In practice, I combine this client-side behaviour with some server-side code that provides move_higher_than() and move_lower_than() methods that efficiently handle all the necessary database updates.

All the pieces mentioned will eventually be available alongside Cogent’s other Rails plugins but until then, here’s enough of the Javascript side of things to get you going.

var Prioritizable = {
    create: function(element) {
        options = Object.extend(arguments[1] || {}, {
            onChange: Prioritizable.onChange,
            onUpdate: Prioritizable.onUpdate
        });
        Sortable.create(element, options);
    },
    destroy: function(element) {
        Sortable.destroy(element);
    },
    onChange: function(item) {
        Sortable.options(item)._item = item;
    },
    onUpdate: function(element) {
        options = Sortable.options(element);
        item = options._item;
        options._item = null;
        sibling = item.previous();
        if (other) {
            position = "higher";
        } else {
            sibling = item.next();
            position = "lower";
        }
        options.onUpdate(item, position, sibling);
    }
};

Enjoy!

Getting Chronic to Parse Non-U.S. Dates

By

In an attempt to push yet more behaviour from my Rails controllers into model classes, I was extracting some code into a yet-to-be-published plugin that allows date and time columns to be set using more human-readable values. For examples:

>> task.completed_at = **"now"**>> task.completed_at=> Wed Apr 09 14:39:12 +1000 2008

Although my actual requirement was to support "now" and "today" I figured it would be rather cool if I could support anything that Chronic does. (If you haven’t used Chronic before, it’s a natural language date/time parser written in pure Ruby that understands a vast array of expressions including ranges.)

Naturally (no pun intended), Chronic also supports explicit dates such as “1/2/08”. As part of my testing however, I discovered that the date parsing is decidedly US-centric presuming, of course, that dates are specified as “month-day-year”. So for anyone living in say, Australia, it can be pretty frustrating to have Chronic.parse("1/2/08") return "Wed Jan 02 12:00:00 +1100 2008" rather than the expected "Fri Feb 01 12:00:00 +1100 2008".

The good news is, there is a solution. The bad news is, the solution is far from elegant. But first some context.

Chronic is actually written very nicely and the code is fairly easy to follow. In essence it works as follows: the input string is tokenized; each token is inspected to see if it’s a keyword such as a month name, day name, or a number, etc.; and finally tries to matches the sequence of tokens against a pattern such as “a day number followed by a month name and then a year.” The problem arises because the pattern for matching “month-day-year” comes before the one for “day-month-year” meaning that unless the first number is greater than 12, Chronic will always consider it to be a month.

The less than elegant solution is almost trivial and involves switching the order in which the patterns are matched. Doing so returns the desired result:

>> Chronic.parse("1/2/08")=> "Fri Feb 01 12:00:00 +1100 2008"

Which is all very well and good but now we have the reverse problem. What would be better is if we had a more general solution, one that allows us to specify the desired precedence when parsing:

>> Chronic.parse("1/2/08")                                    # Default to U.S. date formats=> Wed Jan 02 12:00:00 +1100 2008>> Chronic.parse("1/2/08", **:explicit_date_format => :non_us**)  # Prefer Non-U.S. formats=> Fri Feb 01 12:00:00 +1100 2008>> Chronic.parse("1/2/08", **:explicit_date_format => :us**)      # Prefer U.S. formats=> Wed Jan 02 12:00:00 +1100 2008

Among other things, this allows us to have users in Australia enter dates with one format and users in the U.S. another.

For anyone interested, I’ve pasted a diff for each approach below.

A less than elegant solution:

--- a/chronic-0.2.3/lib/chronic/handlers.rb+++ b/chronic-0.2.3/lib/chronic/handlers.rb@@ -13,8 +13,8 @@ module ChronicHandler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],

A more general solution:

--- a/chronic-0.2.3/lib/chronic/chronic.rb+++ b/chronic-0.2.3/lib/chronic/chronic.rb@@ -43,7 +43,8 @@ module Chronicdefault_options = {:context => :future,-                         :ambiguous_time_range => 6}+                         :ambiguous_time_range => 6,+                         :explicit_date_format => :us}options = default_options.merge specified_options# ensure the specified options are valid@@ -51,6 +52,7 @@ module Chronicdefault_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")end[:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")+      [:us, :non_us].include?(options[:explicit_date_format]) || raise(InvalidArgumentException, "Invalid value ':#{options[:explicit_date_format]}' for :explicit_date_format specified. Valid values are :us and :non_us.")# store now for later =)@now = options[:now] -- - a/chronic-0.2.3/lib/chronic/handlers.rb+++ b/chronic-0.2.3/lib/chronic/handlers.rb@@ -3,41 +3,50 @@ module Chronicclass << selfdef definitions #:nodoc:-       @definitions ||=-      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],+     if @definitions.nil?+        us_date = [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),+                   Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),+                   Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),+                   Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),+                   Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),+                   Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),+                   Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),+                   Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),+                   Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),+                   Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),+                   Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)]++        non_us_date = us_date.dup+        non_us_date[7] = us_date[8]+        non_us_date[8] = us_date[7]++          @definitions =+        {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],-       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),-                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),-                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),-                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),-                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),-                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),-                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],+         :date => {:us => us_date, :non_us => non_us_date},-       # tonight at 7pm-       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),-                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),-                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],+         # tonight at 7pm+         :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),+                     Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),+                     Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],-       # 3 weeks from now, in 2 months-       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),-                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),-                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],+         # 3 weeks from now, in 2 months+         :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),+                    Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),+                    Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],-       # 3rd week in march-       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),-                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]-      }+         # 3rd week in march+         :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),+                     Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]+        }+      end+      @definitionsenddef tokens_to_span(tokens, options) #:nodoc:# maybe it's a specific date-      self.definitions[:date].each do |handler|+      self.definitions[:date][options[:explicit_date_format]].each do |handler|if handler.match(tokens, self.definitions)puts "-date" if Chronic.debuggood_tokens = tokens.select { |o| !o.get_tag Separator }

Fixing lowpro form submission failures

By

I finally worked out why my forms weren’t submitting when the user hits the ENTER key. lowpro serializes the button that was clicked along with any other parmeters when submitting a form via AJAX. However, when the user hits enter under FireFox, there is no button and consequently the browser barfs. Safari on the other hand tries to be too helpful and triggers an onclick event for the first submit button (which is why I never noticed it).

So anyway, rather than try to be too clever myself, I simply changed the parameter serialization in Remote.Form.onsubmit to look like:

parameters : this.element.serialize({ submit: this._submitButton ? this._submitButton.name : null })

Problem solved.

Culturally Sensitive JavaScript

By

JavaScript is a fantastic little language and with the likes of Prototype, Scriptaculous, and my newest favourite, lowpro, you can build some quite frankly, remarkable web applications.

One area where most web browsers fall down however is in their error-reporting, or lack thereof. A fact that has caused me to waste seemingly countless hours trying to find the source of some problem or other only to realise that a typo that had been staring me in the face the entire time was to blame!

Now, like just about any programming library I use these days, most JavaScript libraries use American english. initiali**z**e, capitali**z**e, you know what I’m talking about.

For the most part the use of ‘z’ instead of ’s’ isn’t too much of a problem but just recently I consistently tried to use lowpro’s addBehaviour method, only there isn’t one. It’s called addBehavior (sans ‘u’).

So today after about 20 minutes cursing and swearing at the spelling Steve asked “is there anyway you could create an alias?” Being JavaScript the answer is of course “abso-bloody-lutely!”:

Event.addBehaviour = Event.addBehavior;

You can alias just about anything this way.

No more will my code silently fail due to differences in spelling :)

Cognescentised

By

As of the first of this month (March, 2008) I’ve become a full-time employee of Cogent leading a product development arm of the business.

I’ve know all the cogent guys for some time now and over the years we’ve all become very good friends and colleagues. Many years ago while working together, Steve Hayes and I discussed starting something and whilst it sounded great in principle, my personal circumstances didn’t allow for it. In the meantime, Steve and Marty Andrews got their hands dirty and laid the foundation for what is promising to be a great company based on a set of values almost unheard of in, well, any industry really.

RedHill will still be around for many years to come with [Simian]/simian and Rails Plugins but my day to day software development will be well and truly under the Cogent banner.

So if you’re in Melbourne (or anywhere in the world for that matter) and you’ve been looking for a someone to get your Ruby on Rails project up and out the door, we’d love to hear from you.

Censorship

By

After I posted the last entry, I thought I’d better go share my experience with the Apple community. Let those that have been experiencing the same problems know where I’d got to.

I replied to my own discussion forum post with the following text:

So I’ve changed the country setting back from Australia to the default, New Zealand and magically it appears @ 5Ghz. WTF!?Which was then automatically translated into:

So I’ve changed the country setting back from Australia to the default, New Zealand and magically it appears @ 5Ghz. ***!?"_Since when did we start treating acronyms as potentially offensive? Maybe I meant, _“Why This Fixes it? (Which of course I didn’t but that’s not the point.)

“ABC!?” would reflect quite accurately what came out of my mouth when the piece of hardware failed to work as expected but I’m betting wouldn’t be censored.

Git: Pushing with Every Commit is Flawed

By

Yesterday, both James and Marty indicated that one thing they didn’t like with Git is that not every commit is pushed to the server. Actually what they were complaining about wasn’t that, it was that they couldn’t see what I was working on until I pushed to the server. I complained that kinda defeated the purpose of Git – to push on every commit – but couldn’t articulate why. I now can.

Last night I made a bunch of changes, a spike, to set me up for today. I dutifully pushed those changes to the server (as requested) and went to bed. Unknown to me at the time, though completely predictable, those changes (a spike remember) broke the app. Amusingly, only a day earlier, James had quipped that “we’re doing agile. shouldn’t the repository always reflect working software? what’s with all the broken functionality?”

Git allows me to make micro changes, to spike stuff all day long, and eventually when I’m confident there’s something for the world to see I can push those changes back to the origin. It gives me far greater control and flexibility over the nature and granularity of the changes I make. It means I can have greater trust that changes I make WONT affect other developers or “customers”. These benefits are immediately negated when I push on every commit.

Why don’t we have this problem with something like subversion? Good question. I think the answer is simple: because subversion forces me to work in a way that ensures that everything I commit is in full working order. It also means I tend to commit larger chunks and work a lot more on branches. I would argue that if that’s what we want to do then using Git doesn’t buy us much except “offline” mode which, by definition, doesn’t satisfy the “transparency” issue anyway.

I’m not using Git as a subversion replacement. I’m using it precisely because it allows me to do “more” than subversion. With this comes cultural change. I respect the need for greater transparency but I don’t believe that pushing (at least to the master branch) on every commit is the solution.

So perhaps flawed is too strong a word. Perhaps Ill-considered would be more appropriate.

TimePustule

By

I bought an Apple TimeCapsule for backups sniggers from the usual suspects. The fact is I had perfectly good backups working with an external 500GB LaCie drive but being the glutton for punishment thought I’d give TC a whirl.

So I unpacked it, hooked it up, configured it and then mysteriously, I couldn’t get it to work @ 5Ghz (a/n) which for me is an absolute must as my apartment building seems to have a at least one 2.4Ghz network for every apartment! By work I mean broadcast, be visible, locatable, whatever you want to call it. At 2.4Ghz, there it is. At 5Ghz, where did my TimeCapsule go?

The odd thing is that I made darn sure I had it configured identically to the AirportExtreme it was to replace which ran just dandy @ 5Ghz, even down to the passwords.

Having had it for 3 days with no joy and a couple of other users complaining of the same problem I decided it was time to take it back to the apple store today but before I do so, I thought I’d better erase the hard drive and reset to factory settings. For whatever reason (masochism?) I felt compelled to try giving it one more go, this time changing settings one at a time and restarting the capsule.

It starts off in 2.4Ghz g-mode so the first thing I changed was to have it run at 5Ghz n-mode leaving everything else at the factory default. Restart. There it is. I double check my network configuration (using the network utility). Yup, 5Ghz, 300Mbps. Ok. Change another setting. Restart. Still there.

Next, multicast rate. Chnage that from 6 to 24Mbps. Restart. Fine. Use wide-channels. Fine. Change country from the default New Zealand (go figure!) to Australia. Where did my wireless network go? WTF!?

Whatever changing the country does, it’s not good! At first I thought it might change the channel (which seems hard-wired to 36 by default) but no, it still thinks it’s broadcasting on channel 36. I can still connect to it via ethernet but it won’t show up over WiFi at 5Ghz.

So it’s now running quite happily (somewhere in NewZealand), locked up as tightly as the proverbial duck’s bum, at 5Ghz. I’m honestly still not convinced it’s as reliable as my AirportExtreme though and it seems to be slower than when I backed up to mac mini which, theoretically, should be slower as it had to make more network hops and compete with half-a-dozen CPU and Disk intensive application on it (including recording and playback of TV, movies, music, etc.)

Not happy Jan. How hard can it be to take an AirportExtreme and whack a bloody 1TB hard disk inside it!? Though what can I really expect of a one-dot-oh product from a vendor that seems to be slipping in quality as it’s market share increases.

Managing Views with ActiveRecord Migrations

By

I just added very simple view support to redhillonrails_core trunk. The changes give you the ability to create and drop views using create_view :name, "definition" and drop_view :name respectively as well as preserving the views in schema.rb.

WARNING: This is currently only supported for PostgreSQL. Creating views in MySQL will cause extra tables to be created in schema.rb! Probably not what you wanted.

Ambitious Scoping

By

If you haven’t checked out Ambition for generating ActiveRecord queries I highly recommend you do so. In a nutshell, it allows you to generate queries using the standard Ruby Enumeration idioms. Take for example the following snippet:

class Message < ActiveRecord::Base
  def self.unread
    select { |m| m.read_at == nil }.entries
  end
end

Message.unread
# => SELECT * FROM messages WHERE messages.read_at IS NULL

Notice anything missing? SQL perhaps?

Now clearly there’s a whole lotta magic going on here. That said, ambition does use the standard ActiveRecord finders to query the database. One of the side-effects of this is that when navigating associations, you automatically get all the appropriate scoping.

So, for example, assuming a has_many relationship between User and Message we can do something like this:

user = User.detect { |c| c.name == 'Simon Harris' }
# => "SELECT * FROM users WHERE (users.name = 'Simon Harris') LIMIT 1"

user.messages.unread
# => "SELECT * FROM messages WHERE messages.read_at IS NULL AND messages.user_id = 3"

Here we navigated from user to messages and selected only those that have no read_at date.

As you can see, the restriction by user_id was automatically inserted for us by ActiveRecord as we navigated the association. We didn’t have to do a thing. It just worked the way you would expect it to. Very cool!

However (there’s always a but) this only worked because we included an execution trigger in Messages.unread. These “kickers” as they’re known, include the standard Enueration methods first, entries, detect and size.

Without a kicker, the result of select is actually a query object that you can assign to a variable, call more selects on, or even store for later use! This last feature is kinda neat and leads to some nice stuff with partial caching but it also leads to some hidden complications.

If we remove the kicker from Message.unread so that it instead returns a query and call the kicker explicitly, this happens:

class Message < ActiveRecord::Base
  def self.unread
    select { |m| m.read_at == nil }
  end
end

user = User.detect { |c| c.name == 'Simon Harris' }
# => "SELECT * FROM users WHERE (users.name = 'Simon Harris') LIMIT 1"

user.messages.unread.entries
# => "SELECT * FROM messages WHERE messages.read_at IS NULL"

Where did all the scoping go?!

In the earlier example, the query was executed inside a method on an model class which, because it was called whilst navigating an association, means it was also executed using an appropriate with_scope.

In the second example however, because the query wasn’t actually executed until sometime later – ie outside any model classes and associations – all the scoping information has been forgotten, as if it were never there in the first place. Bbbbbut, you want to have all that Ambition goodness and you’d like your scoping as well right? So what’s a poor boy (or girl) to do?

I’m glad you asked. Here’s a patch to ambitious-activerecord that remembers the scoping that was in play at the time the query was constructed. It seems to work for all my current uses.

DISCLAIMER: very quick-and-dirty and thoroughly untested.

Lock Your Screen with LaunchBar

By

Mostly just so I don’t forget, here are some stupidly simple instructions for locking your screen using LaunchBar. (Technically, this actually starts the screen saver but as that is password protected it has the same effect as locking the screen.)

  • Open the the Script Editor, /Applications/AppleScript/Script Editor
  • Enter the text activate application "ScreenSaverEngine"
  • Save it to ~/Library/Scripts/Lock Screen
  • Open LaunchBar > Configuration > Scripts > Options
  • Enable “Home Library Scripts”
  • Save and Rescan

To lock the screen, simply open LaunchBar and type lock screen (on my setup loc is enough) and hit enter. Voila!

Hotkey fans will probably want to consider using something like MenuMaster or iKey to execute the AppleScript.

HELP: Developing with WebSphere

By

I have to know. Is it just me or is the compile, deploy, debug cycle with WAS just so ridiculously long and slow that it’s practically impossible to do anything productive with it? Does anyone ACTUALLY use WAS for development? If so, how do you manage? If not, what do you use instead and how do you then make sure that it still runs in WAS when it can take 10 minutes just to re-deploy an application? Is there some secret to determining why WAS silently rolls back transactions, for no apparent reason (WLTC0033W and WLTC0032W), before control has even returned to the container and with no exceptions; doesn’t seem to be able set transaction isolation levels on SQL Server but instead creates extra connections with the correct isolation level (READ_COMMITTED) leaving the original one handed back from the DataSource as the default (REPEATABLE_READ); and mysteriously times-out waiting for Oracle connections from a pool of 10 even it only ever seems to need at most 3 when the maximum pool size is set to 100!

Values

By

I was chatting with the lads over at Cogent last Friday about our new venture and the discussion turned to company values. Most of us think of company values as those totally nebulous statements CEOs love to have plastered all over the walls in the vain hope that employees will feel as though being made to work ridiculously long hours was all for a good cause. Thankfully, Cogent’s values aren’t much like other’s and in fact Marty made a great point: values should be actionable in some concrete and meaningful way. Cogent’s values certainly appeal to me but I’m sure they won’t appeal to everyone. Financial transparency being one of particular favourites.

One of the less tangible values is “fairness and equality.” That is, treat everyone fairly and equally, as human-beings. Which got me thinking. It reminded me of “treat others as you would yourself” which I’ve always had a bit of an issue with. Not that I disagree with the sentiment, not at all, but something about it never sits quite right. After a little thinking I settled on “hold yourself to a higher standard than you do others”. I think it fits nicely with treating others fairly and equally.

Making del.icio.us work for me

By

I’ve been posting links to del.icio.us for sometime now. Mainly things I can’t get to right now or don’t access frequently enough to warrant cluttering up my bookmarks with but don’t want to loose track of. When I first started, I would tag things with whatever keywords I could think of. After a while of doing this, I ended up with a huge number of tags but still couldn’t find anything. There was just too much random stuff to filter on.

Attempt #2: I decided to whittle my tags down to the smallest set possible. This was better however every time I went to tag something I found needed to sift through the existing tags and decide which one(s) suited, remember if I had used the plural or singular form, etc. and even then, there was just too much stuff to sift through because now all my links were concentrated in only a few tags.

Attempt #3: My most recent attempt, and the one that has been working brilliantly for me now, is to not worry too much about what exact tags I’m using but, rather than tag it with everything I can think of, I instead use exactly the same keywords I would use were I to search for the link on Google. This sometimes means I have tags that wouldn’t seem to make sense as categories on their own.

Now when I want to find something I’ve bookmarked, I go to my del.icio.us page and search through “your bookmarks” – shouldn’t that be “my bookmarks”? – as if I were doing a search on Google. I reckon I have a close to 100% hit rate and at most 2 or 3 results for any given search. I’ve finally managed to make del.icio.us useful!

Even better, I have set up a custom search in LaunchBar: http://del.icio.us/search/?type=user&p=*

Call for Survey Participants

By

I’m looking to do a survey on software quality using source code duplication, cyclmatic complexity and npath complexity as the measure.

The general idea is to take open and closed source projects and measure the “quality”. The results will then be analysed to determine:

  • How open/closed source compare;
  • How individual projects compare to the “norm”; and
  • What, if any, discernible factors have influenced the quality of the code.

Once the data has been compiled and analysed, I will make the results available to the participants and to the general public. The identity of participants, projects, resources and people involved will be completely confidential – indeed even I don’t actually need to know the names of projects nor will I have access to or need access to the source code – and will not be disclosed to third-parties.

At the request of the study participants, I will be more than happy to present the findings – in person if possible – and make any recommendations that may arise. I will also make myself available to perform a repeat survey in 12-18 months time should this be so desired.

For largely pragmatic reasons, there are several requirements for participation:

  • The project MUST be in a subversion repository;
  • The source code MUST be Java;
  • The project SHOULD NOT be using Simain or CPD (or for that matter any other duplicate code checking tool);
  • The project SHOULD NOT be using Complexian (or for that matter any other complexity checking tool); and
  • An environment that can execute Java and Subversion command-line tools.

Participation couldn’t be simpler. After you contact me, I’ll send you a package containing some processing scripts. To run, simply checkout the HEAD of your project’s source tree into a directory using subversion and run one of the batch scripts contained in the package. For Windows use bin\process.bat src_dir cvs_file For *nix, use bin/process src_dir cvs_file. The scripts will process each revision starting from the currently checked out revision to the first revision.

This processing may take considerable time so if you need to interrupt the process at any point you may do so in the usual manner – eg. ctrl+c. You can then re-start the processing at a later date and it will pick-up where it left off.

Once processing has finished, you can email me the CSV file.

If you think you have a project that fits the profile and would be willing to share some processing time, your participation would be greatly appreciated.

Content Management Simplicity

By

I’ve been wanting to put together a new website for my Aikido School for some time now and had been looking around for possible tools to help out. Possible choices included: continuing to maintain it by hand with a redesign; use some kind of site generation tool; write a Rails app; or find a CMS tool.

The first wasn’t really an option – I’m time poor as it is. The second didn’t seem like a much better solution either to be honest. And If the first two didn’t seem like attractive solutions the third was possibly even worse. So, it came to finding a CMS tool and after a little investigation, googling, reading forums and talking with friends, colleagues, ISPs and a few drinks I had narrowed the field down to two: Joomla; or Drupal.

(Interestingly, both are built on Perl/PHP – quite frankly the idea of a Java-based CMS running inside a J2EE container makes me feel ill just thinking about it and I couldn’t find any Ruby-based solutions that even rate a mention.)

Both Joomla and Drupal are solid, mature products. Both are open source and have a thriving community. Both support extensions/plugins/modules and both have had books written about them. All good things. But in the end I settled on Drupal for a number of reasons some emotive, some based on other’s experience and some technical – perhaps the fact that Joomla only runs on MySQL could be considered both technical AND emotive.

So far, I’m highly impressed. It was very easy to install and setup. The admin screens even tell me when the underlying OS or database isn’t configured properly and even go as far as telling me how to fix the problem! There are a bewildering number of modules available that do almost everything you can imagine. And when I finally wanted to do something outside the “box” – aggregating images based on taxonomies and displayed in the side navigation of the home page – it was almost trivial to programatically construct the content. My distinct impression is that for many sites I’ve traditionally built as yet another new application, a CMS like Drupal plus some custom code would be sufficient.

I’m still not a huge fan of Perl. I like many of the language features such as built in regular expressions, etc. making it ideal for command-line scripting, but it still feels a little too close to the metal for me and lacks much of the syntactic sugar around objects that make languages like Ruby more attractive. That said, I’ve thus far only needed to write custom PHP code once and I reckon it would make a nice module – assuming I could find the time to publish yet another open source project destined to become abandonware ;-)

If only someone in the Ruby community would put something together that’s as good as these two, I’d consider switching. Perhaps something built around merb however maturity and a strong community are certainly big selling points for me, perhaps making it difficult for a new player to gain traction?

Out on the Range

By

I’m pretty sure I’ve blogged before about my dislike of prefix/suffix pairs such as from/to and start/end. They smell of a missing abstraction. There are however languages that provide a nice solution.

We have an application that stores a date against a record in the database. At various times, we want to see if that date falls between a specific date range. The initial implementation of the code looked something like this:

def within?(from_date, to_date)@date >= from && @date <= toend

Which you would then be used along the lines of:

puts "Not available" if record.within?(start_date, end_date)

This immediately activated my olfactory senses: Not only do we have some common suffixes, we also have comparison code that looks like pretty much like every other kind of range comparison code you’re ever likely to write.

As it happens, Ruby has a Range class built-in that I don’t see being used nearly as much as I think it deserves. Using a range we could re-write the example to look something like:

def within?(date_range)date_range.includes?(@date)end

And then in the worst case, call it like this:

puts "Not available" if record.within?(start_date..end_date)

I say worst case because here the client code still uses two separate dates which are then converted to a Range for the purposes of the call. (Interestingly the use of use of .. to construct a Range out of two individual values uses the same number of keystrokes as passing the parameters individually!) However, in the best case, we’d have been using a Range to store the dates in the first place.

Yes, more Rails plugins

By

FWIW, I’ve just created two very new, very simple and VERY BETA Rails plugins.

The first, Simian, is pretty obvious: it adds a couple of rake tasks to run duplicate code checks against your rails project using, you guessed it, Simian.

The second is Restful Transactions. This plugin ensures that your controller’s create, update and destroy actions are wrapped in a database transaction meaning you don’t have to think about it.

As always, you’ll find these plugins and more over at the RedHill on Rails plugin page

UPDATE: The plugin now wraps any action executed as a POST, PUT or DELETE rather than just the create, update and destroy actions.

Stripper

By

Yet another ridiculously simple Rails plugin, Stripper removes leading and trailing blanks from attribute values.

Leading and trailing spaces on attribute values can be a problem. They’re almost never wanted nor intended. In fact, oracle actually treats empty strings as NULL. That’s not to say we necessarily think that the database should be messing with our data mind you but it does show that at least someone agrees with us.

A Diamond in the Rough

By

No, for once this has nothing to do with Ruby (deathly silence) rather, it concerns some problems many people have experienced when trying to use JNDI resources from Quartz jobs under Websphere.

In short, Websphere does not propogate any java:comp/env/ JNDI context to application created (ie non-container) threads. This is apparently by design with the upshot being that if you’re using quartz you won’t be able to use JDBC data sources from any jobs.

There a number of forum topics that discuss this problem along with various solutions including “use a 3rd-party connection pool instead.” Hardly music to the ears when you’ve just spend the last 3 months converting a customer’s entire application over to using JNDI resources. However, after much Googling, I happened upon an article that ultimately solved the problem in a Websphere-friendly manner. It even comes with sample code so you can get started almost immediately.

In essence, the solution makes use of Websphere’s Asychronous Beans (and the Work Manager in particular) as a sort of thread pool. We then took this idea and adapted it around the use of Quartz’s pluggable ThreadPool.

The final twist was the need to support both Websphere and Tomcat seamlessly without resorting to either build or deployment time configuration. For this we simply created a ThreadPool implementation that looks up the Work Manager in the JNDI context. If it exists, we assume that we’re in Websphere and go from there; otherwise we use the default Quartz implementation and hope for the best :)