【问题标题】:Edit with a belongs_to relationship使用 belongs_to 关系进行编辑
【发布时间】:2026-01-14 12:25:02
【问题描述】:

问题

我在编辑具有belongs_to 关系(extra_guest belongs_to age_table)的表单时遇到问题。

我能够创建一个新的 extra_guest 并将其分配给一个 age_table,但我无法让编辑/更新工作,因为我的更新函数返回一个 falseClass。--> @extra_guest.update(extra_guest_params).errors.full_messages 返回 undefined method `errors' for false:FalseClass

代码

型号

class ExtraGuest < ApplicationRecord
  belongs_to :age_table
  validates :age_table, presence: true
end

class AgeTable < ApplicationRecord
  belongs_to :park
  has_many :extra_guests, dependent: :destroy
  validates :name, :age_from, :age_to, presence: true
  validates_associated :extra_guests
end

class Attraction < ApplicationRecord
  belongs_to :park

  has_many :extra_guests, dependent: :destroy
  accepts_nested_attributes_for :extra_guests, allow_destroy: true

  validates :name, presence: true
end

class Park < ApplicationRecord
  has_many :attractions, dependent: :destroy
  has_many :age_tables, dependent: :destroy
  validates :name, :currency, presence: true
end

extra_guests_controller

def edit
    @extra_guest = ExtraGuest.find(params[:id])
    @age_table = @extra_guest.age_table
    @age_table_list = AgeTable.where(park: @attraction.park)
  end

  def update
    @extra_guest = @attraction.extra_guests.find(params[:id])
    @age_table = AgeTable.find(params[:age_table])
    authorize @extra_guest
    if @extra_guest = @extra_guest.update(extra_guest_params)
      redirect_to root_path
    else
      @attraction = Attraction.find(params[:attraction_id])
      @extra_guest = ExtraGuest.find(params[:id])
      @age_table_list = @attraction.park.age_tables
      render 'edit'
    end
  end

private
  def extra_guest_params
    params.require(:extra_guest).permit(:name, :age_table_id,
      extra_guest_prices_attributes: [:id, :name, :price_type, :start_date, :end_date, :price, :duration, :duration_min, :duration_max, :backend_only, :weekend_extra, :_destroy])
  end

视图/extra_guests/表单

<%= simple_form_for [@attraction, @extra_guest] do |f|%>
     <%= f.input :age_table, :as => :select, :selected => @age_table.id, :collection => @age_table_list.map {|u| [u.name, u.id]}, :include_blank => false %>
<% f.button :submit %>

错误信息+参数


Couldn't find AgeTable without an ID

{"utf8"=>"✓",
 "_method"=>"patch",
 "authenticity_token"=>"l8HMnVIRybZg==",
 "extra_guest"=>
  {"age_table"=>"104",
   "extra_guest_prices_attributes"=>
    {"0"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"20-09-2019", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"42"},
     "1"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"2019-10-16", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"43"}}},
 "commit"=>"Save new option",
 "attraction_id"=>"185",
 "id"=>"55"}

【问题讨论】:

    标签: ruby-on-rails activerecord simple-form


    【解决方案1】:

    首先,您说此代码@extra_guest.update(extra_guest_params).errors.full_messages 有错误,但您显示的代码没有该行。

    现在,update 方法如果失败则返回 false https://apidock.com/rails/ActiveRecord/Persistence/update

    这一行:

    @extra_guest = @extra_guest.update(extra_guest_params)
    

    如果失败会设置@extra_guest为false,你不需要设置@extra_guest,直接使用if @extra_guest.update(extra_guest_params)

    使用您命名但不在您显示的代码上的代码行,@extra_guest.update(extra_guest_params).errors.full_messages,如果有错误则@extra_guest.update(extra_guest_params) 将为假,因此找不到.errors 方法。

    你必须把它分成两行:

    @extra_guest.update(extra_guest_params) # after this, @extra_guest will have the errors hash set
    @extra_guest.errors.full_messages # call it on the object and not on the result value from the update method
    

    编辑:您允许age_table_id,但参数为age_table,请将参数名称也固定为age_table_id

    【讨论】:

    • 感谢您帮助调试。它起作用了,在我看到错误消息后,它似乎是一个验证错误。
    【解决方案2】:

    在我看来,您在定义 @attraction 之前尝试使用它。您可以通过在方法中将@attraction 的定义进一步向上移动来解决此问题,但我会将其移动到它自己的方法中,如下所示:

    private
    
    def attraction
       @attraction ||= Attraction.find(params[:attraction_id])
    end 
    

    然后您使用方法名称,它现在为整个控制器定义并在您使用它时调用(与实例变量相反,如果您在未定义它的情况下调用它,它将只是“nil”)。 ||= 允许该方法返回已定义的实例变量的现有值,而不是每次调用该方法时都运行查询。所以你更新操作的第一行是

    @extra_guest = attraction.extra_guests.find(params[:id])

    我会为你那里的其他实例变量做类似的事情(@extra_guest、@age_table 和@age_table_list 应该分别在私有方法中定义)。顺便说一句,为单个控制器使用大量实例变量(这个控制器中有 4 个,这很多)被认为有点代码味道,但你应该先做一些工作然后重构的东西。以后参考:https://thoughtbot.com/blog/sandi-metz-rules-for-developers

    【讨论】: