has_many through의 새로운 레코드를 저장할 때 조심하십시오.
9722 단어 루비RailsActiveRecord
has_many through
한 모델을 만지고 있어 빠진 이벤트가 있었으므로 씁니다. 다만, 빠짐을 회피하는 방법은 알고도, 왜 그러한 거동이 되는지는 아직 모릅니다. 누군가 가르쳐 주면 도움이됩니다.전제
모델
class User < ApplicationRecord
has_many :articles
has_many :game_users
has_many :games, through: :game_users
belongs_to :pinned_article, class_name: 'Article'
end
class Article < ApplicationRecord
belongs_to :user
end
class Game < ApplicationRecord
has_many :game_users
has_many :users, through: :game_users
end
class GameUser < ApplicationRecord
belongs_to :game
belongs_to :user
end
User 와 Game 은
has_many through
의 관계가 되고 있습니다. 그리고 User 와 Article 가 has_many
입니다. 또한 User.pinned_article
에서 Article 을 belongs_to
로 참조합니다.부모와 자식 관계를 결합하여 save
User 를 기점으로 하여 부모-자식 관계를 정리해 save 하려고 생각해, 이하와 같은 코드를 썼습니다.
# Game は予め作っておく
Game.create
u = User.new
a = u.articles.build
u.pinned_article = a
u.games << Game.last
u.save!
이렇게 하면 다음과 유사한 SQL이 발행됩니다.
BEGIN
INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2017-12-15 02:24:44', '2017-12-15 02:24:44')
INSERT INTO `articles` (`user_id`, `created_at`, `updated_at`) VALUES (35, '2017-12-15 02:24:44', '2017-12-15 02:24:44')
INSERT INTO `game_users` (`game_id`, `user_id`, `created_at`, `updated_at`) VALUES (9, 35, '2017-12-15 02:24:44', '2017-12-15 02:24:44')
UPDATE `users` SET `pinned_article_id` = 32 WHERE `users`.`id` = 35
INSERT INTO `game_users` (`game_id`, `user_id`, `created_at`, `updated_at`) VALUES (9, 35, '2017-12-15 02:24:44', '2017-12-15 02:24:44')
COMMIT
겉보기에 잘하고있는 것처럼 보이지만 마지막 SQL에서 왜인지
game_users
에 동일한 레코드가 중복 INSERT되었습니다. User.pinned_article
의 ID 를 반영하기 위해서, INSERT 후에 다시 UPDATE 가 달립니다만, 왠지 관계가 없는 GameUser 의 중간 테이블 데이터가 재차 INSERT 되고 있습니다.실제로는, 중간 테이블을 만들었을 경우 중복 데이터를 막기 위해서 DB 나 Rails 로 독특한 제약을 붙일 것이라고 생각하기 때문에, SQL 가 실패해 ROLLBACK 하고 버립니다.
class GameUser < ApplicationRecord
belongs_to :game
belongs_to :user
validates :user, uniqueness: { scope: [:game_id] }
end
예상한 움직임을 하는 코드
아래의 코드로 하면 기대했던 대로의 움직임을 합니다. 차이는
User.games
에 직접 append 하는 것이 아니라, 중간 테이블의 GameUser 를 build
하고 작성하는 곳입니다.u = User.new
a = u.articles.build
u.pinned_article = a
# build をして作る
gu = u.game_users.build
gu.game = Game.last
u.save!
이것으로 무사히 할 수 있었습니다. 기본은
build
계를 사용해 가는 것이 좋습니까.<<
참고로 ActiveRecord
<<
는 collection_proxy에 정의되어 있습니다.def <<(*records)
proxy_association.concat(records) && self
end
alias_method :push, :<<
alias_method :append, :<<
이
append
를 실행하면 부모 ID가 확인되면 즉시 INSERT가 실행됩니다. save
혹은 필요 없습니다.왜 이렇게 되는가
ActiveRecord의 움직임을 step 실행하면서 쫓아갔습니다. 하지만, 결론, 왜 그렇게 되는지는 시간 만에 해명할 수 없어… 무념. 또 시간을 만들어 조사를 하고 싶습니다.
save!
로 저장할 때 save_collection_association 가 불립니다만, 여기서 왠지 마지막에 중복 데이터가 건너 와 버린다고 한다.
Reference
이 문제에 관하여(has_many through의 새로운 레코드를 저장할 때 조심하십시오.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/zaru/items/d545c0baebeb8d40370b텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)