Связи Rails ActiveRecord некорректно обновлены - PullRequest
1 голос
/ 13 февраля 2010

Я сталкиваюсь с некоторыми Rails 2.3.5 Поведение ActiveRecord я не понимаю. Похоже, что у объекта могут быть обновлены идентификаторы ассоциации непоследовательным образом.

Это лучше всего объяснить на примере:

Создайте модель 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?

1 Ответ

2 голосов
/ 13 февраля 2010

Это связано с тем, как Active Record кэширует запросы, и с тем, как обрабатываются ассоциации has_many.

Если ассоциация не загружена с опцией: include во время поиска. Rails не будет заполнять ассоциацию для найденных записей, пока не потребуется. Когда требуется ассоциация, выполняется памятка , чтобы сократить количество выполненных 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]

N.B. post2.comment_ids внутренне определяется как post2.comments.map(&:id)

P.S. Мой ответ на на этот вопрос может помочь вам понять, почему post2 обновляется, несмотря на то, что вы его не трогали.

...