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
通过这 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