How can I rename or alias a field after it is validated?

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

I was afraid you were going to give this answer, again :sob:

I’m glad to see, at least, that I’m consistent :slight_smile:

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.

1 Like