Asosiasi Rails ActiveRecord diperbarui secara tidak konsisten

Saya mengalami beberapa perilaku Rails 2.3.5 ActiveRecord yang saya tidak mengerti. Tampaknya suatu objek dapat memperbarui id asosiasinya dengan cara yang tidak konsisten.

Hal ini paling baik dijelaskan dengan sebuah contoh:

Buat model Post dengan atribut string 'title' dan model Comment dengan atribut string 'content'.

Berikut asosiasinya:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

Skenario #1: Dalam kode berikut saya membuat satu Post dengan Comment terkait, membuat Post kedua dengan find pada yang pertama, menambahkan Comment kedua ke Post pertama dan menemukan bahwa Post kedua memiliki Comment kedua yang terkait dengannya tanpa tugas yang eksplisit.

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]

Skenario #2: Jalankan kembali perintah di atas tetapi kali ini masukkan satu perintah tambahan yang, secara sepintas, tidak akan mempengaruhi hasil. Perintah tambahannya adalah post2.comments yang muncul setelah membuat comment2 dan sebelum menambahkan comment2 ke 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]

Perhatikan bahwa hanya ada satu komentar yang dikaitkan dengan post2 dalam skenario ini sedangkan dalam Skenario 1 ada dua.

Pertanyaan Besar: Mengapa menjalankan post2.comments sebelum menambahkan Comment baru ke post1 akan membuat perbedaan pada Komentar mana yang dikaitkan dengan post2?


person rlandster    schedule 12.02.2010    source sumber


Jawaban (1)


Hal ini ada hubungannya dengan cara permintaan cache Rekaman Aktif dan cara penanganan asosiasi has_many.

Kecuali jika asosiasi tersebut dipenuhi dengan opsi :include selama pencarian. Rails tidak akan mengisi asosiasi untuk catatan yang ditemukan sampai diperlukan. Ketika pengaitan diperlukan, beberapa memoisasi dilakukan untuk mengurangi jumlah kueri SQL yang dieksekusi.

Menelusuri kode dalam pertanyaan:

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]

Skenario 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]

Catatan post2.comment_ids secara internal didefinisikan sebagai post2.comments.map(&:id)

P.S. Jawaban saya untuk pertanyaan ini mungkin membantu Anda memahami mengapa post2 diperbarui meskipun Anda tidak menyentuhnya.

person EmFi    schedule 13.02.2010
comment
Terima kasih atas jawabannya. Tapi bukankah perilaku ini kelihatannya salah? Caching harus meningkatkan kinerja, bukan menyebabkan inkonsistensi hasil. - person rlandster; 14.02.2010
comment
Ini lebih merupakan masalah konkurensi. Rails tidak mengharapkan sumber luar mengubah hal-hal yang terkait dengan instance model. Sumber luar dalam hal ini berarti segala tindakan yang berasal dari suatu tempat selain dari kejadian tertentu. Jika sangat yakin ini salah, ajukan laporan bug. - person EmFi; 14.02.2010