【问题标题】:Rails has_one build association deletes existing record even if new record is not valid即使新记录无效,Rails has_one 构建关联也会删除现有记录
【发布时间】:2018-11-08 17:10:30
【问题描述】:

我已经看到,在 Rails(至少 5.2.1)中,如果您的模型与另一个模型具有 has_one 关联,则会发生以下情况:

class Car < ApplicationRecord
  has_one :steering_wheel
end

class SteeringWheel < ApplicationRecord
  belongs_to :car

  validate_presence_of :name
end

我有一个带有方向盘的现有汽车对象。然后我尝试像这样构建一个新的方向盘:

car.build_steering_wheel

我尝试构建的新方向盘无效,因为我没有设置名称属性。尽管如此,Rails 已经从数据库中删除了我现有的方向盘记录!我理解并依赖构建关联在构建新记录时删除现有记录,但在新记录无效时不会。

有人知道如何解决这个问题吗?我已经尝试在事务中回滚,独立创建方向盘记录,并且仅在 car.steering_wheel = 方向盘有效时才执行 .. 没有任何效果。

【问题讨论】:

  • 为什么要新建一个?有的话可以更新吗?
  • 确实如此。我这样做是因为控制器的编写方式是 form_tag 总是需要一个新记录。我可以重写控制器,以便它根据是新记录还是现有记录来处理创建或更新操作。
  • 我猜这个问题在很大程度上是一个 X&Y 问题,而实际问题是您的表单或控制器完全错误。
  • 不一定。如果新记录无效,build_associated 不应删除记录。
  • 重写现有方法会是更好的选择。

标签: ruby-on-rails activerecord associations


【解决方案1】:

默认情况下,ActiveRecord 不对关联记录进行验证。

你必须使用validates_associated:

class Car < ApplicationRecord
  has_one :steering_wheel
  validates_associated :steering_wheel
end

irb(main):004:0> Car.create!(steering_wheel: SteeringWheel.new)
   (0.3ms)  BEGIN
   (0.2ms)  ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Steering wheel is invalid
    from (irb):4

此外,如果您在 steering_wheels.car_id 上设置了正确的外键,DB 将不允许您执行 car.build_steering_wheel,因为它会孤立记录:

class CreateSteeringWheels < ActiveRecord::Migration[5.2]
  def change
    create_table :steering_wheels do |t|
      t.belongs_to :car, foreign_key: true
      t.string :name
      t.timestamps
    end
  end
end

irb(main):005:0> c = Car.create!(steering_wheel: SteeringWheel.new(name: 'foo'))
   (0.3ms)  BEGIN
  Car Create (0.7ms)  INSERT INTO "cars" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2018-11-08 18:53:11.107519"], ["updated_at", "2018-11-08 18:53:11.107519"]]
  SteeringWheel Create (2.4ms)  INSERT INTO "steering_wheels" ("car_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["car_id", 3], ["name", "foo"], ["created_at", "2018-11-08 18:53:11.110355"], ["updated_at", "2018-11-08 18:53:11.110355"]]
   (1.3ms)  COMMIT
=> #<Car id: 3, created_at: "2018-11-08 18:53:11", updated_at: "2018-11-08 18:53:11">
irb(main):006:0> c.build_steering_wheel
   (0.3ms)  BEGIN
   (0.6ms)  ROLLBACK
ActiveRecord::RecordNotSaved: Failed to remove the existing associated steering_wheel. The record failed to save after its foreign key was set to nil.
    from (irb):6
irb(main):007:0> 

【讨论】:

  • 如果您添加的关联无效,Validates_associated 不会阻止 build_associated 删除现有记录。换句话说,它不会停止:如果新方向盘无效,car.build_steering_wheel 会删除现有方向盘。
  • 正如我之前所说的 - 这是一个 X&Y 问题。如果您不想替换它,则首先不应使用 build_steering_wheel
  • 但如果我替换它的内容有效,我确实想替换它。
【解决方案2】:

这是 has_one 关联的 build_associated 方法的规定功能。 build_related 将删除现有的关联,无论正在构建的新关联是否有效。因此,如果在事务过程中有任何情况需要保留旧关联,则根本不要使用 build_associated。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-23
    • 1970-01-01
    • 2017-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多