Rails has_one build association deletes existing record even if new record is not valid

醉酒当歌 提交于 2021-01-27 22:27:02

问题


I have seen that in Rails (5.2.1 at least) if you have a model with a has_one association to another model, the following happens:

class Car < ApplicationRecord
  has_one :steering_wheel
end

class SteeringWheel < ApplicationRecord
  belongs_to :car

  validate_presence_of :name
end

And I have an existing car object with a steering wheel. Then I try to build a new steering wheel like so:

car.build_steering_wheel

The new steering wheel I am trying to build is not valid because I did not set the name attribute. NEVERTHELESS, Rails has deleted my existing steering wheel record from the database! I understand and rely on build association deleting the existing record when building a new one, but not when the new record is not valid.

Anyone know how to get around this? I've tried rollback in a transaction, independently creating a steering wheel record and only doing car.steering_wheel = steering wheel if it's valid.. nothing works.


回答1:


ActiveRecord does not enforce validations on associated records by default.

You have to use 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

Additionally if you have setup a proper foreign key on steering_wheels.car_id the DB will not let you do car.build_steering_wheel as it would orphan a record:

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> 



回答2:


This is the prescribed functionality of the build_associated method for has_one associations. build_associated will delete the existing association regardless of whether the new association being built is valid or not. Therefore, do not use build_associated at all if there is any circumstance during the transaction where you want the old association to persist.



来源:https://stackoverflow.com/questions/53212840/rails-has-one-build-association-deletes-existing-record-even-if-new-record-is-no

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!