ruby-on-rails - 如何在Ruby on Rails中出现错误时回滚事务块中的所有事务
问题描述
我有一个具有以下关联的任务模型:
...
# === missions.rb (model) ===
has_many :addresses, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :addresses
has_many :phones, as: :phoneable, dependent: :destroy
accepts_nested_attributes_for :phones
has_one :camera_spec, as: :camerable, dependent: :destroy
accepts_nested_attributes_for :camera_spec
has_one :drone_spec, as: :droneable, dependent: :destroy
accepts_nested_attributes_for :drone_spec
...
当用户创建任务时,他们将任务、电话、地址、CameraSpec 和 DroneSpec 的所有信息输入到一个大表单中。当所有信息都正确时,我能够正确保存记录。但是,如果任何模型中有错误,我想回滚所有事务并呈现有错误的表单。
该主题已在其他地方介绍过,但是,我无法使用我见过的方法回滚所有事务。目前,如果其中一个模型(比如 CameraSpec)出现 DB/ActiveRecord 错误,则之前创建的 Mission、Address 和 Phone 不会回滚。我尝试过嵌套事务,例如:
Mission.transaction do
begin
# Create the mission
Mission.create(mission_params)
# Create the Address
raise ActiveRecord::Rollback unless Address.transaction(requires_new: true) do
Address.create(address_params)
raise ActiveRecord::Rollback
end
...
rescue ActiveRecord::Rollback => e
...
end
end
我尝试抛出不同类型的错误,例如ActiveRecord::Rollback
. 我总是能够捕捉到错误,但数据库不会回滚。我已经尝试过使用和不使用 begin-rescue 语句。我目前的尝试是不嵌套事务,而是将它们提交到单个事务块中,但这也行不通。这是我当前的代码。
# === missions_controller.rb ===
def create
# Authorize the user
# Prepare records to be saved using form data
mission_params = create_params.except(:address, :phone, :camera_spec, :drone_spec)
address_params = create_params[:address]
phone_params = create_params[:phone]
camera_spec_params = create_params[:camera_spec]
drone_spec_params = create_params[:drone_spec]
@mission = Mission.new(mission_params)
@address = Address.new(address_params)
@phone = Phone.new(phone_params)
@camera_spec = CameraSpec.new(camera_spec_params)
@drone_spec = DroneSpec.new(drone_spec_params)
# Try to save the company, phone number, and address
# Rollback all if error on any save
ActiveRecord::Base.transaction do
begin
# Add the current user's id to the mission
@mission.assign_attributes({
user_id: current_user.id
})
# Try to save the Mission
unless @mission.save!
raise ActiveRecord::Rollback, @mission.errors.full_messages
end
# Add the mission id to the address
@address.assign_attributes({
addressable_id: @mission.id,
addressable_type: "Mission",
address_type_id: AddressType.get_id_by_slug("takeoff")
})
# Try to save any Addresses
unless @address.save!
raise ActiveRecord::Rollback, @address.errors.full_messages
end
# Add the mission id to the phone number
@phone.assign_attributes({
phoneable_id: @mission.id,
phoneable_type: "Mission",
phone_type_id: PhoneType.get_id_by_slug("mobile")
})
# Try to save the phone
unless @phone.save!
raise ActiveRecord::Rollback, @phone.errors.full_messages
end
# Add the mission id to the CameraSpecs
@camera_spec.assign_attributes({
camerable_id: @mission.id,
camerable_type: "Mission"
})
# Try to save any CameraSpecs
unless @camera_spec.save!
raise ActiveRecord::Rollback, @camera_spec.errors.full_messages
end
# Add the mission id to the DroneSpecs
@drone_spec.assign_attributes({
droneable_id: @mission.id,
droneable_type: "Mission"
})
# Try to save any DroneSpecs
unless @drone_spec.save!
raise ActiveRecord::Rollback, @drone_spec.errors.full_messages
end
# If something goes wrong, render :new again
# rescue ActiveRecord::Rollback => e
rescue => e
# Ensure validation messages exist on each instance variable
@user = current_user
@addresses = @user.addresses
@phones = @user.phones
@mission.valid?
@address.valid?
@phone.valid?
@camera_spec.valid?
@drone_spec.valid?
render :new and return
else
# Everything is good, so redirect to the show page
redirect_to mission_path(@mission), notice: t(".mission_created")
end
end
end
解决方案
这非常复杂,您完全误解了如何使用嵌套属性:
class MissionsController
def create
@mission = Mission.new(mission_attributes)
if @mission.save
redirect_to @mission
else
render :new
end
end
...
private
def mission_params
params.require(:mission)
.permit(
:param_1, :param_2, :param3,
addresses_attributes: [:foo, :bar, :baz],
phones_attributes: [:foo, :bar, :baz],
camera_spec_attributes: [:foo, :bar, :baz],
)
end
end
所有的工作实际上都是由声明的 setter 自动完成的accepts_nested_attributes
。您只需将白名单参数的散列或散列数组传递给它,让它做它的事情。
如果子对象无效,您可以使用以下方法阻止保存父对象validates_associated
:
class Mission < ApplicationRecord
# ...
validates_associated :addresses
end
这只是将错误键“电话无效”添加到对用户不太友好的错误中。如果要显示每个嵌套记录的错误消息,可以在使用时获取由表单构建器包装的对象fields_for
:
# app/shared/_errors.html.erb
<div id="error_explanation">
<h2><%= pluralize(object.errors.count, "error") %> prohibited this <%= object.model_name.singular %> from being saved:</h2>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
...
<%= f.fields_for :address_attributes do |address_fields| %>
<%= render('shared/errors', object: address_fields.object) if address_fields.object.errors.any? %>
<% end %>
推荐阅读
- python - 如何在 Ubuntu 服务器上手动部署 FastAPI?
- tableau-api - Azure Synapse 分析与 Tableau 的连接
- c++ - OpenCV GpuMat 将复杂矩阵逐元素相乘
- python - Heroku 无法捆绑 python-javabridge(找不到 javahome)
- c# - 在 AWS Lambda (C#) 中反序列化嵌套的 JSON 字符串
- javascript - 如何仅获取父元素的可见子元素?
- spring - Spring HATEOAS RepresentationModelAssembler 使用 Pageable 参数生成链接
- xslt-2.0 - 如何匹配xslt中的非字符串
- google-cloud-firestore - Firestore 功能将整个文档数据保存到 Algolia
- reactjs - 使用 CASL 和 graphql-shield 在 GraphQL 后端上的 RBAC 并与我的 React 前端共享规则