Form classes
Subclass Forms::Base, declare fields where self IS the form, render it anywhere.
The shape#
A form class subclasses Forms::Base and implements one hook — #fields. Inside it, self is the form, so the whole builder surface (field, row, group, Input, submit, fields_for, …) is available as bare calls:
class UserForm < Forms::Base
def fields
field :email
row do
field :first_name
field :last_name
end
submit :primary
end
endrender UserForm.new(model: @user)The class renders Form's full chrome — derived action/method, CSRF token, method override, multipart encoding — then your declared fields. A subclass without #fields raises NotImplementedError.
Inherited defaults: form_options#
form_options sets class-level defaults — positional form modifiers and any keyword Form accepts. They are inherited and merged down the subclass chain: a child's defaults beat its parent's, and instance arguments beat both.
class ApplicationForm < Forms::Base
form_options :spaced, field_variants: [:primary]
end
class AdminForm < ApplicationForm
form_options theme: :plain # merged over the parent's
end
AdminForm.new(model: record, url: "/override") # instance args winThe render-time block#
A block passed at render time appends after the declared fields — handy for a page-specific hidden field or an extra action without subclassing:
render UserForm.new(model: @user) do |f|
f.Hidden(:return_to)
endTesting form classes#
A form class is a Phlex component — #call renders it to a string with no controller, no request, no view context:
it "renders the email field bound to the model" do
output = UserForm.new(model: User.new(email: "[email protected]")).call
expect(output).to include('name="user[email]"')
expect(output).to include('value="[email protected]"')
end