# Rails Form Objects With dry-rb


Building form objects in modern Rails applications is nothing new.
Most Ruby developers are familiar with including **Virtus** and **ActiveModel::Validations** to their form objects.
Today I would like to show how to build a form object using **dry-types** and **dry-validation**

<!-- more -->

Let's build a simple form object for creating postcards. We have 3 models inside the application:

* `Country` with `name` and `is_state_required` fields. The second field indicates that to create a proper address, a user needs to provide a state - like in the US.
* `Country::State` with `name` and `country_id`.
* `Postcard` with `state_id`, `country_id`, `content` and address fields.


## Definition of done
* Form creates new postcard (quite obvious).
* Validates presence of address, city, zip code, content and country.
* Validates format of zip code.
* Validates length of content (if you want something short just tweet it or text it).
* If selected country requires a state, presence of state should be validated.

## Attributes and types
Let's start with attributes definition. Our form object needs to inherit from `Dry::Types::Struct`.
Dry will generate appropriate getters and the constructor.

```ruby
class Postcard
  module Types
    include Dry::Types.module
  end

  class CreateForm < Dry::Types::Struct
    attribute :address, Types::Coercible::String
    attribute :city, Types::Coercible::String
    attribute :zip_code, Types::Coercible::String
    attribute :content, Types::Coercible::String
  end
end
```

Just include `Dry::Types.module` module and use the types. 
*Dry-types* comes with a [wide choice of primitive types along with modifiers](http://dry-rb.org/gems/dry-types/built-in-types/).


Things get a bit more complicated when we would like to use our rails models.
We need to register our types in order to create attributes with these types. 
Which is done as follows:
`TypeName = Dry::Types::Definition.new(::MyRubyClass)`
You can also tell *dry-types* how should the Type be constructed by calling `.constructor` with a block.


So our definitions looks like this:

```ruby
module Types
  include Dry::Types.module
  Country = Dry::Types::Definition.new(::Country)
  CountryState = Dry::Types::Definition.new(::Country::State)
end
```

Now we can use `Country` and `CountryState` as types. So finally form's definition looks like this:

```ruby
class CreateForm < Dry::Types::Struct
  attribute :address, Types::Coercible::String
  attribute :city, Types::Coercible::String
  attribute :zip_code, Types::Coercible::String
  attribute :content, Types::Coercible::String
  attribute :country, Types::Country
  attribute :state, Types::CountryState
end
```

Yeah, we managed to create a simple struct.

### A note about the dry-types struct constructor 

If we don't specify constructor type, a *strict* constructor will be generated, which means it will throw `ArgumentError` if an attribute is missing.
We will handle presence validation using *dry-validation* so we will use **schema** or **symbolized** constructor
[more information on constructor types here](https://github.com/dry-rb/dry-types/issues/72).
To use *schema* constructor type we need to call `constructor_type(:schema)` inside class body.

## Validations

To perform validations inside our form object we'll use [dry-validation gem](http://dry-rb.org/gems/dry-validation/).
It comes with a wide variety of predicates, which are simple to use. Lets start with presence validation:


```ruby
PostcardSchema = Dry::Validation.Schema do
  required(:address).filled
  required(:city).filled
  required(:zip_code).filled
  required(:content).filled
  required(:country).filled
end
```

We create a schema to which we pass model attributes, defined previously, like this:

```ruby
errors = PostcardSchema.call(to_hash).messages(full: true)
```

Ok so what's happened here:

* `to_hash` (or `to_h` ) generates a hash based on attributes 
* `.messages(full: true)` returns full error messages

To pass more requirements to validation, like format, length and so on just pass parameters to `.filled` method.
Lets take *content* as an example, not only it should be present but also it should be longer than 20 characters:

```ruby
required(:content).filled(min_size?: 20)
```

The full list of ready to use predicates can be accessed [here](http://dry-rb.org/gems/dry-validation/basics/built-in-predicates/).

### More complex validation logic

Presence or length validations are delivered by *dry-validation*.
Unfortunately(?) in real live application those are usually not enough, that's why *dry-validation* allows us to write our own predicates.


Let start with a simple one, which will check if the country passed to validation requires state.

```ruby
PostcardSchema = Dry::Validation.Schema do
  configure do
    config.messages_file = Rails.root.join('config/locales/errors.yml')
    def state_required?(country)
      country.is_state_required
    end
  end
# (...)
end
```

As simple as that, just remember to put proper error message to your `errors.yml` file, more information about errors file [here](http://dry-rb.org/gems/dry-validation/error-messages/).

So let's get to the core and check presence of state only if the country requires it. 
We need to tell validation that the state might be present (or might be not). 
To do this just put the following line to our schema:


```ruby
required(:state).maybe
```


### Defining the rule itself


Defining the rule itself goes as follows:

```ruby
rule(country_requires_state: [:country, :state]) do |country, state|
  country.state_required? > state.filled?
end
```

As simple as that:

* we pass rule name along with the fields that are required inside the rule. 
  In our case we use *country* and *state*.
* Those variables are yielded to the block.
* The rule is translated like this *if state is required then check presence of state*.

More information about *high level rules* is available [here](http://dry-rb.org/gems/dry-validation/high-level-rules/)

## Finished form object

```ruby
class Postcard
  module Types
    include Dry::Types.module
    Country = Dry::Types::Definition
                .new(::Country)
    CountryState = Dry::Types::Definition
                     .new(::Country::State)
  end

  class CreateForm < Dry::Types::Struct
    constructor_type(:schema)

    ZIP_CODE_FORMAT = /\d{5}/
    MINIMAL_CONTENT_LENGTH = 20

    attribute :address, Types::Coercible::String
    attribute :city, Types::Coercible::String
    attribute :zip_code, Types::Coercible::String
    attribute :content, Types::Coercible::String
    attribute :country, Types::Country
    attribute :state, Types::CountryState


    def save!
      errors = PostcardSchema.call(to_hash).messages(full: true)
      raise CommandValidationFailed, errors if errors.present?
      Postcard.create!(to_hash)
    end

    private

    PostcardSchema = Dry::Validation.Schema do
      configure do
        config.messages_file = Rails.root.join('config/locales/errors.yml')
        def state_required?(country)
          country.is_state_required
        end
      end
      required(:address).filled
      required(:city).filled
      required(:zip_code).filled(format?: ZIP_CODE_FORMAT)
      required(:content).filled(min_size?: MINIMAL_CONTENT_LENGTH)
      required(:state).maybe
      required(:country).filled

      rule(country_requires_state: [:country, :state]) do |country, state|
        country.state_required? > state.filled?
      end
    end
  end
end
```

Full project, including models and spec is available on [my github](https://github.com/CucumisSativus/dry-form-objects-example/tree/dry_validation).

### Possible refactor

Just to make things simple and readable in a blog post I put everything connected to the object itself into one file.
In real life application this is probably not the best way of organizing your codebase. What can be done:

* `Types` module could be placed as a separate module, probably even a global scope one
* `PostcardSchema` could be placed outside this form object and used in, for example, update form
* the same goes to constants - `ZIP_CODE_FORMAT` and `MINIMAL_CONTENT_LENGTH`

## Conclusion

Using **dry-types** allows you to write type safe components to your application. 
This library comes with a wide choice of ready to use types and it's quite easy to define your own.


I feel like **dry-validation** approach is more clean than **ActiveModel** one. You put all your validation logic inside one, clearly bounded place.
Those validations can be easily reused by other forms (like update ones).


The biggest problem with **dry-rb**, similarly to **ROM** and **Roda**, is a lack of documentation which allows fresh users to start easily.
Trust me or not I spent over 2 hours writing the form object. Mostly because the problems with the documentation and lack of blogposts.
So hopefuly this blogpost will save someone's 2 hours.

