Strong parameters

The migration from protected_attributes (attr_accessible/protected) to strong parameters moves whitelisting of model attributes from the model to the controller, in line with Rails.

What will change in Foreman:

  1. attr_accessible will be removed from all models
  2. Controllers will be updated to call the permit method on params to whitelist attributes
  3. The plugin API will allow plugins to whitelist custom attributes on core models
  4. A basic (deprecated) compatibility method will be added for plugins that add custom attributes to core models
  5. A helper will be added to de-duplicate code between UI/API controllers and nested models

Plugin authors will need to migrate too:

  1. For plugin models, either update controllers to use Rails' permit or Foreman's ParameterFilter, or add a dependency on the protected_attributes gem temporarily
  2. For custom attributes on core models, replace attr_accessible calls with parameter_filter in plugin registration between now and the end of the deprecation period

Changing a model from attr_accessible to strong parameters

This example will use a simple model with three attributes:

class Example < ActiveRecord::Base
  attr_accessible :name, :description, :foo_id, :foo_name
end

If the model has controller(s), they will currently use params[:example] to create and update the model through mass assignment. Create a new helper method in the controller that calls the permit method on params:

class ExamplesController < ApplicationController
  private

  def example_params
    params.require(:example).permit(:name, :description, :foo_id, :foo_name)
  end
end

Then update create, update etc to call example_params instead of params[:example].

If you have both UI and API controllers, the params method can be put into a concern to avoid duplication, e.g. ForemanExample::Controller::Parameters::Example.

If the model uses either Taxonomix or NestedCommonAncestry concerns or subclasses any of Foreman's models, then you should use ParameterFilter like Foreman does, as Foreman::Controller::Parameters concerns are also provided.

Changing core extensions to use the plugin API

When extending a Foreman model, instead of calling attr_accessible, whitelist the additional attribute during plugin registration:

Foreman::Plugin.register :foreman_example do
  parameter_filter CoreExample, :name, :description, :foo_id, :foo_name
end

For plugins that provide models extended by other plugins, consider using the ParameterFilter class from Foreman to get the same features.

Other notes

  • Models that don't have controllers can probably have attr_accessible removed entirely. If they accept user input through mass-assignment outside of controllers, consider using ActionController::Parameters directly.
  • Attributes that have array values should be listed at the end of the permit line (as a hash) in the form :example_ids => []
  • With accepts_nested_attributes_for, keys for inner hashes must be specified or use the keep_param helper to skip validation completely (use carefully). Optionally use ParameterFilter which helps de-duplicate whitelists between the main resource controller and nested hashes.
  • With accepts_nested_attributes_for on Foreman core models (including host facets), define the nested model's attributes with ParameterFilter and use the plugin API to register it on the top-level object, e.g. parameter_filter Host::Managed, :my_facet => [my_facet_param_filter]
  • With accepts_nested_attributes_for, ensure any custom forms or JS use integer keys for nested models and not mixed (e.g. "new_123456" becomes "123456"). Same for API calls, use a unique integer for each unique nested model.
  • Aim to remove any use of protected_attributes as soon as possible. It isn't compatible with Rails 5 and adds complex interactions with Rails 4 methods.

Further information