An open API service indexing awesome lists of open source software.

https://github.com/am-kantox/dry-mutations

Mutations gem interface implemented with `dry-rb`’s validation schemas.
https://github.com/am-kantox/dry-mutations

Last synced: about 1 month ago
JSON representation

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

Awesome Lists containing this project

README

        

# Dry::Mutations

[![Build Status](https://travis-ci.org/am-kantox/dry-mutations.svg?branch=master)](https://travis-ci.org/am-kantox/dry-mutations)
[![Code Climate](https://codeclimate.com/github/am-kantox/dry-mutations/badges/gpa.svg)](https://codeclimate.com/github/am-kantox/dry-mutations)

---

A link between [`dry-validation`](http://dry-rb.org/gems/dry-validation) and
[`mutations`](http://github.com/cypriss/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:

```ruby
gem 'dry-mutations'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install dry-mutations

## Was ⇒ Is

### Was

```ruby
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
```ruby
class ComposedValidation < Mutations::Command
prepend ::Dry::Mutations::Extensions::Command
prepend ::Dry::Mutations::Extensions::Sieve

...
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:

```ruby
# 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:

```ruby
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, default: 3 # 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:

```ruby
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](http://dry-rb.org/gems/dry-validation/reusing-schemas/)
is applicable. Syntax to include the nested schema is as simple as:

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

or, in legacy `mutations` syntax (**NB! Starting with `0.99.9`!**):

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

## `ActiveRecord::Relation` support

```ruby
schema(Dry::Mutations.Schema do
required(:slaves).filled(relation?: Slave)
end)
```

## Combining dry schemas with mutation-like syntax

Since version `0.99.9`, one might pass the `Dry::Validation::Schema` directly
to legacy mutations syntax:

```ruby
required do
model :user
schema :address, AddressSchema # AddressSchema = ::Dry::Validation.Schema {}
date: Date.today
end
```

---

Since version `0.11.1`, one might pass the instance of `Dry::Validation::Schema`
and/or `Dry::Validation::Form` instance to `schema` mutation DSL.

Such a block might be _only one_, and it _must be_ the first DSL in the mutation.
**NB** this is not a preferred way to do things, but it might be useful to _share_
schemas (unlikely the above, this will _embed_ the schema, rather than _nest_ it.)

### Correct

```ruby
Class.new(::Mutations::Command) do
prepend ::Dry::Mutations::Extensions::Command
prepend ::Dry::Mutations::Extensions::Sieve

schema(::Dry::Validation.Form do
required(:integer_value).filled(:int?, gt?: 0)
required(:date_value).filled(:date?)
required(:bool_value).filled(:bool?)
end)

required do
integer :forty_two
string :hello
end
end
```

### Incorrect

```ruby
Class.new(::Mutations::Command) do
prepend ::Dry::Mutations::Extensions::Command
prepend ::Dry::Mutations::Extensions::Sieve

required do
integer :forty_two
string :hello
end

schema(::Dry::Validation.Form do
required(:integer_value).filled(:int?, gt?: 0)
required(:date_value).filled(:date?)
required(:bool_value).filled(:bool?)
end)
end
```

## Declare the resulting type of a schema

Using the approach above, one might start with a schema type declaration:

```ruby
# this line must be a first declaration
schema(::Dry::Validation.Form {})

# now continue with generic `schema {}` blocks to append features:
schema do
required(:integer_value).filled(:int?, gt?: 0)
required(:date_value).filled(:date?)
required(:bool_value).filled(:bool?)
end
```
see [`schema_spec.rb`](https://github.com/am-kantox/dry-mutations/blob/master/spec/dry/mutations/schema_spec.rb)
for an inspiration.

### Subschema’s type

Startign with `0.99.100` we accept `type:` parameter in call to `schema`:

```ruby
schema type: :form do
...
end
```

## Dealing with `outcome`

### Command

```ruby
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

```ruby
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"=>#[5], :rule=>:name, :each=>false}>>,
# "amount"=>#[0], :rule=>:amount, :each=>false}>>
# })
outcome.either.value
#⇒ the hash ⇑ above
```

### Using `Matcher`

```ruby
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!

## Changelog

#### 0.99.1
Support for direct input parameters invocation. 100%-compatibility with `mutations`:

```ruby
def validate # input ≡ { date: nil }
date < Date.now
end
```

#### 1.1.0
More handy `chain`s, better `dry-rb` integration, improvements.

#### 0.99.0
Support for `default:` guard. 99%-compatibility with `mutations`

## 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](https://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](contributor-covenant.org) code of conduct.

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).