For a while, I have felt that the following is the correct way to improve the mass assignment problem without increasing the burden on new users. Now that the problem with the Rails default has been brought up again, it's a good time to revisit it.
When creating a form with form_for
, include a signed token including all of the fields that were created at form creation time. Only these fields are allowed.
To allow new known fields to be added via JS, we could add:
<%= f.allowed_fields "foo", "bar", "baz" %>
The first strategy will not full satisfy apps that support a lot of HTTP requests that do not come from forms generated by Rails.
Because accessible fields is usually a function of authorization, and is not global, we should move allowed fields into the controller. The basic idea is:
class PostsController < ApplicationController
# attributes can be marked accessible for the entire controller
attr_accessible :foo, :bar
def create
# mass assignment can also be done on an instance basis
# this can be used to override the class defaults
attr_accessible(true) if user.admin?
...
end
end
I would imagine that Rails authorization frameworks like CanCan could add sugar to make this even easier in common cases.
The core problem with Rails mass assignment is that attribute protection is an authorization concern. By moving it to the controller, we can have smart defaults (like signed fields in form_for
) and in more advanced cases, make it easier to decide what fields are allowed on a per-user basis.
By moving it into the correct place, we will probably find other nice abstractions that we can use over time to make nice defaults for users without compromising security.
I can see two trends here: one repelling that all validation goes on the model, for those who want to make it dependent on the context, and one repelling that all params sanitation goes on the controller, for those who want to make it dependent on the internals of the business logic. The solution here offered has been a new layer taking care of this issues, á la Dyango Forms, but this could lead to view logic messed up with everything else, this layer becoming a kind of glue between everything.
I indeed support the introduction of a new layer in Rails (Conductor/Presenter/Mediator/Decorator/Template), but adding a new perspective. I advocate a ResourceAccessor, which not only stays between a model and its controller, but besides incarnates the concept of Resource (which can be understood as made of parts of one or several related models). This resource should relate each attribute to its respective model's field and should make validations depending on the user's authorization level. It would be called by the controller on GET actions and passed onto the view, which could automatically rely on the ResourceAccessor's field definitions to display the appropiate type of field (either for scaffolded display or in forms). It would receive the params from the controller, sanitize and handle them out to the respective save methods of each model, usually inside a transaction. Mass assignment would become impossible, since only explicitly defined (and eventually authorized) fields could be set.
This approach solves said issues of mass assignment, bloated models, and repetitive view declarations.
It also integrates the new Rails 4.0 verb PATCH and with the new meaning of PUT together, giving full sense to the creation of a method
replace_resource
, which could be meaningfully applied to such ResourceAccessor instead of the currentupdate_attributes
whenever a full replace is meant (see my take on that).Feel free to elaborate on the concept. Just my two cents.