HyperActiveForm is a simple form object implementation for Rails.
Form objects are objects that encapsulate form logic and validations, they allow to extract the business logic out of the controller and models into specialized objects.
HyperActiveForm's form objects mimic the ActiveModel API, so they work out of the box with Rails' form helpers, and allow you to use the ActiveModel validations you already know.
This allows you to only keep strictly necessary validations in the model, and have business logic validations in the form object. This is especially useful when you want different validations to be applied depending on the context.
Add this line to your application's Gemfile:
gem'hyperactiveform'And then execute:
$ bundle install
Run the install generator:
$ rails generate hyper_active_form:install
this will create an ApplicationForm class in your app/forms directory. You can use it as a base class for your form objects.
You can generate a form and its tests with the following command:
$ rails generate form FooBar
This will create the FooBarForm
Here is an example of an HyperActiveForm form object:
classProfileForm < ApplicationForm# proxy_for is used to delegate the model name to the class, and some methods to the object# this helps use `form_with` in views without having to specify the urlproxy_forUser,:@user# Define the form fields, using ActiveModel::Attributesattribute:first_nameattribute:last_nameattribute:birth_date,:date# Define the validations, using ActiveModel::Validationsvalidates:first_name,presence: truevalidates:last_name,presence: truevalidates:birth_date,presence: true# Pre-fill the form if neededdefsetup(user)@user=userself.first_name=user.first_nameself.last_name=user.last_nameself.birth_date=user.birth_dateend# Perform the form logicdefperform@user.update!(first_name: first_name,last_name: last_name,birth_date: birth_date)endendThe controller would look like this:
classUsersController < ApplicationControllerdefedit@form=ProfileForm.new(user: current_user)enddefupdate@form=ProfileForm.new(user: current_user)if@form.submit(params[:user])redirect_toroot_path,notice: "Profile updated"elserender:edit,status: :unprocessable_entityendendAnd the view would look like this:
<%= form_with(model: @form) do |f| %><%= f.text_field :first_name %><%= f.text_field :last_name %><%= f.date_field :birth_date %><%= f.submit %><% end %>HyperActiveForm mimics a model object, you can use proxy_for to tell it which class and object to delegate to.
When using form_for or form_with, Rails will choose the URL and method based on the object, according to the persisted state of the object and its model name.
The first argument of proxy_for is the class of the object, and the second argument is the name of the instance variable that holds the object.
classProfileForm < ApplicationFormproxy_forUser,:@user# Will delegate to @userendIf you pass an url and method yourself, you don't need to use proxy_for.
setup is called just after the form is initialized, and is used to pre-fill the form with data from the object.
setup will receive the same arguments as the initializer, so you can use it to pass any data you need to the form.
classProfileForm < ApplicationFormdefsetup(user)@user=userself.first_name=user.first_nameself.last_name=user.last_nameself.birth_date=user.birth_dateendendWhen using submit or submit!, HyperActiveForm will first assign the form attributes to the object, then perform the validations, then call perform on the object if the form is valid.
The perform method is where you should do the actual form logic, like updating the object or creating a new one.
If the return value of perform is not truthy, HyperActiveForm will consider the form encountered an error and submit will return false, or submit! will raise a HyperActiveForm::FormDidNotSubmitError.
At any point during the form processing, you can raise HyperActiveForm::CancelForm to cancel the form submission, this is the same as returning false.
HyperActiveForm provides a method to add errors from a model and apply them fo the form.
This is useful when the underlying model has validations that are not set up in the form object, and you want them to be applied to the form.
classUser < ApplicationRecordvalidates:first_name,presence: trueendclassProfileForm < ApplicationFormproxy_forUser,:@userattribute:first_namedefsetup(user)@user=userself.first_name=user.first_nameenddefperform@user.update!(first_name: first_name) || add_errors_from(@user)endendThe power of HyperActiveForm is that you can use it to create forms that don't map to a single model.
Some forms can be used to create several models at once. Doing so without form objects can be tedious especially with nested attributes.
Some forms dont map to any model at all, like a simple contact form that only sends an email and saves nothing in the database, or a sign in form that would only validate the credentials and return the instance of the connected user.
One great example of such forms are search forms. You can use a form object to encapsulate the search logic :
classUserSearchForm < ApplicationFormattribute:nameattribute:emailattribute:min_age,:integerattr_reader:results# So the controller can access the resultsdefperform@results=User.allifname.present?@results=@results.where(name: name)endifemail.present?@results=@results.where(email: email)endifage.present?@results=@results.where("age >= ?",age)endtrueendendAnd in the controller:
classUsersController < ApplicationControllerdefindex@form=UserSearchForm.new@form.submit!(params[:user])@users=@form.resultsendendHyperActiveForm provides callbacks for assign_form_attributes and submit.
You can use these callbacks to run code before or after assigning the form attributes or before or after submitting the form.
classProfileForm < ApplicationForm# ...before_submit:do_something_before_submitbefore_assign_form_attributes:do_something_before_assign_form_attributesdefdo_something_before_submit# Do something before submitting the formenddefdo_something_before_assign_form_attributes# Do something before assigning the form attributesendend