Непоследовательно обновлены ассоциации Rails ActiveRecord
-
20-09-2019 - |
Вопрос
Я сталкиваюсь с некоторым поведением Rails 2.3.5 ActiveRecord, которое я не понимаю.Похоже, что идентификаторы ассоциации объекта могут обновляться непоследовательными способами.
Лучше всего это объясняется на примере:
Создать Post
модель с атрибутом string 'title'
и a Comment
модель с атрибутом string '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
?
Решение
Это связано со способом кэширования запросов 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]
Н.Б. post2.comment_ids
внутренне определяется как post2.comments.map(&:id)
P.S.Мой ответ на этот вопрос может помочь вам понять, почему post2 обновляется, несмотря на то, что вы его не трогаете.