Rails has_many :through with collection_select for

2019-03-06 10:51发布

I'm having issues with creating a form that will save my has_many :through associations. I've successfully saved by posting json, but the forms just won't work for me yet. The request params created by the form submit just won't work out. Any help pointing me to the solution would help me from losing any more time on this. Thanks up front.

EDITED -- Added forms_for attempt and the created params json that doesn't work as well at the bottom --

Json post request params that works:

{
    "author": {
        "name": "Author Name",
        "post_authors_attributes": [
          {"post_id":"1"},
          {"post_id":"2"},
          {"post_id":"3"}
        ]
    }
}

Rails form generated params that don't save.

{
    "author": {
        "name": "assd",
        "post_authors_attributes": [
            "",
            "2",
            "3"
        ]
    }
}

...and the relevant code samples...

Author Model

class Author < ActiveRecord::Base
  has_many :post_authors
  has_many :posts, :through => :post_authors
  accepts_nested_attributes_for :post_authors
end

Post Model (Currently only working on the Author has many Posts, not the reverse)

class Post < ActiveRecord::Base
end

PostAuthor Model

class PostAuthor < ActiveRecord::Base
  belongs_to :post
  belongs_to :author
end

Author Controller new/create actions

  # GET /authors/new
  def new
    @author = Author.new
    @author.post_authors.build
  end

  # POST /authors
  # POST /authors.json
  def create
    @author = Author.new(params)

    respond_to do |format|
      if @author.save
        format.html { redirect_to @author, notice: 'Author was successfully created.' }
        format.json { render :show, status: :created, location: @author }
      else
        format.html { render :new }
        format.json { render json: @author.errors, status: :unprocessable_entity }
      end
    end
  end

authors/_form.html.erb

<%= form_for(@author) do |f| %>
  <% if @author.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@author.errors.count, "error") %> prohibited this author from being saved:</h2>

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

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>

    <%= collection_select(:author, :post_authors_attributes, Post.all, :id, :title,
                                     {include_blank: false, :selected => @author.posts.map(&:id)},
                                     {:multiple => true}) %>

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

Schema

ActiveRecord::Schema.define(version: 20150120190715) do

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

  create_table "post_authors", force: :cascade do |t|
    t.integer  "post_id"
    t.integer  "author_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

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

end

EDIT -- ADDED Details -- Just for due dilligence, I've also tried using a fields_for, but it produces even more messed up json that doesn't save to the database. I have no idea where the "0" key comes from. I'm stuck on this, any help would greatly be appreciated.

fields_for

  <div class="field">
    <%= f.fields_for :post_authors, @author.post_authors do |posts_form| %>
        <%= f.label :Posts %><br>
        <%= posts_form.collection_select(:post_id, Post.all, :id, :title,
                                         {include_blank: false, :selected => @author.posts.map(&:id)},
                                         {:multiple => true}) %>

    <% end %>
  </div>

Produced params to_json

{
    "author": {
        "name": "test",
        "post_authors_attributes": {
            "0": {
                "post_id": [
                    "",
                    "1",
                    "2",
                    "3"
                ]
            }
        }
    }
}

2条回答
闹够了就滚
2楼-- · 2019-03-06 11:23

authors/_form.html.erb:

<%= fields_for(@author_book) do |ab| %>
  <div class="field">
    <%= ab.label "All Books" %><br>
    <%= collection_select(:books, :id, @all_books, :id, :name, {:selected => @author.books.map(&:id)}, {multiple: true}) %>
  </div>
<% end %>

authors_controller.rb:

class AuthorsController < ApplicationController
  before_action :set_author, only: [:show, :edit, :update, :destroy]

  # GET /authors
  # GET /authors.json
  def index
    @authors = Author.all
  end

  # GET /authors/1
  # GET /authors/1.json
  def show
  end

  # GET /authors/new
  def new
    @author = Author.new
    get_books
    respond_to do |format|
    format.html
    format.json { render json: @author }
  end

  end

  # GET /authors/1/edit
  def edit
    get_books
  end
  # POST /authors
  # POST /authors.json
  def create

    @author = Author.new(author_params)
    params[:books][:id].each do |book|
      if !book.empty?
        @author.authorbooks.build(:book_id => book)
      end
    end

    #binding.pry
    respond_to do |format|
      if @author.save

        format.html { redirect_to @author, notice: 'Author was successfully created.' }
        format.json { render :show, status: :created, location: @author }
      else
        format.html { render :new }
        format.json { render json: @author.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /authors/1
  # PATCH/PUT /authors/1.json
  def update
    #binding.pry
    respond_to do |format|
      if @author.update(author_params)

        @author.books = []
        params[:books][:id].each do |book|
          if !book.empty?
            @author.books << Book.find(book)
          end
        end

        format.html { redirect_to @author, notice: 'Author was successfully updated.' }
        format.json { render :show, status: :ok, location: @author }
      else
        format.html { render :edit }
        format.json { render json: @author.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /authors/1
  # DELETE /authors/1.json
  def destroy
    @author.destroy
    respond_to do |format|
      format.html { redirect_to authors_url, notice: 'Author was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_author
      @author = Author.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def author_params
      params.require(:author).permit(:name,:authorbooks_attributes => [:id,:book_ids => []])

    end

    def get_books
      @all_books = Book.all
      @author_book = @author.authorbooks.build
    end

    # def create_params
    #   params.require(:authorbooks).permit(:author_id,book_id: [])
    # end
end
查看更多
别忘想泡老子
3楼-- · 2019-03-06 11:27

For anyone fighting the same kind of issue, I finally managed to get this to work with the following collection_select:

      <%= f.collection_select(:feature_ids, Feature.all, :id, :name,
                              {include_blank: false, :include_hidden => false, :selected => @property.features.map(&:id)},
                              {:multiple => true}) %>
查看更多
登录 后发表回答