Archive for May, 2009

So I Gave This Talk….

May 8th, 2009

I was fortunate enough to have the opportunity to give a talk about our recent work with the SHA LaunchPad redesign and Rails project for the regional conference of the Higher Education Web Professionals Organization, which was hosted here at Cornell by the tireless Jason Woodward.

It was a great conference, with some very interesting talks. The slides for mine, which mostly deal with the concept and philosophy behind our LaunchPad site (which is still not live, sorry), are on SlideShare, so you can see (not hear, unfortunately) Building a New LaunchPad From the Ground Up.

Did you know that POST variables are always passed as strings?

May 8th, 2009

Because I didn’t.  Or at least I didn’t think much about it. And dammit, I should’ve.

As you can probably tell, I spent a long time trying to figure out a very simple problem.  This is one of those posts that is inspired by the (vain) hope that maybe, just maybe, I can spare someone else a few hours of confusion so they can do better things, like go outside and eat ice cream or something.

Here’s the (simplified) situation.  The user can select a variety of widgets via checkboxes in a form.  The information gets passed as widget ids to the controller, which triggers a method that updates the assocation of a user’s widgets:

### WidgetsController ###
 
def updatewidgets
  @user.update_active_widgets(params[:widget_ids]) if request.post?
  redirect "/"
end
 
### User Model ###
has_many :widgets
 
def update_active_widgets(new_widget_ids)
  current_widget_ids = self.widget_ids
 
  # return an array of widget ids that the user no longer wants #
  unbound_widget_ids = current_widget_ids - new_widget_ids
 
  # Delete all unbound widgets... save new widgets....etc...etc...#
 
end

Ok, who can see why this deletes all of the user’s widgets every time we run this script? Hint: I already told you in the title of this post.

Since the params[:widget_ids] is coming from a POST request, every item in the array is going to be a string, even if it was listed as a number. So, if the user selected widget_ids of 1, 2, and 3, the incoming params[:widget_ids] array would look like this:

params[:widget_ids] = ["1","2","3"]

In my method, update_active_widgets, I take a set difference between two arrays to find which of the current widget_ids associated with the user are no longer selected by the user. But the when I pull out the current widget ids using self.widget_ids, I get an array of integers:

self.widget_ids = [1,2,4,6]

So when I take the set difference between an array of strings and an array of integers, Ruby understands them to be totally different, “1″ != 1. So what I was doing was this:

unbound_widget_ids = [1,2,4,6] - ["1","2","3"]
# returns the original set [1,2,4,6]

Thus, I’ve just told Rails that every current widget_id is unbound, and should be deleted. Since Rails is smart enough to automatically convert a number-string to a number in ActiveRecord, there was never a problem adding widgets. My unit tests were even passing fine, because I kept entering my widget_ids as integer arrays.

Obviously, the solution is to convert everything to an integer before doing a set difference, something like:

new_widget_ids = new_widget_ids.collect {|w_id| w_id.to_i}

Now, when I do the set difference, what happens is this:

unbound_widget_ids = [1,2,4,6] - [1,2,3]
# returns the difference set [4,6]

And that fixes it. Widgets with ids 4 and 6 get removed from this user, and we go ahead and add the widget with id 3. Finally.

Regardless, POST values get passed as strings. I should really know things like this by now.