Wednesday, 29 October 2008

Rails a little over-protective?

Was busy developing and minding my own business, when suddenly my functional tests all stopped working giving me this error:

ActionController::InvalidAuthenticityToken: No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store).

Now we happen to have just switched on the use of the cookie store - and the actions all work just fine from the browser. Checking the forms even shows up the authenticity tokens etc... this only occurs during testing.

Googling gives a few rspec-based solutions, but very few are even possible for Test::Unit. I tried disabling config.action_controller.allow_forgery_protection - but that did a big nothing for me. So I bodgied up a quick-n-dirty workaround just for the test environment as per below.

Right now I still don't know what's causing the bug, but this gets me past it until I can figure out what's actually wrong. YMMV :)

  # See ActionController::RequestForgeryProtection for details
  if RAILS_ENV == 'test'
    # dodgy workaround for Rails 2.0 bug in functional tests - which don't
    # seem to use a cookie store properly. Reference to issue:
    # http://groups.google.com/group/railsspace/browse_thread/thread/fcdbfa4e65bf86de
    protect_from_forgery :secret => 'c1c6ebaee01fecc9aa9bc105d235b2c2'
  else
    # Uncomment the :secret if you're not using the cookie session store
    protect_from_forgery # :secret => 'c1c6ebaee01fecc9aa9bc105d235b2c2'
  end

Tuesday, 28 October 2008

Headhunters strike out again

A follow-on from my negative experience with headhunters. I've begun receiving exactly the sort of "job offers" that I was fearing. I've got offers (from all over the world, mind) asking me if I'd like to work as (for example) a junior developer on an embedded-kernel contract in Belgium... which'd be nice and useful if I were interested in that sort of thing instead of having only 6 months non-commercial experience (at uni) doing this stuff as a elective subject... or at least held a work-permit that allowed me to work in Belgium.

The end result makes it fairly obvious that their recruiters are just too damn lazy to do more than a quick search on the db... followed by spamming all the results. They've ripped out the db-friendly data (ie my naked skillset) and divorced it from the important stuff (ie what sort of job I'm actually interested in). Leaving me to be spammed by every idiot with a contract to push.

If I were a junior developer, desperate for any job I could get, I'd probably just have to suck it up... but I'm not - and they're alienating an in-demand developer with lots of experience. This doesn't make me feel like responding positively to any of their future offers... even if they do suit my requirements.

What gets me is that it'd be so easy to make this system work. Even if they had a few extra fields in their db for "what type of job is the candidate looking for", they could run these as a filter over their resultset and turn that spam into much-less-despised targetted advertising.

I'd even be interested (possibly even grateful) to receive such a service - instead of so annoyed that I've blogged about it twice.

Enumerable.average

Was looking around for a quickie function to do an average on an Enumerable/Array just like max and min - and found there isn't one. So here's a quick-n-dirty one that you can drop into config/environment.rb. Examples for use in the comments:

module Enumerable
  # returns a simple average of each item
  # If a block is passed - it will try to perform the operation on the item.
  # if the result is nil - it won't be counted towards the average.
  # Egs: 
  # [1,2,3].average == 2
  # ["a","ab","abc"].average &:length == 2
  # @purchases.average &:item_price == 10.45
  def average(&block)
    the_sum = 0
    total_count = 0
    self.each do |item|
      # either the actual item, or a method called on the item
      next_value = block_given? ? yield(item) : item
      unless next_value.nil?
        the_sum += next_value
        total_count += 1
      end
    end
    return nil unless total_count > 0
    the_sum / total_count
  end
end

I used it for displaying neat averages in the view thus:

    <p>Averages:
    <br />Number of items: <%= @purchases.average &:num_items -%>
    <br />Unit price: <%= @purchases.average &:price -%>
    <br />Total price: <%= @purchases.average {|p| p.num_items * p.price } -%> </p>

Thursday, 23 October 2008

That doesn't help me, it helps you

I just got off the phone with Australia Post and I'm not happy. Even though they were very polite to me, they failed to provide a good customer service experience. This issue requires a bit of backstory.

While in Melbourne last weekend, I'd bought two half-cases of wine and paid for them to be delivered. I have no car, so I left explicit instructions for the parcels to be left on the back doorstep if I was not at home.

I got one of the little blue cards in the post yesterday, telling me that they tried to deliver one of the parcels to me in the 1 hour when I was out of the house getting some lunch. I figured that the winery must have forgotten to put the "in case not at home" message on the parcel, so today I decided to just fetch it from the post office.

I damaged my knees fencing, a couple of years ago, so walking eight blocks while carrying a heavy box is painful for me. My arms and knees both ache, now, but I got the parcel home... only to find a second little blue card in the mailbox.

The parcel from yesterday clearly has the "in case not at home" instructions printed on the outside, so I figured I'd call up Australia Post and see if they could actually do what I paid them to - which is to deliver my second parcel to my house.

I called them up and waded through their long telephone menu-system to get to a real human being. The man at the other end seemed kind enough, but the only option available to him was to allow me to lodge a complaint against the contractor who failed to follow the given instructions.

I don't really want to lodge a complaint - I just wanted my parcel delivered - which is what I paid for.

I asked the man if he could find anyone that would be able to help me get what I wanted. He went back to his supervisor, leaving me on hold, and returned to tell me that the only way I could get it redelivered was to call up the people that I originally bought it from (some winery out in the Yarra valley - I don't know which one as the parcel is still sitting in the aussie post office), and to get *them* to call aussie post and figure out if their contract supports "redelivery-on-failure"...

Good grief! From my POV it's pretty simple:

  1. I paid for delivery to my home
  2. Aussie Post stuffed it up
  3. Aussie Post should find a way to get it right

Instead of finding a way to get my parcel to me, they kept insisting that they were helping me by starting up a complaints process and making sure it "never happens again". If you want to reprimand your contractor, that's your business. It will help you improve your own processes over time, and no doubt his non-instruction-following is the weak link that broke in this particular chain of events...

But doing this isn't going to help me. Getting my parcel to me helps me - that's all I want, and Aussie Post wasn't willing ot help in that regard. I was a customer on the line right then and there with a problem that Australia Post caused - and wasn't able to get any resolution that actually solved that problem.

I was left with the feeling that Australia Post wasn't willing take responsibility for this mistake and do something about it. Instead they wanted to shift the blame to the contractor, and then claim that responsibility was out of their hands.

This reeks of bad customer service.

If you want to leave a good impression with your customers, follow one of the basic rules of human conduct: if you stuff up, take responsibility and try to fix it. Whatever you do, don't just give a reassurance that it won't happen again... we won't believe you. After all, you've just proven that you can't be trusted. At least if you try to find a way to give me what you promised in the first place - it'll go a long way towards reassuring me that you can act in good faith.

Tuesday, 14 October 2008

soap4r + rails can't find SOAP::Mapping::EncodedRegistry

Joining a new project, I had to go through the usual setting-up rigmarole - which means finding several new things to break/configure/reinstall. One of which was to get soap4r to run.

I'd run gem install soap4r just fine, but running rake was giving me the error below:

rake aborted!
uninitialized constant SOAP::Mapping::EncodedRegistry

Scrounging around gave several solutions that didn't work, mainly using two of the three lines below. I required all three of the following before it ran for me.

# Add these lines to config/environment.rb just before the Rails initializer code
require 'rubygems'
gem 'soap4r'
require 'soap/mapping'

Tuesday, 7 October 2008

Words and pictures

Ok, this is a neat toy: Wordle is a site that runs a java applet that takes some text (or a URL for a feed) and make a pretty word-picture... like the one below for this blog.

Hmmm... I wonder if I can use it to improve the editing of my blogposts. Looks like I use "going" far too much!;)

Sunday, 28 September 2008

Headhunters and a dearth of trust

What is it with some headhunters?
I had a phonecall from a headhunter the other day that really rubbed my fur up the wrong way.

First, they wanted my CV in Word format. I hate that. Quite apart from having to explain that I use Linux - which frequently leads in to the "what is an OS" discussion that I find so annoying coming from an IT recruiter. Yes I can use OpenOffice to create a Word doc, but it's a preference thing. I prefer to write my CV in PDF so I have more control over the formatting - because I've found that .doc format "helps" too much, and suffers from bit-rot.

But it's not like they can't read/print-out a copy of my CV if it's in PDF. What really gets me is that there are only two reasons for wanting my CV in Word. First is so they can copy/paste my skills/experience into their mammoth database - reducing me to a set of numbers... which I really don't like.

I recognise that might be a knock to their usual system, but I really don't like being just a number in an uncharted sea of applicants. It doesn't work for me to be recruited by a company that would view me as only so important as the number (and not the quality) of the years of experience I have.

Second is that they want to remove my contact information so that they are the only point of contact between me and the employer. This view is especially reinforced by headhunters that refuse to give you the prospective employer's name, industry, size or any other potentially-identifying details (or politely change subject when I ask, as this one did). How the hell am I supposed to decide if I'm interested in working for a company I know nothing about?

I should point out - I get a phonecall a week from various recruiters. I don't have time to go to an interview a week - so I have to decide, based on what you tell me, if you are worth a morning of my time.

Word to the wise: recruiters perform a valuable service - both to employers and employees. If you are good at what you do, then nobody will be unhappy about paying you for your services! The only recruiters that need to resort to cheap tactics like hiding names are the ones that cannot build a reputation for excellence on their own... thus if you flat-out refuse, then it instantly gets my suspicions up.

Yes, I'm sure you will get ocasionally burnt by employees and employers that are unscrupulous and try to screw you out of your fee. But are those the kind of clients you want to keep anyway?

Recruiting is a people business - it is outsourced HR and builds upon long-term relationships. Trust is important, and screwing around like this ruffles that layer of trust and just feels tacky.

Please don't do it.

Thursday, 25 September 2008

Rspec mocks too much

I've recently had to learn the other side of testing - ie rspec, due to my new contract having a test suite written in "the other testing suite" * ;)

Seems fairly straightforward so far, but there's one big worry I have: Rspec has a huge reliance upon mocks.

Now I completely understand (and endorse) the benefits of using mocks. It means you can independantly test a model/controller/whatever without it mattering if something happens to have broken in some other model/module/whatever. This is a Good Thing and allows for more accurate testing due to all the benefits of loose coupling. However, I get a bit worried if everything is mocked out and there are no integration-style tests at all.

For example, if I'm testing a view, and using rspec best practice I've mocked out all model objects and don't bother with the real thing. What happens if a model changes (say a column is removed that shows up on an index list)? The view specs all rely on a mock that explicitly states what is returned, so the view specs all still pass. But the index page will be broken for a user that actually uses the site, because the real object doesn't bahave that way anymore. The mocks and the model are out of synch. :P

In an infrequently-used area of the site, this sort of error may go undetected and slip through to release.

This worries me. I know I like to rely on my test suite to catch things like this wherever possible. Sure I generally do a manual click-through, but I know that this is unreliable especially WRT edge-cases.

So, am I needlessly worrying? Have I simply missed something about how rspec is supposed to be used? What's best practice in this situation?

*Which admittedly feels a lot like having to switch from vi to emacs... Sure I know that "the other one" works for lots of people, and I meant to get around to learning it someday...

Tuesday, 23 September 2008

Getting ffmpeg to work under ubuntu

I had a fair whack of trouble getting ffmpeg to compile on ubuntu. These are the steps I took that finally got it working for me.

Firstly - I'm going to assume you've downloaded and installed the appropriate libraries (codecs?) for the various audio/video formats that you are going to use (eg lame). I'm just going to cover making ffmpeg actually compile and run.

The usual set of steps will be

  • Downloading ffmpeg: svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
  • cd ffmpeg
  • Configuring appropriately to your build. Eg: ./configure --enable-libmp3lame --disable-vhook --disable-mmx --enable-nonfree --enable-gpl --enable-shared --enable-libamr-nb --enable-libfaad --enable-libx264 --enable-encoder=x264 --prefix=/opt/local --extra-ldflags=-L/opt/local/lib --extra-cflags=-I/opt/local/include
  • make
  • sudo make install

If that all works for you, then brilliant, but for me it fell over while trying to make, telling me it couldn't find x264. For some reason it's necessary to add --enable-pthreads to make it work on ubuntu.

The next big hurdle for me was that a bug appears in the v4l2 library for ubuntu. It doesn’t properly load some of the basic linux types. This is apparently a well-known bug in some of the older linux headers, and will stall the compilation of ffmpeg with a whole slew of bogus error messages, that essentially boil down to the __u64 and __s64 types failing to be defined appropriately.

To make a temporary fix, you can open up asm/types.h (I used find to find it) and figure out where the asm types are located on your distro (for me they’re in asm-i386/types.h).

Now, copy the two lines that define __u64 and __s64 and paste them into:
/usr/include/linux/videodev2.h just above their first use to help define v4l2_std_id

Note - this is an ugly, quick hack, but it should get the compilation going again. For a more permanent solution - I'd suggest chatting to the ubuntu people... or maybe it's time for me to get around to upgrading from feisty ;)

That will generally get you compiled and installed, but I then fell over a run-time bug that boiled down to an error message: “cannot find libavdevice.so.52”. This problem had me stalled for weeks. Google returns a lot of talk about the problem, but very few solutions, but eventually I stumbled across the answer: you need to add a library path to your environment thus:
export LD_LIBRARY_PATH=/usr/local/lib/

Finally ffmpeg safely up and running on ubuntu. Yay!

Saturday, 20 September 2008

Snippet: MyModel.options_for_select

How many times have you written a quickie drop-down box to choose one instance of an associated model? Here's a quickie monkey-patch that will help. Put the following in environment.rb (or similar):

class ActiveRecord::Base
  # convenience method for selection boxes of this model
  # Assumes existance of a function "display_name" that will be a
  # displayable name for the select box.
  def self.options_for_select
    self.find(:all).map {|p| [p.display_name, p.id] }
  end
end

Make sure you have a function in your model named "display_name" that returns what you'd like to see in your drop-down, eg:

class Person < ActiveRecord::Base
  has_many :posts

  # convenience method for the person's name
  def display_name
    [first_name, last_name].compact.join(', ')
  end
end
class Post < ActiveRecord::Base
  belongs_to :person

  alias :display_name :title
end

Then use in a view (eg as follows):

<% form_for(@person) do |f| %>
<p>Pick a post: <%= f.select :post_id, Post.options_for_select -%><%= f.submit 'Go!' -%></p>
<% end %>

Thursday, 4 September 2008

Snippet: monkey-patching a gem

Need to make a quick monkey patch to a gem you're using? Follow the steps below:

  1. If you don't have a vendor gems directory: svn mkdir vendor/gems
  2. cd vendor/gems
  3. Unpack the gem into your vendor/gems directory with
    unpack gem <the_gem_name>
  4. Checkin the above changes so you have a clean version of the gem to start with
  5. Make your monkey patches and check them in
  6. Make sure vendor/gems is in the loadpath for gems

And you're done

Tuesday, 19 August 2008

Link: Why I don't have a startup yet

Great post called Why I don't have a startup yet. Snippet:

"I love what I do at Yahoo, and I care enough about what we create that I want to focus all my energy on creating value for users. It’s good practice. But as long as I work at this job, I won’t have enough left over at the end of the day to seriously invest in anything else."

But further to that - there's so much in the world to do. Doing work is part of that, but if it's the only thing in your life, then "All work and no play makes Taryn a dull girl". Sure, some coding is play - and I do love to code my own stuff too... but I like so many things, that if I focus my off-hours on coding, I'd lose sight of all the rest that life has to offer.

So sure, I choose to spend some time coding, but also a lot of time reading books while sipping great coffee, getting together with interesting bunches of people, travelling to far-flung places, writing up my travels or even writing fiction (however badly), taking photographs, attending medieval feasts and wars, making home-brew mead, or even just walking outside and enjoying the sun.

Life is an adventure... make the most of it.

Wednesday, 13 August 2008

Tact filters for geeks

I just stumbled into this great article. It described the difference between how geeks vs "normal" people filter their social discourse for tact. It makes a lot of sense, and explains my frustration with non-geeks not "talking straight"... as well as, no doubt, the shocked reactions of these same people when I do. Interesting reading.

Tuesday, 12 August 2008

Updating gem to 1.2.0 on Ubuntu

Like several others, I tried to gem update today and failed. It gave me weird error telling me it couldn't find the update gem in the repository:

%> sudo gem update --system
Updating RubyGems
Bulk updating Gem source index for: http://gems.rubyforge.org/
Updating rubygems-update
ERROR:  While executing gem ... (Gem::GemNotFoundException)
    could not find rubygems-update locally or in a repository

Googling gave me a fair few other links to people with the same issue. The main one seems to be listed under this ruby-forum topic which started as the release-notes for the Gem update to version 1.2.0. People that had problems updating to this gem listed their steps-to-reproduce in the replies below, and I tried all the suggestions.

sudo gem update --system was the original command I tried and it gave me the error as above. All the fora on the topic insisted that the workaround is to run: gem install rubygems-update -v 1.1.1, but this just gave the same error. I did note that most of these people were on various flavours of Mac, and the pleas for help from those on Ubuntu were drifting away into the sky unheard...

One forum user said he tried the above from his gem directory and that worked for him...
but not for me...

I even tried reinstalling ruby/rubygems via apt-get, but the former was latest version, and the latter doesn't exist...

I was about ready to nuke the site from orbit... and reinstall from scratch and went to the rubyforge page for rubygems. This lists the source code, but I also noticed it had individual "upgrade" gems available for download. This saved me from my previous scorched-earth plan, and eventually resulted in success. For future reference:

  1. copy rubygems-update-1.1.0.gem to a local directory
  2. then run: sudo gem install rubygems-update-1.1.0.gem
  3. repeat with rubygems-update-1.2.0.gem
  4. then run update_rubygems

Now it's all fine and sudo gem update --system happily fetches my new gems as before.

Thursday, 7 August 2008

Explaining anti-virus suckage to the tech-averse

Being the most IT-savvy member of my extended family, I'm often called upon for random tidbits of IT information. This is regardless of whether or not I'm actually likely to know anything about the subject at hand... because obviously I'm "into IT" so I should know how to network an ancient dot-matrix printer to an ageing pentium... (clue: it's probably not working for you because it's time to replace the hardware).

The one I dread most, however, is "why is my internet so slow"... the obvious candidate being "have you considered the possibility that you might have a virus?" - which is usually met with the same sort of scandalised reaction reserved for asking somebody whether they have a venereal disease.

Surely computer viruses are something that only happens to *other* people? After all, we're good, clean types that don't take part in *that* sort of behaviour... why would we have something so dirty as a virus? besides... which we always use a condom anti-virus software... so there's no possible way we could have anything right?

Depending on the tech-savviness of the audience I'll then launch into an explanation as to why anti-virus software doesn't always work... This generally goes down like a lead balloon. With the usual response being "but I have the latest <insert expensive AV software name here> and keep the signatures fresh up-to-the-minute!"

The problem being that I know this subject only enough to know that even with the latest virus signatures, it's not perfect. I don't know it well enough to really describe in laymans' terms *why*.

Today I came across an article on Coding Horror about how blacklists don't work. but it also neatly segue's into the reason why anti-virus software is always going to be one step behind the competition. I think it summarises the issue nicely - far better than I have ever been able to describe.

I know I could never give this to my grandma, but I think it's a step in the right direction - and I can probably point my partially-tech-savvy aunts and uncles in that direction from now on.

Tuesday, 29 July 2008

icon_button_to_remote

Following from my earlier post on making icon buttons, I needed to add AJAXy goodness to the edit actions on one of our index pages.... but I still wanted cute little icons to make them look pretty.

So I've updated the code to incorporate an icon_button_to_remote method. Note that icon_button_to has also been modified, but it still depends upon the get_icon_path and fixed_button_to methods from the previous post.

  # generates appropriate image-buttons, given an "icon type" and the usual
  # button options.
  def icon_button_to(icon_type, icon_label, form_opts = nil, button_opts = {})
    unless form_opts.blank?
      button_opts.merge!(:type => 'image', :src => get_icon_path(icon_type), 
                       :alt => icon_label, :title => icon_label)
      the_icon = fixed_button_to(icon_label, form_opts, button_opts)
    else
      # no form was passed in - we want a non-linked icon.
      the_icon = image_tag(get_icon_path(icon_type), :width => 16, :height => '16', 
                       :alt => icon_label, :title => icon_label)
    end
    content_tag 'div', the_icon, :class => 'icon_button'
  end
  # add a remote AJAX-linked icon button
  def icon_button_to_remote(icon_type, icon_label, remote_options = {}, button_options = {})
    link_to_remote(icon_button_to(icon_type, icon_label, nil, button_options), remote_options)
  end

The example would be for an index page with a list of Widgets. Each one has an "edit" button using the code below. The page also has a section for entering a new widget - which would be replaced by the "edit this widget" template when you click on the icon.

<!-- In the template at the top of the list -->
<%= w_path = new_widget_path() # url_for is slow
    icon_button_to_remote(
         :new, 'Add new widget', 
         :update => 'new_widget_div',
         :url => w_path,
         :method => :get,
         :html => {:href => w_path},
         :complete => "Element.hide('edit_widget_div'); 
                          Element.show('new_widget_div'); 
                          new Effect.Highlight('new_widget_div');"
        ) -%>

<!-- and next to each widget -->
<%= ew_path = edit_widget_path(@widget) # url_for is slow
    icon_button_to_remote(
         :edit, 'Edit this widget', 
         :update => 'edit_widget_div',
         :url => ew_path,
         :method => :get,
         :html => {:href => ew_path},
         :complete => "Element.hide('new_widget_div'); 
                          Element.show('edit_widget_div'); 
                          new Effect.Highlight('edit_widget_div');"
        ) -%>

<!-- Later in the template... -->
<div id="new_widget_div">New widget form would go here</div>

<div id="edit_widget_div" style="display:none;">Edit widget form will be populated here</div>

You also have to update your Controller code for the new and edit actions to accept js and respond with no layout - otherwise your div gets populated with the entire edit/new page :P

The best way is to move the meat of the page into a partial (which can also be loaded in the edit.rhtml template) callable from the action thus:

  def edit
    respond_to do |format|
      format.js { return render(:partial => 'edit', :layout => false) }
      format.html # edit.rhtml
    end
  end

Saturday, 26 July 2008

Best vs Good enough

I can't remember which I've heard - I think it was "good enough is the enemy of best", but it could as easily be the other way around. It's a bit confusing as I've definitely heard conflicting stories.

We are the best!

From one perspective, you want your new startup to be the best - because, in a lot of ways, that is what will distinguish you from your competitors. The difference in market-share between Google and Yahoo! is an obvious example. So is the difference between Amazon and... whoever comes second after Amazon.

Clearly it pays big to be the best in your niche. You need to be the best. "Good enough" not only won't cut it, but will relegate you as indistinguishable from the swamp of mediocrity in which all the other companies float.

In this sense, "good enough" is the enemy of "best".

Good enough. Ship it!

And yet, you need to *ship*, because if you don't have anything to ship, then the customers will never hear of you. This follows from the principle of "ship early, ship often". It's all too easy to get caught in analysis paralysis, endlessly perfecting what you've got without ever putting it "out there" for real users to get their hands on.

In that respect, "best" is the enemy of "good enough".

So which is it?

As with everything, you need a strategic balance.

You don't need to be best at *everything*. You'll wear yourself out trying. You just need to be best at what you've chosen for your market differentiator. Perhaps you're aiming to provide the best features for power users, or most comprehensible features for beginners, or even the nicest customer service. Whatever you decide on, use that focus to drive your quest for perfection. You can push for the best in this one area and let other areas be "good enough".

If you try to swallow the elephant whole, you'll never get it down. Instead, keep focussed on your core competencies, and don't worry *too* much about perfection in the other areas. You can always make them better in your 2.0 release.

Tuesday, 22 July 2008

More thoughts on Rails-doc 2.0

I'd been re-contemplating the Rails-doc 2.0 site and had begun to write a post about the minor annoyances I found, when suddenly up popped a new post by Jeremy on rubycorner.

I guess I'm not the only person whose experience was akin to: "OMG wow! *click* *click* hang on..."

My annoyances from rails-doc 2.0

There's not a lot wrong with rails-doc 2.0, the following are merely annoying.

Click depth

One of the benefits of the previous rails-doc site is that it's all there at your fingertips all the time. Rails has a deep hierarchy and now the only way into it is to search randomly. This can be fun - the auto-prompter is pretty good... as long as you know exactly what you're looking for. But on the old site, if you can't quite recall what you're looking for, you can browse down to the list alphabetically until you find it. With the new site you have to delete your old search and continue to try other names. Search helps, but having a browse-alternative is also helpful.

Another problem with deep click-hierarchies is that you can't get as much information at once. On the original site, all the methods are on the same page - and all you need do is click the anchor or scroll down to them to get a feel for the whole class. You don't have to keep clicking through and back if you want to look at each of the methods.

Searching code

...requires code-sensitive search. If I type "default_error_messages" it means something entirely different to a search with the words "default error messages" somewhere in the body.

version consistency

If I chose version 1.2.6 for the ActiveRecord class, chances are pretty strong that I'll want to keep looking at version 1.2.6 when I click through to the methods... or anywhere else, for that matter. I think there's a strong case for persisting the chosen version.

Is there really a problem?

As you might notice, these are just GUI annoyances not real issues, as such. So is there really a problem?

Jeremy points out that user-added notes are all well and good, but what we need is for the actual real rails-doc to be updated. That isn't going to happen here (at least with the current incarnation). His proposed solution sounds great - and I'll look forward to its release. Perhaps he should team up with the rails-doc.org folks ;)

But has it got anything right?

Jeremy actually stated this succinctly:

"If I’d known that all we needed was a good looking RDoc app, I could’ve fixed the problem a while ago."

Lets face it, we're geeks and we love shiny new toys... Rails being the epitomy of the shiny new thing. So yes, rails-doc 2.0 got one thing absolutely right - it provides a pretty interface that is easy and fun to explore. That's often all it takes folks like us to start contributing - so they got that right.

Monday, 21 July 2008

Rails doc 2.0

Finally some decent documentation for rails!

Like a number of rails enthusiats, I read Jeremy's blog post with a bit of trepidation. Mainly about the decline of rails blogging, it also points out the distinct lack of decent documentation for Rails... and firmly points a finger at all of us for not contributing to making it better. The worrying point is the conclusion that rails will decline in popularity due to a lack of available, comprehensive documentation.

Luckily, Rob Anderton has put my fears to rest. Rob's post has pointed me at the new Rails doc project. I've only just begun to investigate the site, but I can already tell it's far more useful than the standard Rails API I've been relying upon.

For one thing, it links the documentation to the version of rails you're using. This alone would have saved me a few hours of annoyance trying to get a method to accept parameters that are only available in a more current version than what's on our production server. :P

It also has a really neat keyword-search interface that prompts you with reasonable guesses, and a way for the community to add to the documentation (yay)!

All in all, it shows a lot of potential. So lets get cracking on building the doco up to a standard that developers in other languages have come to expect.

Sunday, 20 July 2008

One of Pradiptas children

Ok, so I put this into a comment on a previous post, but the phenomena has continued to spawn new children, so I thought I'd add to it.

Two days ago I opened my inbox to find it bursting with the results of an epic cascading thread. An overzealous recruiter had made a massive faux pas. He spammed 416 random rails-related folks with a Rails job offer... but put all their email addresses into the CC field.

The list ran the gamut of Rails, from those who'd barely touched it once, through the average rails developers (such as myself) right up to community luminaries such as DHH and Ryan Bates (of RailsCasts fame).

Reverse flash mob

After a few responses using reply-all, and the typical "don't use reply-all" (using reply-all), suddenly the mailing list became self-aware. Despite being accidentally gathered together in one place through an epic mishap, we recognised that we had a common interest... and a captive audience.

Most members of the group took advantage of this to chat about upcoming Rails conventions and what work we all did, and where.

Within two hours a google group (pradiptas-rolodex) was formed. Swiftly following were:

Sadly the wiki page keeps being deleted...

Anyway, it's been fun.

Highlights from the original thread

The post that began it all

I have a couple of Ruby on Rails position, wanted to know if you are
interested?


Max Archie
Technical Recruiter
Prodigus Source

The post that unified us as a community.

> PLEASE TAKE ME OFF THIS CRAZY EMAILING LIST!!!

Please KEEP ME ON THIS CRAZY LIST!!!

You all seem like pretty interesting folk, and perhaps serendipitously
we've all been added to the same ruby on rails marketing schloc. Even
more interesting to me, is that both my friend Harper and I are on it,
so hell they guy at least has good taste.

So in the spirit of fun email lists

Hi, I'm Anders Conbere, I Hack, run a co-working space in Seattle, and
love me some erlang and python.

What do you guys think of starting a google group I was thinking

"Igotfuckedinbanglalore"

~ Anders

P.S. I've thoughtfully removed our spammer from future communication

The best repsonse to the ad

Hi Max,

I am a recruiter who has an opening for a top-tier recruiter such as
yourself.  I need someone who can unwittingly set off the fury of *at least*
400 people, while ignoring all basic email etiquette.  Would you be
interested?   If not, do you know anyone else who is currently looking for
such an opportunity?

Sincerely,
Thanks for the mile long email thread out of freakin nowhere

Best representation of pradipta's mistake in code

PS:  Sorry for crashing anyone's iphone, but I couldn't resist replying.

module Recruiter
   class Email
      def initialize(site = "http://workingwithrails.com/")
          @recipients = Recruiter::EmailHarvester.harvest(site)
      end

      #
      # TODO: Really should change this to do a BCC instead of a TO
      # so 416 people don't get spammed and start an internet phenomenon of
      # replying to this message until the entire internet crashes.
      #
     def send_email(to_recipients, message_body =
File.read("really_bad_generic_email_body.txt") )
        @message = EmailMessage.new
        @message.to = to_recipients
        @message.body = message_body
        Mailer.send(@message)
     end

   end
end