Last Updated: February 25, 2016
·
3.704K
· wojtha

How to enable Strong Parameters protection for non-ActiveRecord models?

I'm currently migrating our Rails app to use Strong Parameters mass-assignment protection instead of model-centric protection, which is used in Rails 3.2.x by default.

Migration of ActiveRecord models is easy and well documented. However I want to enable that kind of protection even for our not persisted models.

So lets have kind of hypothetical model 'CarForm' with properties 'driver', 'manufacturer', and 'color'.

class CarForm

  # It quacks like ActiveModel...
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_accessor :driver, :manufacturer, :color

  validates :driver, :manufacturer, :color, presence: true

  def initialize(values = {})
    values.each do |k, v|
      send("#{k}=", v)
    end
  end

  def persisted?
    false
  end

 end

Readme for Strong Parameters guide us to use the include ActiveModel::ForbiddenAttributesProtection in the model. So we just include that and we are done, right? Nope. This module was designed to work with ActiveRecord with such methods like assign_attributes and initialize where the parameters are filtered through sanitize_for_mass_assignment. So we need to add this method to our model:

def assign_attributes(values, options = {})
  sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
    send("#{k}=", v)
  end
 end

And we can rewrite initialize like this:

def initialize(values = {})
  assign_attributes(values)
end

However when we try to run this code we got the following error:
NoMethodError: super: no superclass method 'sanitize_for_mass_assignment' for #<CarForm:0x000000078aea38>

We missing just one thing the mass assignment protection itself! We need to include one more mixin ActiveModel::MassAssignmentSecurity (which is also part of the ActiveRecord by default). This module contains sanitize_for_mass_assignment method as well. So we need to load this module before the ActiveModel::ForbiddenAttributesProtection mixin to act as superclass.

And now we finally can call:

params = ActionController::Parameters.new({
          'car_form' => {
             'driver' => 'Alfred',
             'color' => '#000000',
             'manufacturer' => 'Rolls-Royce'
          }
        })
sanitized_params = params.require(:car_form).permit(:driver, :color, :manufacturer)
form = CarForm.new(sanitized_params)

Below you can see the modified class:

class CarForm

  # It quacks like ActiveModel...
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations
  # Mass-assignment protection using Strong Params
  include ActiveModel::MassAssignmentSecurity
  include ActiveModel::ForbiddenAttributesProtection

  attr_accessor :driver, :manufacturer, :color

  validates :driver, :manufacturer, :color, presence: true

  def initialize(values = {})
    assign_attributes(values)
  end

  def assign_attributes(values, options = {})
    sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
      send("#{k}=", v)
    end
  end

  def persisted?
    false
  end

end