module Fend::Plugins::Coercions
`coercions` plugin provides a way to coerce validaiton input. First, the plugin needs to be loaded
plugin :coercions
Because of Fend's dynamic nature, coercion is separated from validation. As such, coercion needs to be done before the actual validation. In order to make this work, type schema must be passed to `coerce` method.
coerce username: :string, age: :integer, admin: :boolean
As you can see, type schema is just a hash containing param names and types to which the values need to be converted. Here are some examples:
# coerce username value to string coerce(username: :string) # coerce address value to hash coerce(address: :hash) # coerce address value to hash # coerce address[:city] value to string # coerce address[:street] value to string coerce(address: { city: :string, street: :string }) # coerce tags to an array coerce(tags: :array) # coerce tags to an array of strings coerce(tags: [:string]) # coerce tags to an array of hashes, each containing `id` and `name` of the tag coerce(tags: [{ id: :integer, name: :string }])
Coerced data will also serve as result output:
result = UserValidation.call(username: 1234, age: "18", admin: 0) result.output #=> { username: "1234", age: 18, admin: false }
## Built-in coercions
General rules:
* If input value **cannot** be coerced to specified type, it is returned unmodified. * `nil` is returned if input value is an empty string, except for `:hash` and `:array` coercions.
:any : Returns input
:string : Returns `input.to_s` if input is `Numeric` or `Symbol`
:symbol : Returns `input.to_sym` if `input.respond_to?(:to_sym)`
:integer : Uses `Kernel.Integer(input)`
:float : Uses `Kernel.Float(input)`
:decimal : Uses `Kernel.Float(input).to_d`
:date : Uses `Date.parse(input)`
:date_time : Uses `DateTime.parse(input)`
:time : Uses `Time.parse(input)`
:boolean : Returns `true` if input is one of: `1, “1”, “t”, “true”, :true “y”,“yes”, “on”` (case insensitive)
: Returns `false` if input is one of: `0, “0”, “f”, “false”, :false, “n”, “no”, “off”` (case insensitive)
:array : Returns `[]` if input is an empty string.
: Returns input if input is an array
:hash : Returns `{}` if input is an empty string.
: Returns input if input is a hash
## Strict coercions
Adding `strict_` prefix to type name will cause error to be raised when input is incoercible:
coerce username: :strict_string UserValidation.call(username: Hash.new) #=> Fend::Plugins::Coercions::CoercionError: cannot coerce {} to string
Custom error message can be defined by setting `:strict_error_message` option when loading the plugin:
plugin :coercions, strict_error_message: "Incoercible input encountered" # or plugin :coercions, strict_error_message: ->(value, type) { "#{value.inspect} cannot become #{type}" }
## Defining custom coercions and overriding built-in ones
You can define your own coercion method or override the built-in one by passing a block and using `coerce_to` method, when loading the plugin:
plugin :coercions do # add new coerce_to(:positive_integer) do |input| Kernel.Integer(input).abs end # override existing coerce_to(:integer) do |input| # ... end end
### Handling incoercible input
If input value cannot be coerced, either `ArgumentError` or `TypeError` should be raised.
class PostValidation < Fend plugin :coercions do coerce_to(:user) do |input| raise ArgumentError unless input.is_a?(Integer) User.find(input) end end # ... end
`ArgumentError` and `TypeError` are rescued on a higher level and input is returned as is.
`coerce(modified_by: :user)`
result = PostValidation.call(modified_by: "invalid_id") result.input #=> { modified_by: "invalid_id" } result.output #=> { modified_by: "invalid_id" }
If strict coercion is specified, errors are re-raised as `CoercionError`.
`coerce(modified_by: :strict_user)`
result = PostValidation.call(modified_by: "invalid_id") #=> Fend::Plugins::Coercions::CoercionError: cannot coerce invalid_id to user
### Handling empty strings
In order to check if input is an empty string, you can take advantange of `empty_string?` helper method. It takes input as an argument:
coerce_to(:user) do |input| return if empty_string?(input) # ... end
Public Class Methods
# File lib/fend/plugins/coercions.rb, line 188 def self.configure(validation, opts = {}, &block) validation.const_set(:Coerce, Class.new(Fend::Plugins::Coercions::Coerce)) unless validation.const_defined?(:Coerce) validation::Coerce.class_eval(&block) if block_given? validation.opts[:coercions_strict_error_message] = opts.fetch(:strict_error_message, validation.opts[:coercions_strict_error_message]) validation::Coerce.fend_class = validation validation.const_set(:Coercer, Coercer) unless validation.const_defined?(:Coercer) end