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
The Types modules is defaulted to :strict, this means when I reference String it comes from Strict::String. This is a dry-type.
When type-checking, I have to use ::String because there is a different String present in Types
I chain .optional to indicate that the type should allow nil
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?
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.