Rails 5 - has_many 通过和嵌套属性形式

Rails 5 - has_many through and nested attributes forms

我有两个模型 RequestTableLocation 都具有 has_many 到 RequestLocation table.

加入的关系

我正在尝试创建嵌套表单,但 table_location 数据未保存到数据库中。

如您所见,table_locations"=>["1"] 参数正在传递给创建操作但未保存。

感谢任何帮助。

控制台输出

Started POST "/requests" for 127.0.0.1 at 2017-06-07 10:35:26 -0400
Processing by RequestsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"mHV/xbmdfHmCAsi16KXlW+0bWVSkEo9SRVchdyPpL60o3m3SuKEt4nuUT4PJNEyCsWq3Nj4IWiCMlDbhiPewdA==", "request"=>{"concierge_name"=>"Alex", "concierge_number"=>"954-123-4567", "concierge_email"=>"alex@email.com", "client_name"=>"Adam", "client_number"=>"954-765-4321", "client_email"=>"adam@email.com", "hotel_employee"=>"0", "concierge_service"=>"0", "vip_promoter"=>"0", "arriving_with_client"=>"1", "client_alone"=>"0", "males"=>"", "females"=>"1", "table_minimum"=>"1000", "arrival_time(1i)"=>"2017", "arrival_time(2i)"=>"6", "arrival_time(3i)"=>"7", "arrival_time(4i)"=>"14", "arrival_time(5i)"=>"35", "table_locations"=>["1"], "comments"=>""}, "commit"=>"Submit"}
   (0.1ms)  BEGIN
  SQL (0.4ms)  INSERT INTO "requests" ("concierge_name", "concierge_number", "concierge_email", "client_name", "client_number", "client_email", "arriving_with_client", "people", "females", "table_minimum", "arrival_time", "comments", "created_at", "updated_at") VALUES (, , , , , , , , , , , , , ) RETURNING "id"  [["concierge_name", "Alex"], ["concierge_number", "954-123-4567"], ["concierge_email", "alex@email.com"], ["client_name", "Adam"], ["client_number", "954-765-4321"], ["client_email", "adam@email.com"], ["arriving_with_client", "t"], ["people", 1], ["females", 1], ["table_minimum", 1000], ["arrival_time", "2017-06-07 14:35:00"], ["comments", ""], ["created_at", "2017-06-07 14:35:26.658718"], ["updated_at", "2017-06-07 14:35:26.658718"]]
   (0.3ms)  COMMIT
Redirected to http://localhost:3000/thanks
Completed 302 Found in 8ms (ActiveRecord: 0.8ms)

app/models/request.rb

class Request < ApplicationRecord
    has_many :request_locations
    has_many :table_locations, through: :request_locations
end

app/models/table_locations.rb

class TableLocation < ApplicationRecord
    has_many :request_locations
    has_many :requests, through: :request_locations
end

app/models/request_location.rb

class RequestLocation < ApplicationRecord
    belongs_to :request
    belongs_to :table_location
end

app/controllers/requests_controller.rb

class RequestsController < ApplicationController
  before_action :set_request, only: [:show,
                                     :edit,
                                     :update,
                                     :destroy]

  before_action :authenticate_admin!, except: [:index,
                                               :new,
                                               :create]

  def index
    redirect_to root_path unless admin_signed_in?
    @requests = Request.search(params[:term], params[:filter], params[:page])
  end

  def show
  end

  def new
    @request = Request.new
  end

  def edit
  end

  def create
    @request = Request.new(request_params)
    @request.people = (@request.males || 0) + (@request.females || 0)

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

  def update
    respond_to do |format|
      if @request.update(request_params)
        format.html { redirect_to @request, notice: 'Request was successfully updated.' }
        format.json { render :show, status: :ok, location: @request }
      else
        format.html { render :edit }
        format.json { render json: @request.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @request.destroy
    respond_to do |format|
      format.html { redirect_to requests_url, notice: 'Request was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    def set_request
      @request = Request.find(params[:id])
    end

    def request_params
      params.require(:request).permit(:concierge_name,
                                      :concierge_number,
                                      :concierge_email,
                                      :client_name,
                                      :client_number,
                                      :client_email,
                                      :hotel_employee,
                                      :concierge_service,
                                      :vip_promoter,
                                      :arriving_with_client,
                                      :client_alone,
                                      :people,
                                      :males,
                                      :females,
                                      :table_minimum,
                                      :arrival_time,
                                      :comments,
                                      table_locations: [:id]
      )
    end
end

app/views/requests/_form.html.erb

...
<% TableLocation.all.each do |t| %>
    <%= check_box_tag "request[table_locations][]", t.id, @request.table_locations.include?(t.id) %>
    <%= t.location %>
    <br />
<% end %>
...

解释:

  • 您的 request_params 允许 table_locations: [:id],但这只允许以下格式:

    Parameters: {"utf8"=>"✓", ... "request"=>{"table_locations"=>{"id"=>"1"}, "comments"=>""}, "commit"=>"Submit"}
    

    但你的显示为:

    Parameters: {"utf8"=>"✓", ... "request"=>{"table_locations"=>["1"], "comments"=>""}, "commit"=>"Submit"}
    
    • 因此,试试这个:puts request_params inside the create method,你会注意到它没有 table_locations 值(即使你认为它在那里,但实际上没有),因为它没有 "properly" 在您的强参数 request_params.
    • 中列入白名单
  • 为了能够将多个 TableLocations 对象关联到新建的 Request 对象,格式应如下所示

    request = Request.new(table_location_ids: [1,2,3,4,5])
    

    但是从你的实施来看,你是这样做的(这是行不通的):

    request = Request.new(table_locations: [1,2,3,4,5])
    # => this will raise an error:
    # ActiveRecord::AssociationTypeMismatch: TableLocation expected, got Fixnum
    # however yours didn't raise an error, because it was not whitelisted in the request_params in the first place
    

解决方案:

requests_controller.rb

def request_params
  params.require(:request).permit(..., table_location_ids: [])
end

_form.html.erb

<% TableLocation.all.each do |t| %>
  <%= check_box_tag "request[table_location_ids][]", t.id %>
  <%= t.location %>
  <br />
<% end %>

推荐:

  • 以防万一您还不知道可以通过这种方式执行以下操作,我会将您的代码重构为如下所示:

    requests_controller.rb

    def create
      @table_locations = TableLocation.all
    end
    

    _form.html.erb

    <%= form_for @request do |f| %>
      <% @table_locations.each do |table_location| %>
        <%= f.check_box :table_location_ids, multiple: true, table_location.id %>
      <% end %>
    <% end %>