MiniForm
Helpers for dealing with form objects and nested forms.
Installation
Add this line to your application's Gemfile:
gem 'mini_form'
And then execute:
$ bundle
Or install it yourself as:
$ gem install mini_form
Usage
class ProductForm
include MiniForm::Model
attributes :id, :name, :price, :description
validates :name, :price, :description, presence: true
# called after successful validations in update
def perform
@id = ExternalService.create(attributes)
end
end
class ProductsController < ApplicationController
def create
@product = ProductForm.new
if @product.update(product_params)
redirect_to product_path(@product.id)
else
render :edit
end
end
private
def product_params
params.require(:product).permit(:name, :price, :description)
end
end
Delegated attributes
Attributes can be delegated to a sub object.
class SignUpForm
include MiniForm::Model
attr_reader :account, :user
attributes :name, :email, delegate: :user
attributes :company_name, :plan, delegate: :account
validates :name, :email, :company_name, :plan, presence: true
def initialize
@account = Account.new
@user = User.new account: @account
end
def perform
user.save!
account.save!
end
end
form = SignUpForm.new
form.name = 'name' # => form.user.name = 'name'
form.name # => form.user.name
form.plan = 'free' # => form.account.plan = 'free'
form.plan # => form.account.plan
Nested validator
mini_form/nested
validator runs validations on the given model and copies errors to the form object.
class SignUpForm
include MiniForm::Model
attr_reader :account, :user
attributes :name, :email, delegate: :user
attributes :company_name, :plan, delegate: :account
validates :account, :user, 'mini_form/nested' => true
def initialize
@account = Account.new
@user = User.new account: @account
end
def perform
account.save!
user.save!
end
end
Nested models
Combines delegated attributes and nested validation into a single call.
class SignUpForm
include MiniForm::Model
model :user, attributes: %i(name email)
model :account, attributes: %i(company_name plan)
def initialize
@account = Account.new
@user = User.new account: @account
end
def perform
account.save!
user.save!
end
end
Auto saving nested models
Most of the time perform
is just calling save!
. We can avoid this by using model
's save
option.
class SignUpForm
include MiniForm::Model
model :user, attributes: %i(name email), save: true
model :account, attributes: %i(company_name plan), save: true
def initialize
@account = Account.new
@user = User.new account: @account
end
end
Before/after callbacks
class SignUpForm
include MiniForm::Model
# ... code
before_update :run_before_update
after_update :run_after_update
private
def run_before_update
# ...
end
def run_after_update
# ...
end
# alternatively you can overwrite "before_update"
def before_update
end
# alternatively you can overwrite "after_update"
def after_update
end
end
Using in forms
Using main_model
will delegate id
, to_param
, persisted?
and new_record?
to the model. Allowing you to use it in forms.
class SignUpForm
include MiniForm::Model
main_model :user
def initialize
@user = User.new(account: @account)
end
end
<% form_for SignUpForm.new %>
Delegating model attributes
class SignUpForm
include MiniForm::Model
model :user, attributes: %i(name email), read: %i(id)
def initialize
@user = User.new(account: @account)
end
end
form = SignUpForm.new
form.update! form_params
form.id # => delegates to `user.id`
form.id = 42 # => raises `NoMethodError`
Methods
Method | Description |
---|---|
.model | Defines a sub object for the form |
.attributes | Defines an attribute, it can delegate to sub object |
.attribute_names | Returns list of attribute names |
#initialize | Meant to be overwritten. By defaults calls `attributes=` |
#attributes= | Sets values of all attributes |
#attributes | Returns all attributes of the form |
#update | Sets attributes, calls validations, saves models and `perform` |
#update! | Calls `update`. If validation fails, it raises an error |
#perform | Meant to be overwritten. Doesn't do anything by default |
#before_update | Meant to be overwritten. |
#after_update | Meant to be overwritten. |
#before_assignment | Meant to be overwritten. |
#after_assignment | Meant to be overwritten. |
#transaction | If ActiveRecord is available, wraps `perform` in transaction. |
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Run the tests (
rake
) - Create new Pull Request