Skip to content

Mutations gem interface implemented with `dry-rb`’s validation schemas.

License

Notifications You must be signed in to change notification settings

saverio-kantox/dry-mutations

 
 

Repository files navigation

Dry::Mutations

Build Status Code Climate


A link between dry-validation and mutations gems. This gem enables support for dry-validation schemas to be used within legacy mutations-based syntax.

Installation

Add this line to your application's Gemfile:

gem 'dry-mutations'

And then execute:

$ bundle

Or install it yourself as:

$ gem install dry-mutations

Was ⇒ Is

Was

class ComposedMutation < Mutations::Command
  ...
  def validate
    additional_validate(input1, input2)
    @nested = NestedMutation.new(inputs, input1: input1, input2: input2)
    unless @nested.validation_outcome.success?
      @nested.validation_outcome.errors.each do |key, error|
        add_error(key.to_sym, error.symbolic, error.message)
      end
    end
  end

  def execute
    @nested.run!
  end
end

Is

class ComposedValidation < Mutations::Command
  prepend ::Dry::Mutations::Extensions::Command
  prepend ::Dry::Mutations::Extensions::Dummy

  ...
  def validate
    additional_validate(input1, input2)
  end
end

class ComposedTransform < Mutations::Command
  prepend ::Dry::Mutations::Extensions::Command

  ...
  def execute
    inputs.merge(input1: input1, input2: input2)
  end
end

class ComposedMutation
  extend ::Dry::Mutations::Transactions::DSL
  chain do
    validate ComposedValidation
    transform ComposedTransform
    mutate NestedMutation
  end
end

Call syntax

Basically, any call syntax is supported:

# preferred
ComposedMutation.(input)          # returns (Either ∨ Outcome) object

# legacy
ComposedMutation.run(input)       # returns (Either ∨ Outcome) object
ComposedMutation.new(input).run   # returns (Either ∨ Outcome) object
ComposedMutation.run!(input)      # throws Mutation::ValidationException
ComposedMutation.new(input).run!  # throws Mutation::ValidationException

Usage

Enable extensions for the specific mutation’s command

Prepend a ::Dry::Mutations::Extensions::Command module to your Mutation::Command instance:

class MyMutation < Mutations::Command
  prepend ::Dry::Mutations::Extensions::Command

  required do
    model :company, class: 'Profile'
    model :user
    hash  :maturity_set do
      string :maturity_choice, in: %w(spot forward_days fixed_date)
      optional do
        hash :maturity_days_set do
          integer :days # For spot or forward_days options
        end
        hash :maturity_date_set do
          date :date # When passing a fixed date
        end
      end
    end
    ...

dry-validation syntax

It is possible to mix standard mutations’ syntax with dry-rb schemas:

class MyMutation < Mutations::Command
  prepend ::Dry::Mutations::Extensions::Command

  required do
    model :company, class: 'Profile'
  end

  schema do
    required(:maturity_choice).filled(:str?, included_in?: %w(spot forward_days fixed_date))
  end

Reusing schema

Basically, everything written here is applicable. Syntax to include the nested schema is as simple as:

UserSchema = Dry::Validation.Schema do
  required(:email).filled(:str?)
  required(:name).filled(:str?)
  required(:address).schema(AddressSchema)
end

or, in legacy mutations syntax (NB! This is not yet implemented!):

required do
  string :email
  string :name
  schema :address, AddressSchema
end

Dealing outcome

Command

let!(:command) do
  Class.new(::Mutations::Command) do
    prepend ::Dry::Mutations::Extensions::Command

    required { string :name, max_length: 5 }
    schema { required(:amount).filled(:int?, gt?: 0) }

    def execute
      @inputs
    end
  end
end

Using Either monad

outcome = command.new(name: 'John', amount: 42).run
outcome.right?
#⇒ true
outcome.either.value
#⇒ { 'name' => 'John', 'amount' => 42 }

outcome = command.new(name: 'John Donne', amount: -500).run
outcome.right?
#⇒ false
outcome.left?
#⇒ true
outcome.either
#⇒ Left({
#   "name"=>#<Dry::Mutations::Errors::ErrorAtom:0x00000003b4e7b0
#               @key="name",
#               @symbol=:max_length,
#               @message="size cannot be greater than 5",
#               @index=0,
#               @dry_message=#<Dry::Validation::Message
#                               predicate=:max_size?
#                               path=[:name]
#                               text="size cannot be greater than 5"
#                               options={:args=>[5], :rule=>:name, :each=>false}>>,
#   "amount"=>#<Dry::Mutations::Errors::ErrorAtom:0x00000003b4e508
#               @key="amount",
#               @symbol=:gt?,
#               @message="must be greater than 0",
#               @index=1,
#               @dry_message=#<Dry::Validation::Message
#                               predicate=:gt?
#                               path=[:amount]
#                               text="must be greater than 0"
#                               options={:args=>[0], :rule=>:amount, :each=>false}>>
# })
outcome.either.value
#⇒ the hash ⇑ above

Using Matcher

expect(outcome.match { |m| m.success(&:keys) }).to match_array(%w(amount name))
expect(outcome.match { |m| m.failure(&:keys) }).to be_nil

Turn On Globally (use with caution!)

ENV['GLOBAL_DRY_MUTATIONS'] = 'true' && rake

That way all mutations all over the system will be patched/injected with new functionality. This is untested in all possible environments.

Bug reports are very welcome!

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dry-mutations. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

About

Mutations gem interface implemented with `dry-rb`’s validation schemas.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 99.8%
  • Shell 0.2%