How to use dry-validation with dry-transaction?

Hello,

At dry-rb - dry-transaction v0.13 - Wrapping operations following example is given:

class CreateUser
  include Dry::Transaction(container: Container)

  step :validate, with: "users.validate"
  step :create, with: "users.create"

  private

  def validate(input)
    adjusted_input = upcase_values(input)
    super(adjusted_input)
  end

  def upcase_values(input)
    input.each_with_object({}) { |(key, value), hash|
      hash[key.to_sym] = value.upcase
    }
  end
end
require "dry/container"
require "dry/transaction"
require "dry/transaction/operation"

module Users
  class Validate
    include Dry::Transaction::Operation

    def call(input)
      # returns Success(valid_data) or Failure(validation)
    end
  end

  class Create
    include Dry::Transaction::Operation

    def call(input)
      # returns Success(user)
    end
  end
end

Now is it possible to make Users::Validate look like

module Users
  class Validate
    include Dry::Transaction::Operation

    # Question: Is following possible?
    params do
      required(:email).value(:string)
      required(:age).value(:integer)
    end

    def call(input)
      # returns Success(valid_data) or Failure(validation)
    end
  end
end

Basically what I am trying to ask is can the input validations be defined directly inside a step (or in a transaction itself) without requiring to first define an explicit validation contract like it is illustrated at dry-rb - dry-validation v1.8 - Introduction and then running that contract?

If no, then is there any recommended pattern in dry-style regarding validating inputs in a transaction?

Thanks.

Sure, you just need to override the output of call to coerce it to a monad. Here’s a working example:

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'dry-transaction'
  gem 'dry-validation'
end

require 'dry/transaction'
require 'dry/transaction/operation'
require 'dry/validation'

Dry::Validation.load_extensions(:monads)

module Users
  class Validate < Dry::Validation::Contract
    include Dry::Transaction::Operation

    params do
      required(:email).value(:string)
      required(:age).value(:integer)

      before(:value_coercer) do |result|
        result.to_h.transform_values(&:upcase)
      end
    end

    def call(...) = super.to_monad
  end
end

Container = Dry::Core::Container.new

Container.register("users.validate") { Users::Validate.new }

class CreateUser
  include Dry::Transaction(container: Container)

  step :validate, with: "users.validate"
end

puts CreateUser.new.({ email: "john.doe@example.com", age: "23" }).inspect

1 Like