Skip to the content.

Forms

Source: lib/cafe_car/form_builder.rb, lib/cafe_car/field_builder.rb, lib/cafe_car/field_info.rb. CafeCar::Controller sets CafeCar::FormBuilder as the default form builder — and the installer includes that concern in ApplicationController — so plain form_for/form_with gets all of this on every page, customer-facing forms included, with no cafe_car macro involved.

The default _form partial renders every field the policy permits — most form changes are permitted_attributes changes (see policies.md), not view changes.

Builder methods

= form_for object, url: href_for(object) do |f|
  = f.field :name                 -# label + input + hint + error, wrapped in a Field
  = f.field :price
  = f.association :category      -# searchable select (see below)
  = f.remaining_fields           -# every editable attribute not yet rendered
  = f.submit

Type inference

FieldInfo#input picks the input from the schema — string/decimal → text field, text/json → textarea, integer → number, boolean → checkbox, date/datetime → typed pickers, has_secure_password digests → password fields, ActiveStorage attachments → file field (multiple for has_many_attached), ActionText → rich text area, belongs_to/has_many → association select, accepts_nested_attributes_for → nested sub-forms.

Association selects

f.association :client renders a select capped at CafeCar.max_collection_options (default 100) options, enhanced with Tom Select typeahead that queries the model’s policy-scoped GET /clients/options?q=… feed — records past the cap stay reachable, hidden rows never leak, and without JS it degrades to a plain select. Option labels come from each record’s presented title.

Nested records

accepts_nested_attributes_for :line_items, allow_destroy: true plus the permit:

def permitted_attributes
  [ :number, :issued_on,
    line_items_attributes: [ :id, :_destroy, *policy(LineItem).permitted_attributes ] ]
end

The default form then renders add/remove-able line-item rows with no view code. f.fields_for :line_items (no block) renders each sub-record’s remaining_fields.

Overriding how a field type renders

f.field renders through a partial named after the field’s type — _<type>_field.html.haml, falling back to the generic _field.html.haml:

-# app/views/application/_string_field.html.haml  (app-wide for all string fields)
-# app/views/admin/products/_string_field.html.haml  (this resource only)
= field.wrapper do
  = field.label
  = field.input data: { controller: "autosize" }
  = field.error

The field local is a FieldBuilder: field.label / field.input / field.hint / field.error / field.info (the FieldInfo). Type names match the inference above: string, text, integer, boolean, date, datetime, password, attachment, nested, belongs_to, has_many, json.

(lib/cafe_car/inputs/ exists in the gem but is dormant — the live path is FieldBuilder + FieldInfo + these partials.)