การเชื่อมโยง Rails ActiveRecord อัปเดตอย่างไม่สอดคล้องกัน

ฉันพบพฤติกรรม ActiveRecord ของ Rails 2.3.5 บางอย่างที่ฉันไม่เข้าใจ ดูเหมือนว่าออบเจ็กต์สามารถอัปเดตรหัสการเชื่อมโยงในลักษณะที่ไม่สอดคล้องกัน

นี่เป็นคำอธิบายที่ดีที่สุดด้วยตัวอย่าง:

สร้างโมเดล Post ด้วยแอตทริบิวต์สตริง 'title' และโมเดล Comment ด้วยแอตทริบิวต์สตริง 'content'

นี่คือสมาคม:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

สถานการณ์ #1: ในโค้ดต่อไปนี้ ฉันสร้าง Post อันหนึ่งโดยมี Comment ที่เกี่ยวข้องกัน สร้างอันที่สอง Post โดย find' โดยอันแรก เพิ่ม Comment อันที่สองเข้ากับอันแรก Post และค้นพบว่าอันที่สอง Post มีอันที่สอง Comment เชื่อมโยงอยู่ด้วยโดยไม่มี การมอบหมายที่ชัดเจน

post1 = Post.new
post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]
post2.comment_ids # => [12, 13]

สถานการณ์ #2: เรียกใช้คำสั่งข้างต้นอีกครั้ง แต่คราวนี้ใส่คำสั่งพิเศษหนึ่งคำสั่งที่ด้านหน้าของคำสั่งนั้น ไม่ควรส่งผลกระทบต่อผลลัพธ์ คำสั่งพิเศษคือ post2.comments ซึ่งเกิดขึ้น หลัง สร้าง comment2 และ ก่อน เพิ่ม comment2 ไปยัง post1

post1 = Post.new
post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')
post2.comments # !! THIS IS THE EXTRA COMMAND !!
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]
post2.comment_ids # => [14]

โปรดทราบว่ามีเพียง หนึ่ง ความคิดเห็นที่เกี่ยวข้องกับ post2 ในสถานการณ์นี้ ในขณะที่ในสถานการณ์ที่ 1 มีเพียงสองความคิดเห็น

คำถามสำคัญ: เหตุใดการรัน post2.comments ก่อนที่จะเพิ่ม Comment ใหม่เป็น post1 จึงสร้างความแตกต่างให้กับความคิดเห็นที่เกี่ยวข้องกับ post2


person rlandster    schedule 12.02.2010    source แหล่งที่มา


คำตอบ (1)


สิ่งนี้เกี่ยวข้องกับวิธีที่คำขอแคชของ Active Record และวิธีการจัดการการเชื่อมโยง has_many

เว้นแต่การเชื่อมโยงจะเต็มไปด้วยตัวเลือก :include ในระหว่างการค้นหา Rails จะไม่เติมการเชื่อมโยงสำหรับบันทึกที่พบจนกว่าจะจำเป็น เมื่อจำเป็นต้องมีการเชื่อมโยง memoization บางอย่างจะเสร็จสิ้นเพื่อลดจำนวนการสืบค้น SQL ที่ดำเนินการ

ก้าวผ่านรหัสในคำถาม:

post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1  # updates post1's internal comments cache
post1.save 

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1') 

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2   # updates post1's internal comments cache

# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]

# this is the first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE posts.id = #{post2.id}
post2.comment_ids # => [12, 13]

สถานการณ์ที่ 2:

post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')

# first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE 
#   posts.id = post2.comments #=> Returns one comment (id = 14)
# cached internally.

post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]

# post2.comment has already been cached, so the SQL query is not executed again.

post2.comment_ids # => [14]

เอ็นบี post2.comment_ids ถูกกำหนดภายในเป็น post2.comments.map(&:id)

ป.ล. คำตอบของฉันสำหรับ คำถามนี้ อาจช่วยให้คุณเข้าใจว่าทำไม post2 ถึงได้รับการอัปเดตแม้ว่าคุณจะไม่ได้แตะก็ตาม

person EmFi    schedule 13.02.2010
comment
ขอบคุณสำหรับคำตอบ. แต่พฤติกรรมนี้ดูเหมือนจะไม่ผิดใช่ไหม? การแคชควรปรับปรุงประสิทธิภาพ ไม่ทำให้เกิดความไม่สอดคล้องกันของผลลัพธ์ - person rlandster; 14.02.2010
comment
มันเป็นปัญหาที่เกิดขึ้นพร้อมกันมากกว่า Rails ไม่ได้คาดหวังว่าแหล่งภายนอกจะเปลี่ยนแปลงสิ่งต่าง ๆ ที่เกี่ยวข้องกับอินสแตนซ์ของโมเดล แหล่งที่มาภายนอกในกรณีนี้หมายถึงการกระทำใดๆ ที่มาจากที่อื่นนอกเหนือจากกรณีเฉพาะ หากเชื่ออย่างยิ่งว่าสิ่งนี้เป็นสิ่งที่ผิด ให้ยื่นรายงานข้อผิดพลาด - person EmFi; 14.02.2010