Skip to content

Getting paranoid about your Rails application params? Try paramoid!

License

Notifications You must be signed in to change notification settings

monade/paramoid

Repository files navigation

Tests Gem Version

Paramoid

Getting paranoid about your Rails application params? Try paramoid!

Paramoid is an extension for Rails Strong Parameters that allows to sanitize complex params structures with a super cool DSL, supporting:

  • Required params and default values
  • A simplified nested structures management
  • Conditional sanitization, based on user auth, role or custom logic
  • Renaming and remapping parameter names

Installation

Add the gem to your Gemfile

gem 'paramoid'

and run the bundle install command.

Usage

Declare a class extending Paramoid::Base.

class PersonParamsSanitizer < Paramoid::Base
  # @param [User] user
  def initialize(user = nil)
    params! :first_name, :last_name

    group! :address_attributes do
      params! :id, :road, :town, :state, :zip_code, :country
    end
  end
end

Then use it in your controller:

class PeopleController < ApplicationController

  def create
    @person = Person.create!(person_params)
  end

  private

  def person_params
    # The name is automatically inferred by the controller name
    sanitize_params!
    # Or you can instantiate a custom one
    # You can pass the current user or nil
    # CustomPersonParamsSanitizer.new(current_user).sanitize(params)
  end
end

param! vs params! vs group! vs array!

Paramoid is based on Rails Strong Parameters and it's inheriting its behaviour.

  • param! is used to permit a single scalar parameter. param! :name is equivalent of params.permit(:name, ...)
  • params! is just a shortcut to sanitize in mass a list of parameters having the same options
  • group! is used to sanitize objects or arrays, like params.permit(my_key: [:list, :of, :keys])
  • array! is an alias of group! and it's added for readability: in Strong Parameters, params.permit(name: [:some_key]) accepts both a single object or an array of objects, and this is preserved here.

So the previous example:

class PersonParamsSanitizer < Paramoid::Base
  # @param [User] user
  def initialize(user = nil)
    params! :first_name, :last_name

    group! :address_attributes do
      params! :id, :road, :town, :state, :zip_code, :country
    end
  end
end

Is equivalent to:

params.permit(:first_name, :last_name, address_attributes: [:id, :road, :town, :state, :zip_code, :country])

Required values

Declaring a parameter as required, will raise a ActionController::ParameterMissing error if that parameter is not passed by to the controller. This also works with nested structures.

class UserParamsSanitizer < Paramoid::Base
  def initialize(user = nil)
    params! :first_name, :last_name, required: true
    group! :contact_attributes do
      param! :phone, required: true
    end
  end
end

Default values

You can declare a default value to a certain parameter. That value is assigned only if that value is not passed in the parameters.

Example:

class PostParamsSanitizer < Paramoid::Base
  def initialize(user = nil)
    param! :status, default: 'draft'
    param! :approved, default: false
  end
end

Input:

<ActionController::Parameters {"status"=>"published","another_parameter"=>"this will be filtered out"} permitted: false>

Output:

<ActionController::Parameters {"status"=>"published","approved":false} permitted: true>

Name remapping

You can also remap the name of a parameter.

class PostParamsSanitizer < Paramoid::Base
  def initialize(user = nil)
    param! :status, as: :state
  end
end

Input:

<ActionController::Parameters {"status"=>"draft","another_parameter"=>"this will be filtered out"} permitted: false>

Output:

<ActionController::Parameters {"state"=>"draft"} permitted: true>

Conditional parameters

By using the reference of the current_user in the constructor, you can permit certain parameters based on a specific condition.

Example:

class PostParamsSanitizer < Paramoid::Base
  def initialize(user = nil)
    params! :first_name, :last_name
    param! :published if user&.admin?
  end
end

Inline sanitization

You can also use the sanitizer DSL inline directly in your controller:

class PeopleController < ApplicationController
  def create
    @person = Person.create!(person_params)
  end

  private

  def person_params
    sanitize_params! do
      params! :first_name, :last_name, required: true
    end
  end
end

Full Example

class PersonParamsSanitizer < Paramoid::Base
  # @param [User] user
  def initialize(user = nil)
    params! :first_name, :last_name, :gender

    param! :current_user_id, required: true

    param! :an_object_filtered
    param! :an_array_filtered

    array! :an_array_unfiltered

    param! :role if user&.admin?

    default! :some_default, 1

    group! :contact, as: :contact_attributes do
      params! :id, :first_name, :last_name, :birth_date, :birth_place, :phone, :role, :fiscal_code
    end
  end
end

TODOs

  • Params type checking and regexp-based validations

About Monade

monade

Paramoid is maintained by mònade srl.

We <3 open source software. Contact us for your next project!