0

I made a form where a user can dynamically add nested form fields by clicking a text or url button. When the form is submitted, the content displays in the view template. However, when I go to edit (& hopefully then update) a post at /posts/id/edit, the post content does not appear in the edit view template - it's a blank page in the browser.

log of SQL

Would really appreciate your help! Thank you!

Post Model

class Post < ActiveRecord::Base
  has_many :things, dependent: :destroy
  accepts_nested_attributes_for :things
end 

Things Model

class Thing < ActiveRecord::Base
  belongs_to :post
end 

Schema

create_table "posts", force: :cascade do |t|
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

create_table "things", force: :cascade do |t|
 t.text     "text"
 t.string   "url"
 t.integer  "post_id"
end

Posts Controller

class PostsController < ApplicationController

def edit
    @post = Post.find(params[:id])
end 

def update
end 

new.html.erb

 <button id='addtext'>text</button>
 <button id='addurl'>url</button>

 <%= form_for @post, url: posts_path, html: { multipart: true } do |f| %>

 <%= f.fields_for :thing do |ff| %>
 <% end %> 
 <%= f.submit %>
 <% end %>

posts.coffee

 current_index = 0

 addText = ->
 html = """
 <div>
 <textarea placeholder="Write something..."     name="post[things_attributes][#{current_index}][text]"     id="post_things_attributes_#{current_index}_text"></textarea>
 <input type="hidden" name="post[things_attributes][#{current_index}]    [order]" id="post_things_attributes_#{current_index}_order" value="#{current_index}" />
 </div>
 """

$("#new_post input[type='submit']").before(html)
current_index += 1

$ ->
$('#addtext').on('click', addText)


current_index = 0
Turk
  • 232
  • 3
  • 16
domburford
  • 99
  • 13

1 Answers1

1

There are several problems with your structure:

  1. You don't have any fields in your form_for
  2. You're defining fields manually for fields_for (BIG no no).

form_for

When you create a form_for, Rails uses a form object to take data from your model, and output it into an HTML form.

This output is done at render-time, and will not change regardless of how much manual lifting you do with javascript:

<%= form_for @post do |f| %>
   # you need fields in here
   <%= f.submit %>
<% end %>

The way to debug this is to check the source of your page (Right-Click > View Source). If the <form> element is present, it means the form_for method is working:

enter image description here

Also, remember that form_for is a helper method. It takes arguments and outputs HTML for you to use. It's not magic. If it "doesn't show up", there has to be a logical reason for it.


fields_for

The second step is that your fields_for won't show up without having any objects to populate it.

You've also got a huge problem when you're manually adding new fields_for fields, which is basically an antipattern.

You'll need to do the following:

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def edit
    @post = Post.includes(:things).find(params[:id])
    @post.things.build unless @post.things.any?
  end
end

#app/views/posts/edit.html.erb
<%= form_for @post, url: posts_path, html: { multipart: true } do |f| %>

  <%= f.fields_for :things do |ff| %> #-> association name (plural)
     <%= ff.text_area :text, placeholder: "Write Something" %>
  <% end %> 

  <%= f.submit %>
<% end %>

fields_for populates with the associated objects from the parent you passed to form_for. Thus if you have not built any of the associative objects, you need to ensure at least one is built, in order for the fields to be populated accordingly.


Nested

Lastly, you need to appreciate how to dynamically append fields to the fields_for block.

Whilst your code is not incorrect, it's highly impractical. There are swathes of resources to point you in the right direction:

  1. RailsCasts Nested Forms
  2. Cocoon Gem (HIGHLY recommended)
  3. NoMethodError within nested model form (my own post)

In short, you need to use Ruby/Rails to build the fields_for methods you require, appending them to your form with ajax:

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def edit
    @post = Post.includes(:things).find params[:id]
    @post.things.build unless @post.things.any?
    respond_to do |format|
      format.js
      format.html
    end
  end
end

#app/views/posts/edit.html.erb
<%= form_for @post, authenticity_token: !request.xhr? do |f| %>
  <%= render partial: "things_fields", locals: { f: f } %>
  <% if !request.xhr? %>
    <%= button_to "Add Thing", edit_posts_path(@post), method: :get, remote: true %>
    <%= f.submit %>
  <% end %>
<% end %>

#app/views/posts/_things_fields.html.erb
<%= f.fields_for :things, child_index: Time.now.to_i do |ff| %>
  <%= ff.text_area :text, placeholder: "Write Something" %>
<% end %>

This will allow you to append the fields through a JS call:

#app/views/posts/edit.js.erb
$("<%=j render "edit" %>").data().appendTo("form#post);

The trick with this method is the use of child_index: Time.now.to_i (providing a unique id for each newly appended field).

--

Long post; you've got my email if you want me to go through it specifically with you.

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147