Is there some way to rename a coerced and validated field in the output hash?
I have a web form where an input parameter is named durationText and contains a value like 7 days. We parse this text and get a more reasonable Ruby type (in this case the duration as seconds).
We want to perform validations on the parsed value (e.g. “less than 1 month”) and then pass the parsed value to our underlying object.
This all seems reasonable, as we can create a custom type and handle that. The problem arises with the name: durationText.
In other situations where we have myLongParam at the HTTP interface and my_long_param in Ruby, I’ve added aliases to our data object:
class MyData
include ActiveModel::Model
attr_accessor :my_long_param
alias :myLongParam= :my_long_param=
end
That works, but “pollutes” our domain object with knowledge of the HTTP layer, even if it is only via an alternate name. However, adding an alias called durationText that doesn’t expects to receive a number is going to be downright confusing.
It would be awesome if there a way to have a contract like:
class NewUserContract < Dry::Validation::Contract
params do
required(:lastName).value(:integer).as(:last_name)
end
end
It’s not really within dry-validation’s purview (at least night right now, and maybe not ever) to make data structure transformations like this.
My suggestion would be to combine your validation step with a subsequent mapping step, i.e. after the validation succeeds, pass the data through an object like this to apply your structural transformations:
require "dry/validation"
require "transproc"
module Transformations
extend Transproc::Registry
import Transproc::HashTransformations
end
class Contract < Dry::Validation::Contract
params do
# Ignoring your actual time parsing for simplicity's sake
required(:duration_text).filled(:string)
end
end
class Mapper < Transproc::Transformer[Transformations]
rename_keys duration_text: :duration
end
contract = Contract.new
mapper = Mapper.new
input = {"duration_text" => "hi"}
validation = contract.(input)
if validation.success?
Mapper.new.(duration_text: "hi")
# => {:duration=>"hi"}
end
If you needed to do this kind of mapping in a lot of your application’s validation contracts, I’m totally confident you could build your own contract superclass that incorporates a post-validation mapping step (either via an injected mapper object, or via adding a mapper do ... end DSL, or maybe even both) such that it all feels natural and easy to do.
Just to confirm - this will never-ever EVER be a feature in dry-validation. I’ll be working on porting transproc to dry-transformer later this year, so expect various improvements and better integration with other dry-rb gems.