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.