以单一形式使用has_many_through和accepts_nested_attributes_for

时间:2018-07-24 21:45:24

标签: ruby-on-rails ruby-on-rails-5

有时候,Rails让您开心地大笑,而让您绝望地哭泣。这是后者之一... 在我正在构建的一个更复杂的应用程序上为此苦苦挣扎之后,我构建了一个简单的应用程序来隔离我遇到的问题。 总结-帖子中有评论和标签。一个标签可以属于多个帖子,而一个帖子可以具有多个标签。我想要一个嵌套表格,让我可以一口气做到这一点。我正在使用复选框,因为在更复杂的应用程序中需要使用它们。

comment.rb

class Comment < ApplicationRecord
    belongs_to :post
end

post.rb

class Post < ApplicationRecord
    has_many :comments
    has_many :post_tags
    has_many :tags, through: :post_tags
    accepts_nested_attributes_for :comments
    accepts_nested_attributes_for :tags,  update_only: true
end

post_tag.rb

class PostTag < ApplicationRecord
    belongs_to :post
    belongs_to :tag
end

_form.html.erb

<%= form_with(model: post, local: true) do |form| %>
    <% if post.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>


  <h2>Comments</h2>
  <%= form.fields_for :comments do |fc| %>
    <%= fc.label :body %>
    <%= fc.text_field :body %>
    <br />
  <% end %>

  <h2>Tags</h2>
  <%= form.fields_for :tags do |ft| %>
    <%= ft.collection_check_boxes(:tag, Tag.all, :id, :tag) do |cb| %>
      <%= cb.label %>
      <%= cb.check_box %>
  <% end %>
    <br />
  <%  end %>


  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

posts_controller.rb

标准脚手架-除外

def new
    @post = Post.new
    @post.comments.build
    @post.tags.build
end


# Never trust parameters from the scary internet, only allow the white list through.
def post_params
    params.require(:post).permit(:title, comments_attributes: [:body, :id], tags_attributes: [{:tag=>[]}, :id])
end

这是collection_check_boxes生成的HTML

<input type="hidden" name="post[tags_attributes][0][tag][]" value="" />
    <label for="post_tags_attributes_0_tag_1">tag 1</label>
    <input type="checkbox" value="1" name="post[tags_attributes][0][tag][]" id="post_tags_attributes_0_tag_1" />

    <label for="post_tags_attributes_0_tag_2">tag 2</label>
    <input type="checkbox" value="2" name="post[tags_attributes][0][tag][]" id="post_tags_attributes_0_tag_2" />

    <label for="post_tags_attributes_0_tag_3">tag 3</label>
    <input type="checkbox" value="3" name="post[tags_attributes][0][tag][]" id="post_tags_attributes_0_tag_3" />

    <label for="post_tags_attributes_0_tag_4">tag 4</label>
    <input type="checkbox" value="4" name="post[tags_attributes][0][tag][]" id="post_tags_attributes_0_tag_4" />
<br />
<input type="hidden" value="3" name="post[tags_attributes][0][id]" id="post_tags_attributes_0_id" />

这是通过的

Parameters: {"utf8"=>"✓", "authenticity_token"=>"0eFWbwi3J6gHnyys6+V/aVymheWE37RWtqq0xm46/umASeyTqj6hTqmTrL4DdvV48yFmMgOtiiYorswTtyCbyw==", 
"post"=>{"title"=>"test post", "comments_attributes"=>{"0"=>{"body"=>"test comment", "id"=>"1"}}, 
"tags_attributes"=>{"0"=>{"tag"=>["", "1", "4"], "id"=>"3"}}}, "commit"=>"Update Post", "id"=>"1"}

问题出在tag_attribute上。当更新运行时,它将使用名称“ [”,“ 1”,“ 4”]”更新标签条目3,而不是更新表。我不知道为什么要选择id为“ 3”。 您可能希望Rails能够自动完成所有工作。当它起作用时,它是甜食。如果没有,那就太令人沮丧了。 关于我要去哪里的任何建议?我的怀疑在于collection_check_boxes或params.require。

非常感谢。

1 个答案:

答案 0 :(得分:0)

TL; DR:

  • 您的情况下不需要嵌套属性(请随时删除它们)

_form.html.erb:

<%= form_with(model: post, local: true) do |form| %>
  <!-- ... -->
  <!-- ... -->
  <%= form.collection_check_boxes(:tag_ids, Tag.all, :id, :tag) do |cb| %>
    <%= cb.label %>
    <%= cb.check_box %>
  <% end %>
<% end %>

posts_controller.rb:

# ...
# ...
def post_params
  params.require(:post).permit(
    :title,
    comments_attributes: [:body, :id],
    tag_ids: []
  )
end

说明:

无论何时考虑如何正确设置表单(即使具有嵌套属性),我通常通常都在rails console中进行。因此,请查看下面的有趣技巧,您会立即理解我的意思:)

# rails console
post = Post.first

# hmm I wonder what should we put inside the .update() below, for us to be able to set the associated tags?
post.update(...)

# ok, should be easy enough, let's try below?
post.update(tags: [Tag.find(1), Tag.find(3)])

# cool, above works right?, except... that we cannot use it because we are simulating `params` values,
# ... and `params` values do not have record Objects! but only primitives such as `String` and `Integer`

# hmm... so... why not use the primary keys, right? Let's try below
post.update(tag_ids: [1, 3])

很酷,终于成功了! :)  基本上,那么现在我们只需要以某种方式将上面的tag_ids: [1, 3]转换为表单的输入字段,然后我们应该期望像下面这样具有输入字段的name(请注意,上面的TL; DR应该已经看起来像这样

<input type='checkbox' name='post[tag_ids][]' value='1' checked>
<input type='checkbox' name='post[tag_ids][]' value='2'>
<input type='checkbox' name='post[tag_ids][]' value='3' checked>
<input type='checkbox' name='post[tag_ids][]' value='4'>
<input type='checkbox' name='post[tag_ids][]' value='9'>
<!-- OTHER REMAINING TAG IDS HERE. ETC... -->