Archive for April, 2007

You don’t know what you’ve got …

Thursday, April 26th, 2007

My personalised Google home page has vanished.

I’m sure Google is working hard to fix whatever the problem is and I can’t complain too much as it’s free (although Google does get to learn all about my favourite sites).

But it’s weird how dependent I had become on that list of bookmarks and feeds. I’m almost lost without it.

The Cost of Change

Wednesday, April 25th, 2007

There’s a real problem with the way that Rails reduces the cost of change.

You see, it’s a good thing. Getting started is often the hardest thing to do so not having to make big decisions up-front can let you build up momentum without spending days staring at a blank sheet of paper.

But it’s also a bad thing. Instead of moving forwards you can, if you’re not careful, spend ages going back and revisiting your old code (because you know how to make it that little bit better).

I’ve now set myself a rule only to rewrite an existing RHTML template into Markaby if (and only if) I was going to be editing it anyway. Not just because I happened to be passing.

Damn you Martin Fowler!
Damn you refactoring!
Damn you unit tests for making my life easier!

form_test_helper and AJAX (update)

Tuesday, April 24th, 2007

Jason Garber has updated his excellent form_test_helper plug in to support XmlHttpRequests.

Rather than the clumsy code I showed before you can now test your Ajax form with a simple and elegant:

# test in non AJAX modethingy = Thingy.find 1get :edit, :id => thingy.idassert_response :success

submit_form do | form |  form['thingy[field]'] = 'wotsit'endassert_redirected_to thingy_path(thingy)thingy.reloadassert_equal 'wotsit', thingy.field

# test in AJAX modexhr :get, :edit, :id => thingy.idassert_response :successassert_rjs :replace_html, "thingy_panel"

submit_form :xhr => true do | form | form['thingy[field]'] = 'doodah'endassert_response :successassert_rjs :replace_html, "thingy_panel"assert_rjs :visual_effect, :highlight, "thingy_panel"thingy.reloadassert_equal 'doodah', thingy.field

Note: the fantastic example above also uses Kevin Clark’s ARTS plug in for testing the RJS responses.

So what are you waiting for? Fire up that subversion client and get downloading.

Thanks Jason.

Can you use the same controller in two places at once?

Monday, April 23rd, 2007

I’m currently wrestling with a problem. My application has Attendees that either belong to PrivateCourses or Bookings. It uses a polymorphic association for this: has_many :attendees, :as => :attendable.

However, in both cases the UI is pretty much identical. So I figured I could set up routes like so:

map.resources :private_courses do | private_course |  private_course.resources :attendees, :name_prefix => 'private_course_'endmap.resources :bookings do | booking |  booking.resources :attendees, :name_prefix => 'booking_'endmap.resources :attendees

This gives me three options: either reference the attendee in the context of a PrivateCourse private_course_attendees_path(@private_course), in the context of a Booking booking_attendee_path(@booking) or all on its own attendee_path(@attendee). I was thinking I could examine the supplied parameters to figure out what the Attendable should be:

if !params[:booking_id].blank?  @attendable = Booking.find params[:booking_id]elsif !params[:private_course_id].blank?   @attendable = PrivateCourse.find params[:private_course_id]endif @attendable.blank?   @attendee = Attendee.find params[:id]else  @attendee = @attendable.attendees.find params[:id]  end

Looks good so far.

But it turns out my view code is turning into a conditional nightmare. Take the “new” form:

if @attendee.attendable.is_a?(PrivateCourse)  form_for :attendee, :url => private_course_new_attendee_path(@attendee.attendable) do | form |   ...  endelse  form_for :attendee, :url => booking_new_attendee_path(@attendee.attendable) do | form |    ...  endend

Of course, not only is this ugly but there needs to be code in the controller to set up the new Attendee’s Attendable. In which case I should just make the controller look like this:

@attendee = @attendable.attendees.buildif @attendable.is_a?(PrivateCourse)  render :action => 'new_attendee_for_private_course'else  render :action => 'new_attendee_for_booking'end

Yet as I went through this, I kept thinking that every single action needs the same conditional logic – actual form definitions aside, there is actually nothing that is shared between the two conditions.

So, my answer, barring a flash of inspiration, is “No, you cannot use the same controller in two places at once”.

Now I need to rework my body of code to have two controllers – PrivateCourseAttendees and BookingAttendees that both make use of a set of shared partials, probably in an Attendees folder.

Markaby Gotcha

Monday, April 23rd, 2007

One thing to watch out for, when using Markaby:

  link_to_remote 'whatever', :url => some_path(@something), :before => do_this, :complete => do_that

will not work.

Instead you need:

  link_to_remote 'whatever', :url => some_path(@something).to_s, :before => do_this, :complete => do_that

Spot the difference? The :url parameter has a .to_s appended on the end. Otherwise Markaby intercepts the call and eats the output.

Rails 1 – 0 Smalltalk

Friday, April 20th, 2007

One thing has struck me over my recent Rails-beautification tangent.

My fictional resource-routes example:

  map.resources_called(:course_templates,     :adding => [      GetMethod.called(:build_courses, :on => :members),       PostMethod.called(:do_build_courses, :on => :members)    ],     :nesting => map.resources_called(:course_details)  )

looks better than the fictional Smalltalk example.

Likewise my (real) Markaby view example looks better than the (real) Seaside/Smalltalk example.

Things are not so bad in the world of Rails …

markup.become(:beautiful)

Thursday, April 19th, 2007

In my brief exploration of Seaside one of the points that caught my eye was that the components render themselves in HTML.

 html table:    [html tableRow:        [html tableData: [html bold: 'Name'].         html tableData: person name].     html tableRow:        [html tableData: [html bold: 'Age'].         html tableData: person age]]

Two points: firstly – I read in one interview (sorry, can’t find the link) that this leaves the designer to control the CSS and the programmer to control the HTML. This immediately struck me as a good thing – for the HTML to be properly CSSable it needs to be well-structured and semantically rich. Which you should do anyway but it’s easy to forget. And secondly, that code snippet above looks great compared to your typical .rhtml file (I think it’s something to do with the angle brackets in HTML which make it hard to read).

So, in the interests of making my application more beautiful, I started investigating how to do something similar in Rails. Which lead me to Markaby – Markup in Ruby. Install the plug-in and, instead of creating rhtml views you create mab views.

Which look like this:

div.contents! do  error_messages_for :booking  form_for :booking, :url => booking_path(@public_course, @booking).to_s, :html => {:method => :put} do | form |    h3 "Customer Details: "    p do       label "Contact: "      collection_select :booking, :contact_id, @contacts, :id, :name    end    p do      label "Notes: "      text_area :booking, :notes, :rows => 5    end    p.explain "Add any arbitrary notes here"    p do      blank_label      submit_tag "Save"      text " or "      link_to "cancel", booking_path(@public_course, @booking), :confirm => 'Are you sure?'    end  endend

Doesn’t that make you feel good? Especially the p.explain (which outputs a P tag with the class “explain”) – immediately you can see how semantic markup becomes integral to your views.

I did have a few problems with the helper methods – I had to set @output_helpers = false and I switched from using the form.text_field :method helpers to the old-style text_field :object, :method helpers (which also meant losing my labelled_form_builder). But it’s a small price to pay for such superior looking markup.

Single Table Inheritance and Virtual Controllers

Wednesday, April 18th, 2007

I have the following models: a “base” class, Event, with two descendants, PublicCourse and PrivateCourse. Although, data-wise, they are pretty similar, behaviour-wise they are quite different. So Single Table Inheritance (STI although that could mean something completely different) is the way to go. (The alternative, of course, is a mix-in – which in most cases is far more preferable to inheritance).

I also have a public_courses_controller and a private_courses_controller. As each needs their own user-interface, distinct from the other.

However, there are places in the user-interface where I am showing a list of Events and I wanted to link to them – either private or public. I started by writing a helper:

def event_link(event)  event.is_a?(PrivateCourse) ? link_to(h(event.title), private_course_path(event)) : link_to(h(event.title), public_course_path(event))end

Meaning I could write views like so:

  @events.each do | event |    event_link(@event)  end

However I didn’t like it – the way events used a totally different link syntax to every other entity in my application.

So instead I created a “virtual” controller, events_controller, that has a single show action.

def show  @event = Event.find(params[:id])  @event.is_a?(PrivateCourse) ? redirect_to(private_course_path(@event)) : redirect_to(public_course_path(@event))end

Now my view looks like

  @events.each do | event |    link_to(h(event.title), event_path(event))  end

The upside – my views are consistent, regardless of entity. The downside – if you click that link it forces a database hit and a redirect. However, I’m not expecting 11000 requests per hour, let alone 11000 requests per second (this is a commercial service, not a social networking phenomena) – so I’ll deal with the scaling issues if and when they happen

Stay Beautiful

Tuesday, April 17th, 2007

Mr Hansson has been winding people up recently, with his Twitter Controversy. But one of his more polite recent posts is about Seaside – a Smalltalk web framework that uses stateful objects on the server to allow a modal-style flow of control within your web application. One component receives a callback as the user clicks a link, it calls into another component and sits and waits until the second component returns control to the first – just like calling a subroutine.

I’ve made no secret of my love of Smalltalk – it’s what attracted me to Ruby and Rails in the first place. There’s a beauty about Smalltalk code (although please forgive any typos in the code below as I’ve not done any for a while) that you rarely see anywhere else, although Rails has it in places. I could go on for hours about:

  under21s:= people collect: [ person | person under21 ].  

Is that better than:

  under_21s = people.collect do | person |     person.under_21?   end

Not much in it. Ruby has the question mark – I like that as it makes the code read better. But Smalltalk has less superflous punctuation to make it read better still. And I love the full-stop to finish the sentence.

How about:

  nail needsHitting ifTrue: [ nail hitOn: #TheHead with: aHammer ].

Versus:

  nail.hit(:on => :the_head, :with => hammer) if nail.needs_hitting?

Ruby has the if at the end of the sentence, which I really like – and it’s even better when using unless. And Smalltalk has its slightly weird “everything is an object or message” ifTrue: method on the true and false instance variables that lead to something that is a bit less readable than the Ruby. But Smalltalk doesn’t need the parenthesis. And blocks are passed as normal objects (not weird add-ons that may or may not be tacked on to the end and sometimes implicitly converted to Procs). And if is not a reserved word.

Recently I’ve noticed the Smalltalk creeping into my Ruby style. Things like:

  validate(course, :against => template, :on => this_date)

Which is reasonable. But the brackets just kill me.

And then I had to write this:

  form_for(:course_template,     :url => course_template_path(@course_template),     :html => {:method => :put} do | form |     # form.stuff  end

It’s awful (yeah, yeah, I need SimplyHelpful).

What about, in config/routes.rb:

  map.resources(:course_templates,     :member => {:build_courses => :get, :do_build_courses => :post}) do | course_template |    course_template.resources(:course_details)  end

They really ought to read:

  courseTemplates:= map resourcesCalled: #CourseTemplates;    withAnAdditional: #GetMethod :called #BuildCourses :on #Members;    withAnAdditional: #PostMethod :called #DoBuildCourses :on #Members;    nesting: (map resourcesCalled: #CourseDetails).

Note: the nested call to resourcesCalled is actually sent to map, not to a nested object as in the Ruby original.

A literal translation would be:

  map.resources_called(:course_templates).with_an_additional(    :get_method, :called => :build_courses, :on => :members).with_an_additional(    :post_method, :called => :do_build_courses, :on => :members).nesting(      map.resources_called(:course_details)    )

The semi-colon chains method calls together (assuming each call returns the original return value from map.resources_called. But it’s really really ugly in Ruby.

The closest I can think of for this is:

  map.resources_called(:course_templates,     :adding => [      GetMethod.called(:build_courses, :on => :members),       PostMethod.called(:do_build_courses, :on => :members)    ],     :nesting => map.resources_called(:course_details)  )

which is a lot better but still pretty ugly (nested brackets – urgh).

Of course, map.resources is probably an instance of DHH’s syntactic vinegar – I’m sure that in his mind you should never add extra calls as it breaks the nice clean REST architecture. Well tough. I think myserver.com/course_templates/23;build_courses describes what I want very succinctly.

It’s definitely something that’s on my radar now … I want my Ruby and Rails code to be beautiful. Otherwise I may just be looking at Seaside in more depth.

form_test_helper and AJAX

Monday, April 16th, 2007

I have been using Jason Garber’s form_test_helper plugin. Basically, if you’ve ever had it where you rename a parameter in a form – change your test accordingly (post :action, :id => 1, :name => ‘George’ becoming post :action, :id => 1, :first_name => ‘George’) and forget to update your view, form_test_helper is for you. Instead you ask your controller to render your form and then call submit_form, supplying it the parameters you want. It parses your form, looks for the fields and then submits it to the Url embedded in the form tag. So the above would fail unless you actually had a field named first_name in your UI. Anyway, I thoroughly recommend it for avoiding embarrassing bugs.

However, it does not work with xhr requests. So, in Jason’s absence (he’s away at the moment), I’ll show you the changes needed here. It’s nowhere near complete but it does the trick for me.

Grab the plugin and edit vendor/plugins/form_test_helper/lib/form_test_helper.rb.

Around line 444-ish:

  # Alias for select_form when called with a block.   # Shortcut for select_form(name).submit(args) without block.  def submit_form(*args, &block)    @ix_xhr = false    internal_submit_form(*args, &block)  end

  # As submit_form but sends an XHR request  def submit_form_xhr(*args, &block)    @ix_xhr = true    internal_submit_form(*args, &block)  end

  def internal_submit_form(*args, &block)    if block_given?      select_form(*args, &block)    else      selector = args[0].is_a?(Hash) ? nil : args.shift      select_form(selector).submit(*args)    end  end

And around line 480-ish:

  def make_request(method, path, params={}, referring_uri=nil)    if self.kind_of?(ActionController::IntegrationTest)      self.send(method, path, params.stringify_keys, {:referer => referring_uri})    else      params.merge!(ActionController::Routing::Routes.recognize_path(path, :method => method))      if params[:controller] && params[:controller] != current_controller = self.instance_eval("@controller").controller_name        raise "Can't follow links outside of current controller (from #{current_controller} to #{params[:controller]})"      end      self.instance_eval("@request").env["HTTP_REFERER"] ||= referring_uri # facilitate testing of redirect_to :back      if @is_xhr        self.xhr(method, params.delete(:action), params.stringify_keys)      else        self.send(method, params.delete(:action), params.stringify_keys)      end    end  end

As I say, it’s a bit of an ugly hack but it works for me. I will submit it to Jason once I have tidied it up.