Using wrap_parameters to allow a non-ActiveRecord parameter

Rails 4 introduced strong parameters:

With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means that you’ll have to make a conscious decision about which attributes to allow for mass update. This is a better security practice to help prevent accidentally allowing users to update sensitive model attributes.

There are some caveats to using them, and I found this article helpful in understanding what’s going on behind the scenes: http://patshaughnessy.net/2014/6/16/a-rule-of-thumb-for-strong-parameters

One issue I have come across a couple of times now, which I would like to describe here, concerns explicitly permitting a parameter in addition to the attributes of an ActiveRecord model.

In one case, my model included a field, let’s call it code, composed of two parts: ABC.123. It was convenient to store this as one field in the database, but I wanted to separate it into two input fields in the UI, only one of which could be modified. So to handle this I wrote two readers and a setter on the model. Here’s what I mean (leaving out handling of edge cases):

def code_letters
  code.split('.').first()          # e.g. ABC
end

def code_numbers
  code.split('.').last()           # e.g. 123
end

def code_numbers=(value)
  write_attribute(:code, "#{code_letters}.#{value}")
end

Next, I added an entry to the parameter whitelisting in the controller:

def model_params
  params.require(:account).permit(:other_fields, :code_numbers)
end

This, however, was not enough. The value was POSTed fine, but was not available in the controller after calling model_params. There was no easy way of figuring out what to do, but I eventually came across a solution: wrap_parameters.

This takes the form of either an :include list, or an :exclude list; I couldn’t just specify one additional parameter to be let through. So I specified them all:

class AccountsController < ApplicationController
  wrap_parameters include: Account.attribute_names + [:code_numbers]
end

ParamsWrapper actually uses the model’s attribute_names method by default, so I simply specified the default and tacked on what I needed to add.

Of course, there are always better solutions, and if this type of abstraction becomes more common I would introduce a layer around the model which would be the subject of the form’s POST, then translate each attribute onto the model as needed.

This entry was posted in Technology. Bookmark the permalink. Trackbacks are closed, but you can post a comment.

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*
*

Time limit is exhausted. Please reload the CAPTCHA.