Skip to content

Commit

Permalink
Add option for controlling when form fields should be nested under th…
Browse files Browse the repository at this point in the history
…eir parent (#1711)
  • Loading branch information
camertron authored Dec 15, 2022
1 parent 1568f7f commit b39dd62
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-flowers-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/view-components': patch
---

Add option for controlling when form fields should be nested under their parent
2 changes: 2 additions & 0 deletions lib/primer/forms/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Builder < ActionView::Helpers::FormBuilder

UTILITY_KEYS = Primer::Classify::Utilities::UTILITIES.keys.freeze

alias primer_fields_for fields_for

def label(method, text = nil, **options, &block)
super(method, text, classify(options).merge(generate_error_markup: false), &block)
end
Expand Down
27 changes: 25 additions & 2 deletions lib/primer/forms/dsl/form_reference_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,34 @@ module Forms
module Dsl
# :nodoc:
class FormReferenceInput < Input
attr_reader :ref_block, :fields_for_args, :fields_for_kwargs
attr_reader :ref_block, :fields_for_args, :fields_for_kwargs, :nested
alias nested? nested

def initialize(*fields_for_args, builder:, form:, **fields_for_kwargs, &block)
# Pass `nested: false` to prevent the referenced form fields from being treated as nested
# under the parent form's model. For example, consider these models:

# class User < ActiveRecord::Base
# has_many :addresses
# end

# class Address < ActiveRecord::Base
# belongs_to :user
# end

# A sign-up form might include fields from `User` as well as `Address`. Since addresses are
# associated with users, it's perfectly natural to accept the address fields as nested
# attributes. Rails will name each field accordingly. For example, the `street` field on
# `Address` will be named `user[address][street]`.

# For situations like this where an association exists between two models, the nested
# attributes approach works great. However sometimes all you want is to compose two forms
# together that aren't connected by an association. In such cases the fields will still
# include the name of the parent model, eg. `user[address][street]` instead of what we want,
# `address[street]`. To render the form independent of the parent, pass `nested: false`.
def initialize(*fields_for_args, builder:, form:, nested: true, **fields_for_kwargs, &block)
@fields_for_args = fields_for_args
@fields_for_kwargs = fields_for_kwargs
@nested = nested
@ref_block = block

super(builder: builder, form: form, **fields_for_kwargs)
Expand Down
2 changes: 1 addition & 1 deletion lib/primer/forms/form_reference.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<%= builder.fields_for(*@input.fields_for_args, **@input.fields_for_kwargs) do |fields| %>
<%= builder_or_view.primer_fields_for(*@input.fields_for_args, **@input.fields_for_kwargs) do |fields| %>
<%= render(@input.ref_block.call(fields)) %>
<% end %>
4 changes: 4 additions & 0 deletions lib/primer/forms/form_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class FormReference < BaseComponent
def initialize(input:)
@input = input
end

def builder_or_view
@input.nested? ? builder : @view_context
end
end
end
end
53 changes: 53 additions & 0 deletions test/lib/primer/forms/form_reference_input_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

require "lib/test_helper"

class Primer::Forms::FormReferenceInputTest < Minitest::Test
include Primer::ComponentTestHelpers

class UserForm < ApplicationForm
form do |user_form|
user_form.text_field(name: :first_name, label: "First name")
user_form.text_field(name: :last_name, label: "Last name")
user_form.fields_for(:address, nested: @nested) do |builder|
AddressForm.new(builder)
end
end

def initialize(nested:)
@nested = nested
end
end

class AddressForm < ApplicationForm
form do |address_form|
address_form.text_field(name: :street, label: "Street")
address_form.text_field(name: :city, label: "City")
address_form.text_field(name: :state, label: "State")
end
end

def test_nests_fields
render_in_view_context do
primer_form_with(scope: :user, url: "/foo") do |f|
render(UserForm.new(f, nested: true))
end
end

assert_selector "input[name='user[address][street]']"
assert_selector "input[name='user[address][city]']"
assert_selector "input[name='user[address][state]']"
end

def test_does_not_nest_fields
render_in_view_context do
primer_form_with(scope: :user, url: "/foo") do |f|
render(UserForm.new(f, nested: false))
end
end

assert_selector "input[name='address[street]']"
assert_selector "input[name='address[city]']"
assert_selector "input[name='address[state]']"
end
end

0 comments on commit b39dd62

Please sign in to comment.