[英]How to rollback all transactions in transaction block on error in 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 的所有信息輸入到一個大表格中。 當所有信息都正確時,我能夠正確保存記錄。 但是,如果任何模型中存在錯誤,我想回滾所有事務並呈現有錯誤的表單。
此主題已在其他地方討論過,但是,我無法使用我見過的方法回滾所有事務。 當前,如果模型之一出現 DB/ActiveRecord 錯誤,比如說 CameraSpec,那么之前創建的 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
。 我總是能夠捕捉到錯誤,但數據庫不會回滾。 我已經嘗試過使用和不使用開始救援語句。 我目前的嘗試是不嵌套事務,而是將它們提交到單個事務塊中,但這也不起作用。 這是我當前的代碼。
# === 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
我有一個任務模型,該模型具有以下關聯:
...
# === 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的所有信息輸入一種大格式。 當所有信息正確時,我能夠正確保存記錄。 但是,如果任何模型中都有錯誤,我想回滾所有事務並使用錯誤呈現表單。
在其他地方已經討論了該主題,但是,我無法使用已經看到的方法回滾所有事務。 當前,如果其中一個模型存在DB / ActiveRecord錯誤,例如CameraSpec,則不會回滾先前創建的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
所有的工作實際上都是由accepts_nested_attributes
聲明的 setter 自動完成的。 您只需將白名單參數的哈希或哈希數組傳遞給它,然后讓它做它的事情。
如果子對象無效,您可以使用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 %>
看看你的代碼,我可以看到你在開始救援塊的幫助下使用了 ActiveRecord::Base.transaction 塊。 但是 ActiveRecord::Base.transaction 支持救援塊,可以使用下面的代碼
ActiveRecord::Base.transaction do
# 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 Exception => 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.