ruby-grape/grape · error · ArgumentError

Cannot inherit from contract, only schema

Error message

Cannot inherit from contract, only schema

What it means

`contract MyContract do ... end` would need to create a schema that inherits from the passed object, and only Dry::Schema processors can act as schema parents. Dry::Validation::Contract classes expose their schema through a `schema` method (hence the `respond_to?(:schema)` probe), and a contract's rules cannot be merged into an inline schema block, so this combination raises ArgumentError.

Source

Thrown at lib/grape/dsl/validations.rb:21

module Grape
  module DSL
    module Validations
      # Opens a root-level ParamsScope, defining parameter coercions and
      # validations for the endpoint.
      # @yield instance context of the new scope
      def params(&)
        Grape::Validations::ParamsScope.new(api: self, type: Hash, &)
      end

      # Declare the contract to be used for the endpoint's parameters.
      # @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
      #   The contract or schema to be used for validation. Optional.
      # @yield a block yielding a new instance of Dry::Schema::Params
      #   subclass, allowing to define the schema inline. When the
      #   +contract+ parameter is a schema, it will be used as a parent. Optional.
      def contract(contract = nil, &block)
        raise ArgumentError, 'Either contract or block must be provided' unless contract || block
        raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)

        Grape::Validations::ContractScope.new(self, contract, &block)
      end

      private

      # Clears all defined parameters and validations. The main purpose of it is to clean up
      # settings, so next endpoint won't interfere with previous one.
      #
      #    params do
      #      # params for the endpoint below this block
      #    end
      #    post '/current' do
      #      # whatever
      #    end
      #
      #    # somewhere between them the reset_validations! method gets called
      #

View on GitHub (pinned to 22d7975629)

Solutions

  1. Use the contract as-is with no block: `contract MyContract`.
  2. If you need customization, pass the underlying schema (`MyContract.schema`) with the block - schemas support inheritance.
  3. Better: define a new contract class that composes/reuses the rules of the original.

Example fix

# before
contract MyContract do
  required(:extra).filled(:string)
end # raises 'Cannot inherit from contract, only schema'

# after
contract MyContract # no block

# or inherit from the schema
contract MyContract.schema do
  required(:extra).filled(:string)
end
Defensive patterns

Strategy: type-guard

Validate before calling

def schema_like?(obj) = !obj.respond_to?(:schema) # contracts respond to :schema and reject blocks

raise ArgumentError, 'cannot add a block to a contract' if block && contract.respond_to?(:schema)

Type guard

def inheritable_contract?(obj) = obj.respond_to?(:schema) ? false : obj.is_a?(Class) || obj.is_a?(Dry::Schema::Processor)

Prevention

When it happens

Trigger: `contract MyContract do required(:extra).filled end` where MyContract < Dry::Validation::Contract. Passing a contract class read from configuration and also adding a tweak block. Copying a schema-with-block example but substituting a contract class.

Common situations: Trying to extend a shared contract with endpoint-specific keys. Migration from schemas to contracts while keeping the inline-block style. Mixing up Dry::Schema::Params and Dry::Validation::Contract in a codebase that uses both.

Related errors


AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21). Data as JSON: /api/errors/cf6fbd1658a3a946. Report an issue: GitHub.