friends TGIF!
I am wondering if anyone has solved the following problem as I have failed to do so both in dry-* and sorbet. I’d like to name this problem the “PATCH problem”
Let’s assume we have the following a users
table with
name
→ not nullable string
dob
→ (date of birth) nullable string
gender
→ nullable string
In a typical PATCH request the user gets to update whatever the provided in the request body only, therefore we should not update fields that are missing.
The loosely typed nature of hashes has an advantage over types as an input: there is a clear distinction between a missing input and a null
input. Let’s see an example.
(1) The following request body is relatively clear, we want to update the two fields.
{"user": {"name": "Meredith", "dob": "1968-01-01", "gender": "Female"}}
(2) With this body, we just want to nullify
the dob
.
{"user": {"dob": null}}
(3) With this body, we just want to update the name
{"user": {"name": "Dwight"}}
Let’s represent the above as a type
class User < Dry::Struct
attribute :name, String
attribute :dob, Types::String.optional
attribute :gender, Types::String.optional
end
And now we have an issue at hand. Looking at an instance of a user, how do you know dob
is null
because scenario 2 or 3? You cannot really distinguish.
It might be worth mentioning that I want to craft a domain type or even a typed input from the controller before I pass it in the service / domain layer.
In Haskell or Elm for instance, the above can be represented as
attribute :dob, Unchanged | Deleted | Updated a
I wonder if anyone has come across this before and has a workable solution. For instance, a simple but not great implementation would be
class User < Dry::Struct
attribute :name, String
attribute :dob, Types::String.optional
attribute :dob_given?, Bool
# ...
attribute :gender, Types::String.optional
end
Another example is having a specific struct for each possibility(Expressing the type of attribute B as dependent on an arbitrary value of attribute A.) but that can explode relatively quickly.
where we explicitly ask the crafter of the Struct to tell us if the input is actually nil
because the user provided nil
or never given.
Bonus points: one extra worry I have is how would any solution play with conversion to other types like my_struct.to_hash
.