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

https://github.com/alexwayfer/formalism

Ruby gem for forms with validations and nesting.
https://github.com/alexwayfer/formalism

Last synced: about 1 year ago
JSON representation

Ruby gem for forms with validations and nesting.

Awesome Lists containing this project

README

          

# Formalism

[![Cirrus CI - Base Branch Build Status](https://img.shields.io/cirrus/github/AlexWayfer/formalism?style=flat-square)](https://cirrus-ci.com/github/AlexWayfer/formalism)
[![Codecov branch](https://img.shields.io/codecov/c/github/AlexWayfer/formalism/main.svg?style=flat-square)](https://codecov.io/gh/AlexWayfer/formalism)
[![Code Climate](https://img.shields.io/codeclimate/maintainability/AlexWayfer/formalism.svg?style=flat-square)](https://codeclimate.com/github/AlexWayfer/formalism)
[![Depfu](https://img.shields.io/depfu/AlexWayfer/benchmark_toys?style=flat-square)](https://depfu.com/repos/github/AlexWayfer/formalism)
[![Inline docs](https://inch-ci.org/github/AlexWayfer/formalism.svg?branch=main)](https://inch-ci.org/github/AlexWayfer/formalism)
[![license](https://img.shields.io/github/license/AlexWayfer/formalism.svg?style=flat-square)](https://github.com/AlexWayfer/formalism/blob/main/LICENSE.txt)
[![Gem](https://img.shields.io/gem/v/formalism.svg?style=flat-square)](https://rubygems.org/gems/formalism)

Ruby gem for forms with validations and nesting.

## Why

I need for service-like objects.

I've explored these projects:

* [Reform](https://github.com/trailblazer/reform)
* [Mutations](https://github.com/cypriss/mutations)
* [Interactor](https://github.com/collectiveidea/interactor)
* [dry-rb](https://github.com/dry-rb)

But nothing of them supports all features I need for:

* nesting (into unlimited levels) of themselves;
* simple syntax;
* custom validations and coercions;
* unified output.

So, I've tried to combine these all into one library and got Formalism.

### Why here are forms and what about service objects?

I've discovered that form object, only with validations,
are useless without service objects. So, I've combined them:
service objects include validations.

### If these are service objects, why they called forms?

Because if we're combining them — it's more like forms with logic inside for me
than service objects built-in forms. Even in HTML we're writing ``.
So, Formalism can accept all data from any-difficult `` and process it,
also with nested forms (for example, if you have some request form
with contact data and want to pass contacts into something like user form).

### And if I need for simple service object without validation?

You can use `Formalism::Action`, a parent of `Formalism::Form`.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'formalism'
```

And then execute:

```shell
bundle install
```

Or install it yourself as:

```shell
gem install formalism
```

## Usage

### Basic example

```ruby
class FindArtistForm < Formalism::Form
field :name

private

def validate
if name.to_s.empty?
errors.add 'Name is not provided'
end
end

def execute
Artist.first(fields_and_nested_forms)
end
end

class CreateAlbumForm < Formalism::Form
field :name, String
fiels :tags, Array, of: String
nested :artist, FindArtistForm

private

def validate
if name.to_s.empty?
errors.add 'Name is not provided'
end
end

def execute
Album.create(fields_and_nested_forms)
end
end

form = CreateAlbumForm.new(
name: 'Hits', tags: %w[Indie Rock Hits], artist: { name: 'Alex' }
)
form.run
```

### Running

Usually you need to initialize a form and execute `#run` method.
Internally, it runs `#valid?` (public) and `#execute` (private) methods.
`#valid?` runs `#validate` (private) of a form itself and nested forms.
`#run` can be redefined for database transaction, for example.

Also you can call `.run` with arguments for `#initialize`,
it's the alias for `#initialize` + `#run`.

#### Form outcome

Any call of `run` returns `Form::Outcome` instance which has `#success?`,
`#result` and `#errors` methods. Result is a result of `#execute` method.
Be careful: calling `#result` for failed outcome will raise `ValidationError`.

### Field type

Field receives type as the second argument.
It's not required.
It can be a constant, String or Symbol.
If specified — there is a coercion to specified type,
if not — data remains unchanged.

Nested forms — their class, as constant.
Type or `:initialize` block is required.

Formalism also supports `Array` type with the optional `:of` option
(type of elements).
Coercion will be applied to a data itself and to its elements.

#### Coercion

There is built-in coercion into some types, if you try to coerce
to undefined type — you'll get `Formalism::Form::NoCoercionError`.

You can define a coercion to some type via definition of such class:

```ruby
# frozen_string_literal: true

module Formalism
class Form < Action
class Coercion
## Class for coercion to String
class String < Base
private

def execute
@value&.to_s
end
end
end
end
end
```

### Default value

`field` and `nested` accepts `:default` option.
It can be any value, if it's an instance of `Proc` — it'll be executed
in the form instance scope.

### Different keys

`field` supports `:key` option (Symbol) to receive data by a different key,
not as a field name.

### Custom initialization of nested forms

By default, nested forms initialized with data by key as their name
in parent data. So, if a parent receive `{ foo: 1, bar: { baz: 2 } }`,
it's nested form `:bar` will receive `{ baz: 2 }`.

If you want to prevent initialization at all, or pass custom arguments —
you should use `:initialize` option which accepts a proc
with a form class argument.

If you want to just refine incoming data (add or remove) — you should define
`#params_for_nested_*` private method, where `*` is a nested form name.
You can use `super` inside.

### Order of filling with data

Fields and nested forms are filling in order of their definition.
But sometimes you want to change this order, for example,
if you have a nested forms in ancestors which depends on data in children forms.
For such cases you can use `:depends_on` option, which accepts fields
and nested forms names as Symbol or Array of symbols. They will be filled
(and initialized) before dependent.

### Merging into final data

There is `Form#fields_and_nested_forms` as final data
(after coercion, defaults, etc). But you may want to not include some fields
or nested forms into this data. You can do it via `:merge` option,
which can be `true`, `false` or `Proc` (executed in form's instance scope).

For example:

```ruby
field :bar, merge: true
nested :only_valid, nested_form_class, merge: ->(form) { form.valid? }
```

### Runnable

You can disable `#valid?` and `#run` of forms (including nested ones)
by setting `form.runnable = false`.
It can be helpful for some cases, for example, with policies (permissions):

```ruby
def initialize_nested_form(name, options)
return unless (form = super)

form.runnable = allowed_to_change?(name)
form
end
```

### Inheritance

Any `class ChildForm < ParentForm` will have all fields and nested forms
from `ParentForm`.

#### Removing (inherited) field

But you're able to remove (usually inherited) fields by:

```ruby
class ChildForm < ParentForm
remove_field :field_from_parent
end
```

#### Modules

You can define modules and use them later like this:

```ruby
module CommonFields
include Formalism::Form::Fields

field :base_field
nested :base_nested
end

class SomeForm < Formalism::Form
include CommonFields

field :another_field
end
```

### Convert to params

You can convert a Form back to (processed) params, for example, for view render:

```ruby
form = CreateAlbumForm.new(
name: 'Hits', tags: %w[Indie Rock Hits], artist: { name: 'Alex' }
)

form.to_params
# {
# name: 'Hits',
# tags: %w[Indie Rock Hits],
# artist: { name: 'Alex' }
# }
```

### Actions

For actions without fields, nesting and validation you can use
`Formalism::Action` (the parent of `Formalism::Form`).

### Plugins

There is a few plugins which I personally need for:

* [`formalism-model_forms`](https://github.com/AlexWayfer/formalism-model_forms)
Default CRUD forms for [Sequel](https://sequel.jeremyevans.net/) DB models.
Can be renamed!

* [`formalism-sequel_transactions`](https://github.com/AlexWayfer/formalism-sequel_transactions)
[Sequel](https://sequel.jeremyevans.net/) transactions inside forms.

* [`formalism-r18n_errors`](https://github.com/AlexWayfer/formalism-r18n_errors)
[R18n](https://github.com/r18n/r18n) errors inside forms, including validation helpers.
Can be separated!

## Development

After checking out the repo, run `bundle install` to install dependencies.

Then, run `toys rspec` to run the tests.

To install this gem onto your local machine, run `toys gem install`.

To release a new version, run `toys gem release %version%`.
See how it works [here](https://github.com/AlexWayfer/gem_toys#release).

## Contributing

Bug reports and pull requests are welcome on [GitHub](https://github.com/AlexWayfer/formalism).

## License

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