0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

(個人メモ) Rails5モデルの関連付けるとき 気をつけましょう

Posted at

前提

Railsでモデルを更新するようなコードを書こうとして、思いの外ハマって変更しない関連させていたモデルもロードさせる

テーブル構造

以下簡単なテーブルを作ってみます。

Untitled_Diagram_drawio_-_draw_io.png

とりあえず書いてみる

ひとまず自分の思うがままにコードを書いていく。

# 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

いい感じにコードを書いているが。。。

しかし、問題が起きる…

既存レコードを更新してみるとログで無駄なqueryが出ているな

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

以上

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?