DEV Community
In Ruby on Rails applications, handling complex forms can become messy when too much logic is stuffed into models or controllers. This is where Form Objects come in—a design pattern that helps separate form-related logic from Active Record models, making our applications more maintainable and testable.
The Problem with Traditional Forms
By default, Rails encourages using Active Record models directly in forms. However, this approach has some downsides:
- Fat Models: Business logic and validation bloat the model, making it harder to maintain.
- Fat Controllers: Controllers become responsible for handling complex form submissions.
- Multiple Models in One Form: Standard Rails forms work well with single models, but handling multiple related models can be cumbersome.
To address these issues, we use Form Objects.
What is a Form Object?
A Form Object is a Plain Old Ruby Object (PORO) designed to handle form submissions. It encapsulates form-specific validations and persistence logic while keeping models and controllers clean.
When to Use a Form Object
- When a form involves multiple models.
- When you want to keep your controllers thin and models focused.
- When form validation differs from the database schema.
- When reusing form logic across multiple places in your application.
Implementing a Form Object in Rails
Let's walk through an example where we create a UserRegistrationForm
that handles user sign-ups along with profile information.
Step 1: Create the Form Object
Create a new file in app/forms/user_registration_form.rb
:
class UserRegistrationForm
include ActiveModel::Model
attr_accessor :name, :email, :password, :password_confirmation, :bio
validates :name, :email, :password, :password_confirmation, presence: true
validates :password, confirmation: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
def initialize(attributes = {})
super(attributes)
end
def save
return false unless valid?
ActiveRecord::Base.transaction do
user = User.create!(name: name, email: email, password: password)
Profile.create!(user: user, bio: bio)
end
true
rescue ActiveRecord::RecordInvalid
false
end
end
Step 2: Use the Form Object in the Controller
Modify the UsersController
to use the UserRegistrationForm
:
class UsersController < ApplicationController
def new
@form = UserRegistrationForm.new
end
def create
@form = UserRegistrationForm.new(user_params)
if @form.save
redirect_to root_path, notice: 'User registered successfully!'
else
render :new
end
end
private
def user_params
params.require(:user_registration_form).permit(:name, :email, :password, :password_confirmation, :bio)
end
end
Step 3: Update the View
Modify the new.html.erb
view:
<%= form_with model: @form, url: users_path do |form| %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :email %>
<%= form.email_field :email %>
</div>
<div>
<%= form.label :password %>
<%= form.password_field :password %>
</div>
<div>
<%= form.label :password_confirmation %>
<%= form.password_field :password_confirmation %>
</div>
<div>
<%= form.label :bio %>
<%= form.text_area :bio %>
</div>
<div>
<%= form.submit 'Register' %>
</div>
<% end %>
Benefits of Using Form Objects
- Separation of Concerns: Keeps models and controllers focused on their primary responsibilities.
- Improved Testability: You can test form logic independently.
- Easier Maintenance: Avoids bloated models with unnecessary validations.
Form Objects in Ruby on Rails provide a clean way to manage complex form submissions while keeping code organized. By encapsulating form logic in a dedicated class, we improve maintainability, readability, and re-usability in our applications.
If you're dealing with bloated models and controllers due to complex forms, consider adopting Form Objects as a structured solution!
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)