Because my request payload is based on jsonapi.org spec, so I just wondering is there any way to setup parent and child relationship to inherit schema from the parent. Please look at example below:
class JSONAPIResourceSchema < Dry::Validation::Contract
params do
required(:data).schema do
required(:type).filled(:string)
required(:attributes).schema do
### yield child schema ###
end
end
end
end
class UserSchema < JSONAPIResourceSchema
params do
required(:name).filled(:string)
end
end
UserSchema.new.call(data: { type: "users", attributes: { name: "John Doe" } })
require 'dry/validation'
class UserSchema < Dry::Schema::Params
define do
required(:name).filled(:string)
end
end
class MyContract < Dry::Validation::Contract
params do
required(:attributes).hash(UserSchema.new)
end
end
my_contract = MyContract.new
my_contract.call(attributes: { name: "" }).errors.to_h
# {:attributes=>{:name=>["must be filled"]}}
We may introduce an API to hide the details in cases like that, but this will work for now.
Thanks for your suggestion, then it will have 1 schema and 1 contract to represent one request object.
I have ended up with a solution with inheritance and override params methods:
class JSONAPIRequestSchema < ApplicationRequestSchema
def self.params(resource_type, &block)
super() do
required(:data).schema do
required(:type).filled(:str?, eql?: resource_type.to_s)
required(:attributes).hash do
instance_eval(&block)
end
end
end
end
def self.rule(*args, &block)
args = args.map { |arg| "data.attributes.#{arg}" }
super(*args) do
attributes = values.dig(:data, :attributes)
instance_exec(attributes, &block) if block_given?
end
end
end
class ChargeRequestSchema < JSONAPIRequestSchema
params(:charge) do
required(:amount).type(:integer).filled(:int?, gteq?: 100, lteq?: 99999999)
required(:currency).value(included_in?: %w[usd USD])
end
end
I just curious what do you think for this solution @solnic?
@samnang this looks like a good solution for now. It would be good to have an API for this kind of use cases in the future though.
One thing to mention that’s unrelated to inheritance, is that it’s recommended to use rules for domain validation like gteq?: 100 etc. For now you can use use rule but in 1.2.0 we’ll add an extension that will define macros for all the built-in predicates, so you’ll be able to do things like:
class ChargeRequestSchema < JSONAPIRequestSchema
params(:charge) do
required(:amount).filled(:integer)
required(:currency).filled(:string)
end
rule(:amount).validate(gteq?: 100, lteq?: 99999999)
rule(:currency).validate(included_in?: %w[usd USD])
end
oh and one more thing:
# this
required(:amount).type(:integer).filled(:int?, ...)
# can be simplified as this
required(:amount).filled(:integer, ...)