Rails 5 API - has_many 通过创建操作返回 2 条记录,尽管只有 1 条记录保存在数据库中

Rails 5 API - has_many through create action is returning 2 records although only 1 is persisted in the database

通过这 3 个模型的关联,我有以下 M2M

Customer -> Residences <- Properties

此外 属性 模型与地址相关:

class Address < ApplicationRecord
  has_one :property
end

在创建 属性 之前,客户将始终存在。 属性 通过提交地址创建。

这是控制器动作,除了成功渲染外总是起作用 returns 2 个属性(即基本上是 2 个住所记录)。

但是,数据库中只有一个。我知道它与 stale objects 有关,但不知道如何解决它。

我尝试添加 @customer.reload@customer.reload.residences 以及 @customer.reload.properties 但仍然得到 2 条记录。

  # POST /customers/:id/properties
  def create
    @customer = set_customer
    Customer.transaction do
      address = Address.find_by_place_id(address_params[:place_id]) 
      if address.nil?
        @property = @customer.properties.create
        @property.address = Address.new(address_params)
        if @property.save
          @customer.reload
          render json: @customer, status: :created
        else
          render json: @property.errors, status: :unprocessable_entity
        end
      else
        # irrelevant code to the problem
      end
    end
  end

def set_customer
  Customer.find(params[:customer_id])
end

关于 this question 的评论(来自@Swaps)表明使用 << 而不是 create 有时会导致重复,但无论我怎么做,我总是得到 2.

编辑

我设法强迫它像这样工作,但这感觉像是一个黑客:

    if @property.save
      @customer = set_customer
      render json: @customer, status: :created
    else

** 更新 - 模型 **

class Customer < ApplicationRecord
  has_many :residences
  has_many :properties, through: :residences
end

class Residence < ApplicationRecord
  belongs_to :customer
  belongs_to :property
end

class Property < ApplicationRecord
  belongs_to :address
  has_many :residences
  has_many :customers, through: :residences
end

class Address < ApplicationRecord
  has_one :property
  has_one :location # ignore this, not relevant
end

您正在尝试手动执行 ActiveRecord 可以使用 accepts_nested_attributes_for 自动执行的操作。它甚至适用于 has_many through 操作。

class Customer < ApplicationRecord
  has_many: :residences, inverse_of :customer
  has_many: :properties, through: :residences

  accepts_nested_attributes_for :residences
end

class Residence < ApplicationRecord
  belongs_to :customer
  belongs_to :property

  validates_presence_of :customer
  validates_presence_of :property

  accepts_nested_attributes_for :property
end

class Property < ApplicationRecord
  has_one :address
  has_many :residences
  has_many :customers, through: :residences

  accepts_nested_attributes_for :address
end

class Address < ApplicationRecord
  belongs_to :property
end

class CustomersController < ApplicationController
  def create
    customer = Customer.new(customer_params)
    if customer.save
      redirect_to customer, notice: 'Customer saved!'
    else
      render :new
    end
  end

  def customer_params
    params.require(:customer).permit(
      name:, ..., 
      residences_attributes: [
        property_attributes: [
          name, ..., 
          address_attributes: [
            street, city, state, postal_code, ...
          ]
        ]
      ]
    )
  end
end

参考文献:

你能试试这个吗?

def create
  @customer = set_customer
  Customer.transaction do
    address = Address.find_by_place_id(address_params[:place_id]) 
    if address.nil?
      @customer.properties.new(address_params)
      if @customer.save
        render json: @customer, status: :created
      else
        render json: @customer.errors, status: :unprocessable_entity
      end
    else
      # irrelevant code to the problem
    end
  end
end

我在想你真的需要@属性实例变量吗?是为了您的查看文件吗?

更新 1

您能否像这样添加您的客户和住宅模型:

客户模型

class Customer < ApplicationRecord
  has_many :residences
  has_many :properties, through: :residences
end

住宅模型

class Residence < ApplicationRecord
  belongs_to :customer
  belongs_to :property
end

问题是 ActiveRecord 中的陈旧对象与保存后数据库中的对象相比。

“.reload”不起作用,我不得不使用我的 hack 强制 ActiveRecord 再次在数据库中找到客户,并强制重新加载(我假设它使 AR 缓存无效):

  def create
    @customer = set_customer
    Customer.transaction do
      address = Address.find_by_place_id(address_params[:place_id]) 
      if address.nil?
        @property = @customer.properties.create
        @property.address = Address.new(address_params)
        if @property.save!
          @customer = set_customer # force reload from db
          render json: @customer, status: :created
        end
      else
        address.update!(address_params)
        if @customer.properties.find_by_id(address.property.id).nil?
          # although we updated the address, that is just a side effect of this action
          # the intention is to create an actual residence record for this customer
          @customer.properties << address.property
          @customer = set_customer # force reload from db
          render json: @customer, status: :created
        else
          @customer.errors.add(:customer, 'already has that property address')
          render json: ErrorSerializer.serialize(@customer.errors), status: :unprocessable_entity
        end
      end
    end
  end

  def set_customer
    Customer.find(params[:customer_id])
  end