Rails の has_one 関連付けは、トランザクションがロールバックされた後でも保存されます [クローズ]
概要
Rails 3 アプリを Rails 5 バージョンにアップグレードしています。 has_one とbelongs_to の関連付けがあります。トランザクションブロックにレコードを保存しています。 Rails 5では、トランザクションがロールバックされた場合にnullデータが「タスク」テーブルに挿入されることがわかりました。 Rails 3 の場合、これは起こりません。これは Rails 5 で予期される動作ですか?既存の機能を壊したくないので、Rails 5でもRails 3の動作を維持する方法はありますか?
以下は、この問題を発生させるサンプルコードです。
class Student < ApplicationRecord
has_one :task
end
class Task < ApplicationRecord
belongs_to :student
validates_presence_of :name
end
student = Student.first
student.name = nil
def student.testing
begin
ActiveRecord::Base.transaction do
t = Task.new
t.name = 'ddd'
t.student = self
t.save!
puts "raising exception"
raise "exception"
end
rescue => e
end
end
student.testing
student.save!
解決策
この例から、私が理解できる問題は 1 つだけです。それは、rails が関連付けの inverse_of を検出する方法に関するものです。
class Task < ApplicationRecord
belongs_to :student
end
class Student < ApplicationRecord
has_one :task, dependent: :destroy
end
>> Task.reflect_on_association(:student).inverse_of.name
=> :task
inverse_of が設定されているため、student を task.student に割り当てると、その逆も、student.task に割り当てられます。
student = Student.first
student.association(:task).target #=> nil
Task.new(student: student)
student.association(:task).target #=> #<Task:0x00007f2333a18ad0 id: nil, name: nil, student_id: 2>
# something to save ---------------------^
student.save!
# INSERT INTO "tasks" ("name", "student_id") VALUES (?, ?) [["name", nil], ["student_id", 2]]
タスクトランザクションをロールバックしたかどうかは問題ではありません。オブジェクトはまだstudent.taskに割り当てられており、親が保存されるときに新しい関連付けが保存されます。これはまったく同じことを行います:
student = Student.first
Task.transaction do
Task.create!(student: student)
raise ActiveRecord::Rollback
end
student.save!
TRANSACTION (0.1ms) begin transaction
Task Create (0.2ms) INSERT INTO "tasks" ("name", "student_id") VALUES (?, ?) [["name", nil], ["student_id", 2]]
TRANSACTION (0.1ms) rollback transaction
TRANSACTION (0.0ms) begin transaction
Task Create (0.1ms) INSERT INTO "tasks" ("name", "student_id") VALUES (?, ?) [["name", nil], ["student_id", 2]]
TRANSACTION (9.8ms) commit transaction
class Task < ApplicationRecord
belongs_to :student, inverse_of: false
end
has_many と同じように動作します。
Task.reflect_on_association(:student).inverse_of #=> nil
student = Student.first
student.association(:task).target #=> nil
Task.new(student: student)
student.association(:task).target #=> nil
# nothing to save -----------------------^
student.save!
class Task < ApplicationRecord
belongs_to :student
end
class Student < ApplicationRecord
has_many :tasks, dependent: :destroy
end
Task.reflect_on_association(:student).inverse_of # => nil
student = Student.first
student.association(:tasks).target #=> []
Task.new(student: student)
student.association(:tasks).target #=> []
# nothing to save ------------------------^
student.save!
class Task < ApplicationRecord
belongs_to :student, inverse_of: :tasks
end
Task.reflect_on_association(:student).inverse_of.name #=> :tasks
student = Student.first
student.association(:tasks).target #=> []
Task.new(student: student)
student.association(:tasks).target #=> [#<Task:0x00007f007d82b2c8 id: nil, name: nil, student_id: 2>]
# something to save ----------------------^
student.save!
# INSERT INTO "tasks" ("name", "student_id") VALUES (?, ?) [["name", nil], ["student_id", 2]]