convert empty params to nil

what’s the best way to convert a request parameter to nil if it’s empty? like if I have an action which is handling form input and I want to make sure every empty string is turned into a nil before attempting to store in in the database?

I could of course do it manually, but I was wondering if something like this existed:

params do
  required(:book).hash do
    required(:name).filled(:string)
    required(:volume).filled(:integer)
    required(:publisher).make_nil_if_empty  # is this possible?
  end
end

Sure, you can do this. It requires graduating from standard, nominal types like :string to your own type object.

First, I always define a top-level types file in my lib/ dir that gets required early.

Types = Dry.Types(default: :strict)

module Types
  StringPresence = String.optional.constructor do |value|
    next value unless value.is_a?(::String)

    if value.strip.empty?
      nil
    else
      value
    end
  end
end
  1. The Types modules is defaulted to :strict, this means when I reference String it comes from Strict::String. This is a dry-type.
  2. When type-checking, I have to use ::String because there is a different String present in Types
  3. I chain .optional to indicate that the type should allow nil
  4. The constructor definition performs the coercion logic of empty → nil

Next, define your param using the required macro: this means that the key should always be present. I will use the generic maybe helper to indicate that the value may be nil.

Pass your concrete type object as the argument.

params do
  required(:book).hash do
    required(:name).filled(:string)
    required(:volume).filled(:integer)
    required(:publisher).maybe(Types::StringPresence)
  end
end

This is superior to doing it procedurally in the handler, because now you have a reusable type object that is solely responsible for this operation, and you have given it a name.

I really liked this solution! unfortunately, I’m not able to get it to work as expected. I’ve modified the bookshelf web app and added a blurb to the books database:

ROM::SQL.migration do
  up do
    alter_table :books do
      add_column :blurb, :text, null: true
    end
  end

  down do
    alter_table :books do
      drop_column :blurb
    end
  end
end

then I add it as a text field in my view and add it to the params in my action:

params do
  required(:id).filled(:integer)
  required(:book).hash do
    required(:title).filled(:string)
    required(:author).filled(:string)
    required(:blurb).value(Types::StringPresence)
  end
end

if I then check if the params are valid using the request.params.valid? check I get the following error:

{book: {blurb: ["must be a string"]}}

is there a different database column type I need to use or some other way I can tell it that it being nil is fine?

If your database column is nilable, the :blurb param should probably be marked as optional as well:

optional(:blurb).value(Types::StringPresence)

Now dry validation is halting because you’re requiring it.

Use required(:blurb).maybe(Types::StringPresence). I though .value would work, but I was wrong. .maybe is for key values that may be nil.

@wout’s solution is also valid. The distinction is, do you prefer a present key that is nil, or an absent key. The original question seemed to be asking for a nil key that was present.

thank you both for your help :cherry_blossom: