首页 > 解决方案 > 我的#build 方法没有保存到数据库

问题描述

我希望我能很好地解释这一点。我正在尝试在我的 rails 应用程序中构建一个表单来创建一个航班,并为其分配一个飞行员。我不断收到“nil:NilClass 的未定义方法‘build’”消息。

这是我的飞行模型

class Flight < ApplicationRecord
    belongs_to :pilot
    belongs_to :passenger
    accepts_nested_attributes_for :pilot

end

这是我的试点模型

class Pilot < ApplicationRecord
    has_many :flights
    has_many :pilots, through: :flights
    
    
end

我怀疑顺便说一句,这可能与我建立人际关系的方式有关,但很难证明这一点。

当我第一次开始制作它时,这就是我在我的 FlightController 中的#new 方法所拥有的

def new
        @flight = Flight.new
        @flight.pilot.build
end

但是我最初确实将“@flight.pilot.build”行移到了我的#create方法,因为这是将它保存到数据库的地方,但它仍然给了我相同的“未定义方法`build'为nil:NilClass”

def create
        @flight = Flight.create(flight_params)
        @flight.pilot.build
        if @flight.save
            redirect_to flight_path(@flight)
        else
            render 'new'
        end
    end


private

    def flight_params
        params.require(:flight).permit(:flight_number, :destination, pilot_attributes: [:name, :rank])
    end

这是我试图在其上执行创建飞行任务的视图。

<h1>Create New Flight</h1>

<%= form_for(@flight) do |f|%>
<div>
<%= f.label :flight_number %>
<%= f.text_field :flight_number %>
</div><br>

<div>
<%= f.label :destination %>
<%= f.text_field :destination %>
</div><br>

<div>
<label for="flight_pilot_id">Assign a pilot:</label>
<%= f.collection_select :pilot_id, Pilot.all, :id, :name, {:prompt => "Select a Pilot"} %>
</div><br>


    <h3>Or Create new Pilot:</h3>
    <%= f.fields_for :pilot, Pilot.new do |pilot_attributes|%>
        <%= pilot_attributes.label :name, "Pilot name:" %>
        <%= pilot_attributes.text_field :name %>
        <br>
        <%= pilot_attributes.label :rank, "Rank:" %>
        <%= pilot_attributes.text_field :rank %>
        <br>
    <% end %>



<%= f.submit %>

<% end %>

我究竟做错了什么?

标签: ruby-on-rails

解决方案


与关联不同,您不能从关联中has_many创建新实例,has_one/belongs_to因为当您尚未分配关联时它为 nil。

相反,has_one/belongs_to类方法生成以下方法:

                                  |            |  belongs_to  |
generated methods                 | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other                             |     X      |      X       |    X
other=(other)                     |     X      |      X       |    X
build_other(attributes={})        |     X      |              |    X
create_other(attributes={})       |     X      |              |    X
create_other!(attributes={})      |     X      |              |    X
reload_other                      |     X      |      X       |    X

您的控制器实际上应该是:

def new
  @flight = Flight.new(flight_params)
  # this just seeds the inputs
  @flight.build_pilot 
end

def create
  @flight = Flight.new(flight_params)
  if @flight.save
    redirect_to @flight
  else
    # this just seeds the inputs
    @flight.build_pilot unless @flight.pilot 
    render :new
  end
end

正如您在此处看到的,构建实际上与插入数据库几乎没有关系。相反,它只是填充表单中的字段,如果关联为零,这些字段将不存在。嵌套属性负责实际创建飞行记录。不要提供第二个参数,fields_for因为它不提供默认值 - 它会覆盖那里的任何现有记录。

<%= f.fields_for :pilot do |pilot_attributes| %>
  <%= pilot_attributes.label :name, "Pilot name:" %>
  <%= pilot_attributes.text_field :name %>
  <br>
  <%= pilot_attributes.label :rank, "Rank:" %>
  <%= pilot_attributes.text_field :rank %>
  <br>
<% end %>

如果要分配现有记录,只需传递pilot_id参数并将其列入白名单:

<%= form_with(model: @flight) do |f| %>
  <%= f.collection_select(:pilot_id, Pilot.all, :name, :id) %>
<% end %>
def flight_params
  # be nice to maintainers and don't write your strong parameters 
  # in one mega-line
  params.require(:flight)
        .permit(
          :flight_number, :destination, :pilot_id,
          pilot_attributes: [:name, :rank]
        )
end

但是,如果用户同时传递了 Pilot_id 和嵌套属性,这可能会导致问题,因此您需要处理这种可能性。


推荐阅读