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

May 8th, 2009 by Casey Dreier Leave a reply »

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.

Advertisement

Leave a Reply

You must be logged in to post a comment.