(개인 메모) Rails5 모델을 연결할 때 조심합시다.

전제



Rails에서 모델을 업데이트하는 코드를 쓰려고 생각 밖에 빠져서 변경하지 않는 관련시킨 모델도 로드시킨다

테이블 구조



이하 간단한 테이블을 만들어 보겠습니다.



우선 쓰자.



일단 자신이 생각하는대로 코드를 써 간다.
# app/models/product.rb

class Product < ApplicationRecord
  belongs_to :category
  belongs_to :main_image, class_name: 'PublicImage', foreign_key: :main_image_id

  validates :name, uniqueness: true
end

좋은 느낌에 코드를 쓰고 있지만. . .

그러나 문제가 일어난다...



기존 레코드를 업데이트하면 로그에 쓸모없는 쿼리가 나오지 않습니다.
Product.first.update(name: "test")
  Product Load (0.7ms)  SELECT  `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1
   (0.1ms)  BEGIN
  Category Load (0.6ms)  SELECT  `categories`.* FROM `categories` WHERE `categories`.`id` = 1 LIMIT 1
  PublicImage Load (0.2ms)  SELECT  `public_images`.* FROM `public_images` WHERE `public_images`.`id` = 1 LIMIT 1
  Product Exists (0.8ms)  SELECT  1 AS one FROM `products` WHERE `products`.`name` = BINARY 'black-ball' AND `products`.`id` != 1 LIMIT 1
   (0.2ms)  COMMIT
=> true

왜 Product 업데이트에만 Category와 PublicImage도 로드하고 있는지
조사해 보면 "Rails5에서 belongs_to 관련은 디폴트로 required: true가 된다"는 것을 알았습니다.

개선



required: false로 하고 싶을 때는 optional: true라고 쓸 수 있게 된다.
실감 required: true로 하고 싶습니다만, 그대로 쓰고 있으면 쓸데없는 query가 발생되고 있던 채 기분 나쁘습니다.

그럼, 다음의 쓰는 방법으로 해결합시다.
단순히 외래 키를 업데이트 할 때 유효성 검사를 수행합니다.
# app/models/product.rb

class Product < ApplicationRecord
  belongs_to :category, optional: true
  belongs_to :main_image, class_name: 'PublicImage', foreign_key: :main_image_id, optional: true

  validates :name, uniqueness: true

  validates :category, presence: true, if: :validate_category_presence?
  validates :main_image, presence: true, if: :validate_main_image_presence?

  private

  def validate_category_presence?
    new_record? || category_id_changed?
  end

  def validate_main_image_presence?
    new_record? || main_image_id_changed?
  end
end

결과



쉬운 개선이지만 속도의 효과는 대단합니다.
다음은 쉽게 검증하고 볼 수 있습니다.
a = Time.now
1000.times{Product.first.update(name: "test")}
b = Time.now
b - a

수정 전: 15.837107
수정 후: 5.362079

이상

좋은 웹페이지 즐겨찾기