rubymonolith / superform

Build highly customizable forms in Rails
MIT License
263 stars 14 forks source link

Support nested association forms (e.g. fields_for) #24

Open jlw opened 4 months ago

jlw commented 4 months ago

While I don't use nested forms frequently, I expect the functionality will be required in the app I'm currently testing superform with.

@bradgessler - I might have some time to help out with this in the coming weeks.

bradgessler commented 4 months ago

This should work if you call namespace(:attr_name) or collection(:attr_name). Did you try those?

Assuming that does what you want it to do, I think we have a docs problem. Is that something you'd be willing to tackle in a PR?

jlw commented 4 months ago

@bradgessler, I may just have tried it incorrectly. I have a User that has_one :search and accepts_nested_attributes_for :search, and in my User form I tried

row field(:name).input
...
namespace :search do
  row field(:sort_order).input
  ...
end

This did not pull in the value from the associated Search record and created <input name="user[sort_order]" ...> as opposed to what I had expected: <input name="user[search_attributes][sort_order]" ...>.

Is there another pattern I should try using for this scenario?

bradgessler commented 4 months ago

Oh I see! Yeah, I haven't looked at nested attributes. I'm pretty sure that will take a new component and modifications to the object mapper.

bradgessler commented 4 months ago

Oh and try something like this for your block:

row field(:name).input
...
namespace :search_attributes do |s|
  row s.field(:sort_order).input
  ...
end

I think this will render the form, but I'm not sure how it will handle fields that are removed or parameter permits.

jlw commented 4 months ago

FYI, that raises NoMethodError undefined method 'input' for #<Superform::Field:0x00000001185442a0 @key=:sort_order, @parent=#<Superform::Namespace ... which (based on a very limited reading of the gem's code) suggests to me that the nested namespace does not have everything the root namespace has.

mhmaguire commented 4 months ago

The issue here is that namespace is not passed ~Superform::Rails::Field~ Superform::Rails::Form::Field

see #27

nolantait commented 3 months ago

A note for those struggling with collection, it's not like namespace where it takes a block. The collection will take your block as a template and yield a namespace for each member of the model's collection.

It does this through an enumerator that needs to be enumerated with #each to yield the values and actually call your block:

class TestForm < ApplicationForm
  def template
    render field(:name).input(type: :text)
    render field(:email).input(type: :email)

    namespace(:contact) do |contact|
      render contact.field(:phone).input(type: :tel)
    end

    collection(:addresses).each do |address|
      render address.field(:street).input(type: :text)
    end
  end
end

This would create a field like something[addresses][0][street]. The index auto increments by one for each item.

bradgessler commented 3 months ago

A note for those struggling with collection, it's not like namespace where it takes a block. The collection will take your block as a template and yield a namespace for each member of the model's collection.

Yeah this is a really important distinction and I just noticed it's not in the README.md. If somebody wants to add a section that describes the difference between namespaces and collections I'd be happy to merge it.

nolantait commented 3 months ago

I've added some documentation to my other PR: https://github.com/rubymonolith/superform/pull/28

If you want me to extract it into a branch without everything else just let me know.

bradgessler commented 3 months ago

@nolantait thanks for opening a PR! I took a stab at writing this last night and finally committed this morning before seeing your message. I would ask for that to be split from the current PR you have, but since I wrote something would you mind looking at https://github.com/rubymonolith/superform?tab=readme-ov-file#namespaces--collections to see if it makes sense? Second set of eyes is insanely helpful for docs.

nolantait commented 3 months ago

Your writeup is awesome, much better than mine. I finally understand the field(:something).collection vs collection(:something). Had a hard time figuring that one out on my own.

I think the only part missing from collections is how to use accepts_nested_attributes_for. Even if you have plans to build it into the library, in the meantime I think a lot of Rails devs looking to migrate will want examples of the common use cases.

I've done it in my apps by remapping the collection key by renaming it to the expected *_attributes. Simple enough but showing its possible will go a long way while scanning the README.

bradgessler commented 3 months ago

What’s the use case for Rails nested attributes in Superform? I generally avoid that library so I genuinely don’t know.

if somebody on this thread has gotten nested attributes successfully with Superform and can clearly articulate the use case I’d be willing to merge a PR with a blurb about this into the README.

jlw commented 2 months ago

@bradgessler - the use case for nested attributes is when we have a closely-related (or even tightly-bound) set of data split across database tables and it makes sense to edit data from both tables together.

The specific case I was looking at when I opened this issue was where we have both predicate data like date of birth (some of our resources are limited by age) as well as accidental data like addresses (many of our resources are only shown to nearby users and we allow quickly re-searching between multiple addresses like home and work); we store those different sets in different tables but want one form for managing them.

An edge case I was working with last week was a core table that had grown to nearly 200 columns - most of those columns were not directly displayed to the end user in most scenarios so those "auxiliary" columns had been split out into a separate table. The main data collection form still needs to handle columns from both tables, so with vanilla Rails I would use nested attributes.

I think I've also seen nested attributes used for adding related has-many or HABTM records.

I try to avoid these scenarios as much as possible, but I think all of the interesting Rails apps I've worked with in the last decade have had at least one place where this pattern was used.