ActiveDryForm
Installation
Add this line to your application's Gemfile:
gem 'active_dry_form'
Under the hood ActiveDryForm uses dry-validation, dry-monads
Initialize form
ProductForm.new(params: { title: 'n', price: '120' })
ProductForm.new(params: { product: { title: 'n', price: '120' } })
ProductForm.new(record: Product.find(params[:id]), params:)
Attribute accessors
form.title # => 'n'
form.price # => '120'
form.price = '119' # => '119'
form[:price] # => '119'
form[:price] = '121' # => '121'
Methods
form.validate # => checks field validity
form.validator # => #<Dry::Validation::Result{:title=>"n", :price=>120, errors={:title=>["minimum length 2"]}...>
form.valid? # => false
form.persisted? # => true
form.errors # => {:title=>["minimum length 2"]}
form.base_errors = []
form.errors_full_messages # => ['Cannot be less than 2 words']
form.record # => #<Product:0x00007f05c27106c8 id: 1, title: 'name', price: 100, description: 'product'>
# form.data - is hash, casted to types, after validate
form.data # => {:title=>"n", :price=>120}
form.data[:price] # => 120
form.update # Failure(:invalid_form)
Methods form.update
and form.create
return Result monad
# app/forms/product_form.rb
class ProductForm < Form
fields :product do
params do
required(:title).filled(:string, min_size?: 2)
required(:price).filled(:integer)
optional(:description).maybe(:string)
optional(:upload_attachments).maybe(:array)
end
rule(:description) do
key.failure('Cannot be less than 2 words') if value.split.size < 2
end
end
action def update
# Here you can implement any save logic
record.update!(data)
Success(record)
end
end
In your controller
class ProductsController < ApplicationController
include Dry::Monads[:result]
def new
@form = ProductForm.new
end
def create # without monads
@form = ProductForm.new(params:)
@form.validate
if @form.valid?
Product.create!(@form)
redirect_to products_path
else
render :new
end
end
def edit
@form = ProductForm.new(record:)
end
def update # with monads
@form = ProductForm.new(record:, params:)
case @form.update
in Success(product)
redirect_to product
else
render :edit
end
end
private def record = Product.find(params[:id])
end
in your view (slim for example)
/ app/views/products/new.slim
- active_dry_form_for @form do |f|
= f.input :title
= f.input :price
= f.input_select :status, Product::STATUSES.values
= f.input_file :upload_attachments, multiple: true, label: false
= f.button 'Submit'
Fill attributes init values
In your controller
def new
@form = ProductForm.new(params: { title: 'name', price: 120 })
@form.attributes = { title: 'new name', price: 100 }
@form.description = 'product description'
end
or like this
def new
@form = ProductForm.new
@form.create_default(params[:title])
# class ProductForm < Form
# ...
# def create_default(title)
# self.title = title
# end
# end
end
Inputs
Inputs
under the hood uses standard rails builder form methods, and you can use it on form.
input method automatically determines tag type:
- by dry type predicates - date, time, integer, number, string, boolean
- by field name - password, email, telephone, url
- active_dry_form_for @form, html: { 'data-controller': 'product'} do |f|
= f.input :title, 'data-product-target': 'title', readonly: true,
= f.show_error(:title)
= f.input_select :category_id, Category.pluck(:name, :id),
{ include_blank: true },
{ label: false, multiple: true, style: 'max-width:unset;'}
= f.input_check_box :is_discount
= f.input_check_box_inline :is_sale
= f.input_text :shipper_name
= f.input_text_area :description
= f.input_hidden :admin_id
= f.input_file :upload_attachments, multiple: true, label: false
= f.input_date :date
= f.input_datetime :date_time
= f.input_integer :category_id
= f.input_number :number
= f.input_password :password
= f.input_email :email
= f.input_url :url
= f.input_telephone :telephone
// standard rails builder form methods
= f.label :name
= f.search_field :name
= f.button 'Submit'
Create custom input
# lib/acitve_dry_form/builder.rb
module ActiveDryForm
class Builder
def input_date_native(field, options = {})
wrap_input(__method__, field, options) { |opts| date_field(field, opts) }
end
end
end
The form can be nested
class NestedDryForm < Form
class BookmarkForm < Form
fields(:bookmark) do
params do
required(:url).filled(:string)
optional(:id).maybe(:integer)
optional(:name).maybe(:string)
end
end
end
fields :product do
params do
required(:title).filled(:string, min_size?: 2)
required(:price).filled(:integer)
optional(:description).maybe(:string)
optional(:upload_attachments).maybe(:array)
optional(:bookmarks).array(Dry.Types.Constructor(BookmarkForm) { |params| BookmarkForm.new(params: params) })
end
end
action def update
bookmarks_data = data.delete(:bookmarks)
record.attributes = data
record.bookmarks_attributes = bookmarks_data if bookmarks_data
record.save!
Success(record)
end
end
As you noticed in the above example, we use the construction Dry.Types.Constructor(BookmarkForm) { |params| BookmarkForm.new(params: params) }
,
what it is dry types
you can find out here
Internationalization
By default, uses i18n_key
from fields(:user)
, but you can set a custom key using fields(:profile, i18n_key: :user)
.
Using standard rails i18n path:
ru:
helpers:
label:
user:
id: Идентификатор
name: Имя
or translations for your model attribute names
ru:
activerecord:
attributes:
user:
id: Идентификатор
name: Имя
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 the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_dry_form.
License
The gem is available as open source under the terms of the MIT License.