Open dwilkie opened 5 years ago
I'm not entirely sure how to solve this yet, but what I am sure is that this is definitely not something that will require extending the schema DSL. Maybe we could have a mechanism for converting existing schema from required keys to optional.
Anyway, this is something that dry-validation can solve eventually, so I'm moving this issue there.
@dwilkie you could just send all of those params again, instead of sending blank values on update.
@solnic Do you have any ideas on how dry-validation can solve this ? Something like having variants of a schema ? Or having another api to transform the main schema ? We are needing this feature as well in order to not duplicate contracts that have different schemas.
@bilby91 I don't have. For the time being you can simply do this:
require 'dry-validation'
class UserContract < Dry::Validation::Contract
def self.define_params(key = :required)
params do
public_send(key, :name).value(:string)
public_send(key, :email).value(:string)
end
end
end
class UserContract::Create < UserContract
define_params
end
class UserContract::Update < UserContract
define_params(:optional)
end
create_contract = UserContract::Create.new
create_contract.(name: 'Jane', email: 'jane@doe.org')
#<Dry::Validation::Result{:name=>"Jane", :email=>"jane@doe.org"} errors={}>
update_contract = UserContract::Update.new
update_contract.(email: 'jane@doe.org')
#<Dry::Validation::Result{:email=>"jane@doe.org"} errors={}>
I had a related query to this issue which I was about to ask and thought let me first check if I can find any related issues in open issues list and I ended up finding this. My use-case is
I have following schema for an Address model I have:
ValidationContexts = Types::String.enum('abc', 'def')
params do
required(:address_line_1).filled(Types::StrippedString)
optional(:address_line_2).maybe(Types::StrippedString)
required(:city).filled(Types::StrippedString)
required(:state).filled(Types::StrippedString)
required(:country).filled(Types::StrippedString)
required(:zip_code).filled(Types::StrippedString)
required(:contact_detail).filled(Types::Instance(::ContactDetail))
optional(:validation_context).filled(ValidationContexts)
end
I have a need wherein applying a rule on optional validation_context
I want to make the required contact_detail
optional.
rule(:validation_context, :contact_detail) do
if key?
case values[:validation_context]
when ValidationContexts['abc']
# make contact_detail optional. Is this possible?
else
# do nothing
end
end
end
Is something like that possible?
My setup
ruby '2.7.1'
gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
gem 'dry-validation', '~> 1.5'
Thanks.
@jiggneshhgohel no it's not possible. Once you're within a rule your schema is already established so you can't alter it based on input.
@solnic Thanks for the prompt response.
I am having the same issue. What I am trying to do is:
params[:foo]
is required.params[:foo]
is true
, then params[:bar]
is required, otherwise params[:bar]
is optional.Looks like it's not possible using dry-validation.
The equivalent JSON schema looks like:
{
"anyOf":
[
{
"title": "foo is true",
"properties": { "foo": { "type": "boolean", "enum": [true] } },
},
{
"title": "foo is false",
"properties":
{
"foo": { "type": "boolean", "enum": [false] },
"bar": { "type": "string"},
},
"required": ["bar"],
},
],
}
Looks like it's not possible using dry-validation.
Dependencies between fields must be handled via rules, not through the schema dsl. :bar
should always be optional, then you check its presence with custom logic in a rule block.
In many REST APIs it's often the case when creating resources, certain fields are required but when updating resources, all fields are optional and only the fields which are provided are updated.
Examples
For example, the following schema is good for validating user creation.
But when updating a user, we need the following schema:
We don't want to repeat the schema and the rules
My first thought of a possible solution would be something like:
For a PATCH request, the resource could be injected as an external dependency, for a POST request the resource doesn't exist yet so it's not injected.