A way to :
form_with
<%= form_with url: "/search", method: :get do |form| %>
<%= form.label :query, "Search for:" %>
<%= form.search_field :query %>
<%= form.submit "Search" %>
<% end %>
<%= form_with model: @book do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :author %>
<%= form.text_field :author %>
<%= form.submit %>
<% end %>
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
end
person = Person.new(age: 'thirty-three')
person.valid? # true
person.valid?(:account_setup) # false
class SomeController < ApplicationController
def user_params
params.require(:user).permit(:first_name, :last_name)
end
end
class SomeOtherController < ApplicationController
def user_params
params.require(:user).permit(:first_name, :last_name, :role)
end
end
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person
accepts_nested_attributes_for :addresses
end
class Address < ApplicationRecord
belongs_to :person
end
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person
accepts_nested_attributes_for :addresses
end
class Address < ApplicationRecord
belongs_to :person
end
My implementation of a form object for Rails
ActiveModel::Attributes
and ActiveModel::Validations
Intrepidd/hyperactiveform
class ProfileForm < ApplicationForm
end
class ProfileForm < ApplicationForm
proxy_for User, :@user
attribute :first_name
attribute :last_name
attribute :birth_date, :date
validates :first_name, :last_name, :birth_date,
presence: true
def setup(user)
@user = user
self.first_name = user.first_name
self.last_name = user.last_name
self.birth_date = user.birth_date
end
def perform
@user.update!(
first_name: first_name,
last_name: last_name,
birth_date: birth_date
)
end
end
class UsersController < ApplicationController
def edit
@form = ProfileForm.new(user: current_user)
end
def update
@form = ProfileForm.new(user: current_user)
if @form.submit(params[:user])
redirect_to root_path, notice: "Profile updated"
else
render :edit, status: :unprocessable_entity
end
end
<%= form_with(model: @form) do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.date_field :birth_date %>
<%= f.submit %>
<% end %>
class UserSearchForm < ApplicationForm
attribute :name
attribute :email
attribute :min_age, :integer
attr_reader :results
def perform
@results = User.all
if name.present?
@results = @results.where(name: name)
end
if email.present?
@results = @results.where(email: email)
end
if age.present?
@results = @results.where("age >= ?", age)
end
true
end
end
Allowing for dynamic forms : attributes are added, removed, updated on the fly without reloading the page.
export default class extends Controller {
static targets = ['form']
submit () {
if (!this.hasFormTarget) return
this.formTarget.requestSubmit()
}
}
Or be using AutoSubmit from Stimulus Components ✨
export default class extends Controller {
static targets = ['form']
submit () {
if (!this.hasFormTarget) return
this.formTarget.requestSubmit()
}
}
export default class extends Controller {
static targets = ['form']
submit () {
if (!this.hasFormTarget) return
this.formTarget.requestSubmit()
}
refresh() {
const hiddenInput = Object.assign(document.createElement('input'), {
type: 'hidden',
name: 'refresh_form',
value: '1'
})
this.formTarget.appendChild(hiddenInput)
this.formTarget.setAttribute('novalidate', 'novalidate')
this.submit()
}
}
<%= f.check_box :show_advanced,
data: {
action: "change->submit-form#refresh"
}
%>
<% if @form.show_advanced %>
<% # ... %>
<% end %>
def update
if params[:refresh_form]
@form.assign_form_attributes(params[:user])
render :edit, status: :unprocessable_entity
return
end
if @form.submit(params[:user])
redirect_to root_path, notice: "Profile updated"
else
render :edit, status: :unprocessable_entity
end
end
def update
@form = UserForm.new(current_user)
return if intercept_refresh(@form, :edit, params[:user])
if @form.submit(params[:user])
redirect_to root_path, notice: "Profile updated"
else
render :edit, status: :unprocessable_entity
end
end
private
def intercept_refresh(form, template, form_params)
if params[:refresh_form]
form.assign_form_attributes(form_params)
render template, status: :unprocessable_entity
true
end
end
<%= turbo_refreshes_with(method: :morph, scroll: :preserve) %>
<%= form_with(model: @form) do |f| %>
<%# ... %>
<% end %>
This will make sure focus states and scroll positions are preserved, making the refresh feel more natural.
aria-busy
aria-busy
attribute to the form while it's busy.
#form {
transition: opacity 0.2s ease-in;
}
#form[aria-busy="true"] {
opacity: 0.6;
}
// TailwindCSS:
// aria-busy:opacity-60 transition-[opacity] duration-200 ease-in
It's a trade-off between convenience and performance.
For me, the developer experience is greatly improved, and the downsides are negligible for most use cases.
Presentation & all links available here :