From 08512576715d7b6232eb3236a9d46597722cec32 Mon Sep 17 00:00:00 2001 From: mhenrixon Date: Sat, 11 Jul 2026 17:37:12 +0200 Subject: [PATCH] feat(builder): hidden_field alias for Rails FormBuilder migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `form.hidden_field(:accepted_terms_document_id, value: x)` raised NoMethodError because Forms::Form has no snake_case Rails FormBuilder API — only the PascalCase `Hidden` escape hatch. Add `hidden_field` to PhlexForms::Builder as a snake_case alias delegating to the same model-bound `Field#hidden` path, so Rails hosts can migrate hidden-field call sites verbatim. An explicit `value:` still wins over the model's current value. Lives on the Builder mixin, so both Forms::Form and FieldsForBuilder (nested fields_for) get it. ## Test Coverage - renders a scoped hidden input honoring an explicit value: override - binds value from the model when none is passed - renders under the plain theme too (bare hidden input) ## Verification - [x] bundle exec rubocop lib spec passes - [x] bundle exec rspec passes (165 passed, 1 pending) --- README.md | 5 +++++ lib/phlex_forms/builder.rb | 7 +++++++ spec/forms/components_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/README.md b/README.md index 3ffad23..1ef40af 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,11 @@ f.Checkbox(:terms) ; f.Toggle(:notify) ; f.FileInput(:avatar) f.Label(:email) ; f.Hidden(:token) ; f.submit("Save", :primary) ``` +Migrating from Rails' `FormBuilder`? `f.hidden_field(:token, value: x)` is a +snake_case alias for `f.Hidden` — same model-bound path, and an explicit +`value:` wins over the model's current value — so those call sites port over +verbatim. + For a bespoke widget (date picker, tag field, remote select), wrap it in `f.Control` and bind through the public helpers — this is the supported path, not a fork reason: diff --git a/lib/phlex_forms/builder.rb b/lib/phlex_forms/builder.rb index a5cdb7c..7e40a09 100644 --- a/lib/phlex_forms/builder.rb +++ b/lib/phlex_forms/builder.rb @@ -175,6 +175,13 @@ def Hidden(name, **) render field_object(name).hidden(**) end + # Rails FormBuilder-compatible hidden field, for straight migration of + # `form.hidden_field(:token, value: x)` call sites. Same model-bound path as + # `Hidden`; an explicit `value:` wins over the model's current value. + def hidden_field(name, **) + render field_object(name).hidden(**) + end + def Label(name, text = nil, *modifiers, **, &) render field_object(name).label(text, *modifiers, **, &) end diff --git a/spec/forms/components_spec.rb b/spec/forms/components_spec.rb index 2ac480e..57179e7 100644 --- a/spec/forms/components_spec.rb +++ b/spec/forms/components_spec.rb @@ -65,6 +65,41 @@ def render_form_via(model, **args, &block) end end + describe "hidden_field (Rails FormBuilder migration aid)" do + it "renders a scoped hidden input with an explicit value: override" do + user = build_model(:user, name: "Ada") + + output = render_form(user) do |f| + f.hidden_field(:accepted_terms_document_id, value: 42) + end + + expect(output).to include('type="hidden"') + expect(output).to include('name="user[accepted_terms_document_id]"') + expect(output).to include('id="user_accepted_terms_document_id"') + expect(output).to include('value="42"') + end + + it "binds the value from the model when none is passed" do + user = build_model(:user, token: "abc123") + + output = render_form(user) { |f| f.hidden_field(:token) } + + expect(output).to include('type="hidden"') + expect(output).to include('name="user[token]"') + expect(output).to include('value="abc123"') + end + + it "renders under the plain theme too (bare hidden input)" do + user = build_model(:user, token: "abc123") + + output = render_form(user, theme: :plain) { |f| f.hidden_field(:token) } + + expect(output).to include('