问题
I am using a nested form to post Urls and Texts. Via javascript, I am allowing any permutation of these two fields e.g. Url1 Text1 Text2 Url2 or Url1 Text1 Url2 Text2 etc
After submitting the form and redirecting to the index.html.view, I am then trying to keep the original order of whatever permutation I decide to use in the form i.e. if I decided to post Text1 Url1 Url2 Text2 Url3 I need that exact same arrangement to display in the browser.
Post Form Code
Post Model
class Post < ActiveRecord::Base
has_many :texts
has_many :urls
accepts_nested_attributes_for :texts, :urls
end
Text Model
class Text < ActiveRecord::Base
belongs_to :post
end
Url Model
class Url < ActiveRecord::Base
belongs_to :post
end
Schema
ActiveRecord::Schema.define(version: 20160215123916) do
create_table "posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "texts", force: :cascade do |t|
t.text "textattr"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "post_id"
end
add_index "texts", ["post_id"], name: "index_texts_on_post_id"
create_table "urls", force: :cascade do |t|
t.string "urlattr"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "post_id"
end
add_index "urls", ["post_id"], name: "index_urls_on_post_id"
end
Posts Controller
class PostsController < ApplicationController
def index
@posts = Post.includes(:texts, :urls).all.order(created_at: :desc)
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to '/posts'
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:texts_attributes => [:textattr], :urls_attributes => [:urlattr])
end
end
Index.html.erb (Unsuccessful Attempt)
<% @posts.each do |post| %>
<% post.texts.each do |text|%>
<%= text.textattr %> <br>
<% end %>
<% post.urls.each do |url|%>
<%= url.urlattr %> <br>
<% end %>
<% end %>
Index.html.erb (Unsuccessful Attempt)
<% @posts.each do |post| %>
<% [post.texts, post.urls].flatten.sort_by { |c| [c.created_at] }.each do |content| %>
<% if content.class.name == "Text" %>
<%= content.textattr %>
<% elsif content.is_a? Url %>
<%= content.urlattr %>
<% end %>
<% end %>
<% end %>
回答1:
As Richard says, you need to add an order
column (or position
- calling the column order
can cause confusion with the generated SQL).
I think the main issue is your coffeescript code. You need to generate a hidden input with each row you add, and use a single current_index
counter for row you add:
current_index = 0
addText = ->
html = """
<br>
<textarea 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}" />
"""
$("#new_post input[type='submit']").before(html)
current_index += 1
addUrl = ->
html = """
<br>
<input type="url" name="post[things_attributes][#{current_index}][url]" id="post_things_attributes_#{current_index}_url">
<input type="hidden" name="post[things_attributes][#{current_index}][order]" id="post_things_attributes_#{current_index}_order" value="#{current_index}" />
"""
$("#new_post input[type='submit']").before(html)
current_index += 1
In your index.html.erb
, order the Things
by the order
column:
<% @posts.each do |post| %>
<% post.things.order(:order).each do |thing| %>
<br>
<%= thing.try(:text) %>
<%= thing.try(:url) %>
<% end %>
<br>
<% end %>
It's also worth noting that this dynamic nested form pattern is very well-handled by the cocoon gem. I'd recommend taking a look and using that in your project.
回答2:
As per my previous answer, you definitely need to put your texts
and urls
objects into a single table/model. This will allow you to assign "order" values for them.
I would add an order
value to the texts
/ urls
and then use the child_index value to populate it:
#app/views/posts/new.html.erb
<%= form_for @post do |f| %>
<%= f.fields_for :meta do |t| %>
<%= t.text_field :textattr %>
<%= t.hidden_field :order, t.index %>
<% end %>
<%= f.submit %>
<% end %>
According to the docs:
When a collection is used you might want to know the
index
of each object into the array. For this purpose, theindex
method is available in theFormBuilder
object.<%= form_for @person do |person_form| %> ... <%= person_form.fields_for :projects do |project_fields| %> Project #<%= project_fields.index %> ... <% end %> ... <% end %>
--
This will allow you to pass the order
attribute through to your db:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
@post = Post.new post_params
@post.save
end
private
def post_params
params.require(:post).permit(meta_attributes: [:order, :textattr])
end
end
Update
I just tried this approach and can confirm it works (index
):
The only thing I would say is that you might not have an order
column in your model, although it should not come back with the merge error.
I notice you're using outdated javascript code, can you try without using that code and see what happens?
来源:https://stackoverflow.com/questions/35455261/how-to-get-the-output-of-a-nested-form-to-stay-in-its-exact-original-order-desp