How to get the output of a nested form to stay in its exact original order, despite permutations, using the index action & index template?

蓝咒 提交于 2019-12-12 02:52:48

问题


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, the index method is available in the FormBuilder 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!