Paramore
Paramore allows you to declare a typed schema for your params, so that any downstream code can work with the data it expects.
Installation
In your Gemfile:
gem 'paramore'
In your terminal:
$ bundle
Usage
Without typing
param_schema :item_params,
item: [:name, :description, :for_sale, :price, metadata: [tags: []]]
This is completely equivalent (including the return type) to
def item_params
@item_params ||= params
.require(:item)
.permit(:name, :description, :for_sale, :price, metadata: [tags: []])
end
With typing
A common problem in app development is untrustworthy input given by clients. That input needs to be sanitized and typecast for further processing. A naive approach could be:
# items_controller.rb
def item_params
@item_params ||= begin
_params = params
.require(:item)
.permit(:name, :description, :for_sale, :price, metadata: [tags: []])
_params[:name] = _params[:name].strip.squeeze(' ') if _params[:name]
_params[:description] = _params[:description].strip.squeeze(' ') if _params[:description]
_params[:for_sale] = _params[:for_sale].in?('t', 'true', '1') if _params[:for_sale]
_params[:price] = _params[:price].to_d if _params[:price]
if _params.dig(:metadata, :tags)
_params[:metadata][:tags] =
_params[:metadata][:tags].map { |tag_id| Item.[tag_id.to_i] }
end
_params
end
end
This approach clutters controllers with procedures to clean data, which leads to repetition and difficulties refactoring. The next logical step is extracting those procedures - this is where Paramore steps in:
# app/controllers/items_controller.rb
class ItemsController < ApplicationController
def create
Item.create(item_params)
end
param_schema :item_params,
item: Paramore.field({
name: Paramore.field(Paramore::SanitizedString),
description: Paramore.field(Paramore::StrippedString, null: true),
for_sale: Paramore.field(Paramore::Boolean, default: false),
price: Paramore.field(Paramore::Decimal),
metadata: Paramore.field({
tags: Paramore.field([Types::ItemTag], compact: true)
})
})
end
Types::ItemTag
could be your own type:
# app/types/item_tag.rb
module Types::ItemTag
module_function
def [](input)
Item.[input.to_i]
end
end
Now, given params
are:
<ActionController::Parameters {
"unpermitted"=>"parameter",
"name"=>"Shoe \n",
"description"=>" Black, with laces",
"for_sale"=>"true",
"price"=>"39.99",
"metadata"=><ActionController::Parameters { "tags"=>["38", "112"] } permitted: false>
} permitted: false>
Calling item_params
will return:
<ActionController::Parameters {
"name"=>"Shoe",
"description"=>"Black, with laces",
"for_sale"=>true,
"price"=>39.99,
"metadata"=><ActionController::Parameters { "tags"=>[:shoe, :new] } permitted: true>
} permitted: true>
This is useful when the values are not used with Rails models, but are passed to simple functions for processing. The types can also be easily reused anywhere in the app, since they are completely decoupled from Rails.
Notice that the Paramore::StrippedString
does not perform .squeeze(' ')
, only Paramore::SanitizedString
does.
nil
Types are non-nullable by default and raise exceptions if the param hash misses any.
This can be disabled for any type by declaring Paramore.field(Paramore::Int, null: true)
.
nils will usually not reach any of the type classes - if some parameter is nullable, the class will not be called.
If a parameter is non-nullable, then a Paramore::NilParameter
error will be raised before calling the class.
If an incoming array contains nils, they will get passed to type classes.
If you wish to get rid of empty array elements, declare Paramore.field(Paramore::Int, compact: true)
.
Configuration
Running $ paramore
will generate a configuration file located in config/initializers/paramore.rb
.
config.type_method_name
- default is[]
, to allow using, for example,SuperString["foo"]
syntax. Note that changing this value will preclude you from using built in types.
Safety
- Types will not be called if their parameter is missing (no key in the param hash)
- All given types must respond to the configured
type_method_name
and an error will be raised when controllers are loaded if they don't.
License
Paramore is released under the MIT license: