← Back to Upcase

Dynamically creating fields in a nested form


(Scott Hollinshead) #1

I have a nested form that I am working on, that adds products to a job sheet.

At the moment It starts with 7 rows by doing 7.times { @job_sheet.products.new } in the new action.

I would like It so It starts with one but create an add button that can add more rows, and a remove to remove them.

I found this RailsCast which is exactly what I want to do.

However I have no idea what this is doing

module ApplicationHelper
  def link_to_add_fields(name, f, association)
    new_object = f.object.send(association).klass.new
    id = new_object.object_id
    fields = f.fields_for(association, new_object, child_index: id) do     |builder|
      render(association.to_s.singularize + "_fields", f: builder)
    end
    link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
  end
end

Can somebody please break this down for me so I can understand it, and is there a better or easier way of doing this?

Thanks

NOTE The code would not format correctly but it is at the bottom of the link I have provided.


(Derek Prior) #2

The code is confusing because it’s written to be useful in all settings. If you translate it to your models, it should become more clear.

f is your form builder.

f.object is the object that the form is for. In your case, @job_sheet.

f.object.send(association) is then equivalent to @job_sheet.products, which this code then calls klass.new on, which says “get me the class of that association and call the initializer”.

So all of the above lines just gave you a new object for the association and assigns it to new_object. Then the code goies about the call to fields_for. It sets the child_index to new_object.object_id simply because it’s a convenient unique identifier. Within the fields_for block there’s the call to render a partial which will follow the convention of the association singularized appended with _fields. So in your case, it’d be _product_fields.html.erb.

The code ends with the call to link_to to tie all of this together.

As I said, it’s a bit opaque because it’s written to be generally useful. I think it’s interesting to try to understand this code but if I were doing this today I’d probably go with cocoon. It’s API might be a bit easier to read too.


(Scott Hollinshead) #3

Thanks @derekprior things are a little more clear to me now. Is it possible to do this in a Rails console or IRB session, so I can see what each step is doing?

Thanks.


(Derek Prior) #4

You can play with the non-form related things in a console without a problem. To use the form stuff (form_for, fields_for, etc), you’ll need to include ActionView::Helpers in your console session.


(Scott Hollinshead) #5

Thanks again @derekprior I will give it a go.


(Scott Hollinshead) #6

@derekprior Is there a way I can do this without using a helper or a gem, that’s easier for me to understand?


(Derek Prior) #7

Not that I’m aware of. I’m no longer a pro subscriber to RailsCasts, but from what I can remember it lays out the ways you would go about building this yourself. You could eliminate some of the misdirection in the RailsCasts sample and hardwire it to your use case - that is - render a specific partial, call new on a specific relation rather than going through send, etc).

The one part of the code I didn’t explain yet is the link itself. It’s creating an HTML link that has as a data attribute HTML that represents the fields needed for the nested model. When the link is clicked, JavaScript grabs that html from the data attribute and puts it into the form (incrementing the id, I believe, so that it’s always unique as you keep clicking ‘add’).

Cocoon does something very similar. Last I checked instead of using a data attribute it uses a hidden div to the same effect.


(Scott Hollinshead) #8

@derekprior would this work?

def link_to_add_fields(name, job_sheet)
  new_object = job_sheet.products.new
  id = new_object.object_id 
  **not to sure what to do with the fields part**
  link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end

Also my form is a nested resource so my main form is <%= form_for([@company, @job_sheet]) do |form| %>


(Derek Prior) #9

That looks like a start. Then you need to do the same fields_for stuff done above (but you can hardcode your partial) and add the javascript, etc.

THis is really rather complicated. As I said, I think it’s great to try and figure out what’s going on here, but in production I’d probably rely on cocoon.