[RUBY] [Rails] Vermeiden Sie das SELECT, das beim Erstellen eines Modells mit Gehört_zu Definiert ausgegeben wird!

Problem

Wussten Sie, dass die SELECT-Anweisung ausgeführt wird, wenn Sie ein Modell mit Gehäusen erstellen, das in Rails definiert ist?

Angenommen, Sie haben das folgende Modell.

def User < ApplicationRecord
  has_many :reviews
end

def Review < ApplicationRecord
  belongs_to :user
  belongs_to :book
end

def Book < ApplicationRecord
  has_many :reviews
end

Wenn Sie zu diesem Zeitpunkt eine Überprüfung erstellen, wird SQL wie unten gezeigt ausgegeben.

# id=1 Benutzer und Buch existieren
irb(main):001:0> Review.create!(user_id: 1, book_id: 1)
   (0.5ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.5ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 14:31:27.343637', '2020-06-30 14:31:27.343637')
   (2.0ms)  COMMIT

Es wird für Benutzer und Bücher AUSGEWÄHLT, in denen Gehör_zu definiert ist.

Der Grund für die Auswahl ist einfach. Gehört zu, erfordert standardmäßig das Vorhandensein verwandter Modelle. Was ist also mit den erforderlichen Bestätigungen? Kurz bevor es wie zuvor erstellt wird, wird es AUSGEWÄHLT und auf Existenz überprüft.

Übrigens, wenn die Existenz des verwandten Modells willkürlich ist, schreiben Sie "Gehört zu: Benutzer, optional: Wahr". Wenn Sie es so schreiben, wird SELECT nicht ausgeführt. Siehe auch Rails Guide https://railsguides.jp/association_basics.html#optional

Um das SELECT im Titel zu vermeiden, setzen Sie "optional: true"! ... nicht! !!

Sicher, SELECT wird nicht mehr ausgegeben, aber die Einstellung "optional: true" ist nicht geeignet, wenn das zugehörige Modell erforderlich ist.

Lösung

Wie vermeidet man SELECT, ohne "optional: true" zu setzen? Sie müssen lediglich das zu erstellende Objekt übergeben.

irb(main):002:0> user = User.first
  User Load (0.8ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):003:0> book = Book.first
  Book Load (0.6ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
irb(main):004:0> review = Review.create!(user: user, book: book)
   (0.4ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 14:32:14.911478', '2020-06-30 14:32:14.911478')
   (2.0ms)  COMMIT

Durch Übergeben des Objekts kann die Existenz bestätigt werden, ohne SELECT auszuführen, sodass die SELECT-Anweisung nicht ausgegeben wird. In diesem Beispiel ist die Anzahl der SQLs jedoch gleich, da jedes Modell kurz zuvor separat ausgewählt wird !! Die Anzahl der SQLs ist jedoch völlig unterschiedlich, z. B. wenn mehrere Überprüfungen für denselben Benutzer erstellt werden.

Unten finden Sie den Code zum Erstellen einer Überprüfung der Bücher 1-5 für Benutzer1 (teilweise weggelassen, um die Lesbarkeit zu verbessern). Die erste besteht darin, die ID anzugeben und zu erstellen.

irb(main):039:0> user = User.first
irb(main):040:0> books = Book.where(id: [1, 2, 3, 4, 5])
irb(main):042:0> books.each do |book|
irb(main):043:1*   Review.create!(user_id: user.id, book_id: book.id)
irb(main):044:1> end
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)
   (0.3ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Review Create (0.8ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:10:00.009569', '2020-06-30 15:10:00.009569')
   (3.6ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 2 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 2, '2020-06-30 15:10:00.020722', '2020-06-30 15:10:00.020722')
   (1.8ms)  COMMIT
   (0.4ms)  BEGIN
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 3 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 3, '2020-06-30 15:10:00.029827', '2020-06-30 15:10:00.029827')
   (1.9ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 4 LIMIT 1
  Review Create (0.3ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 4, '2020-06-30 15:10:00.037725', '2020-06-30 15:10:00.037725')
   (1.7ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 5 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 5, '2020-06-30 15:10:00.045389', '2020-06-30 15:10:00.045389')
   (1.6ms)  COMMIT

Übergeben Sie dann das Objekt und erstellen Sie es.

irb(main):045:0> user = User.first
irb(main):046:0> books = Book.where(id: [1, 2, 3, 4, 5])
irb(main):047:0> books.each do |book|
irb(main):048:1*   Review.create!(user: user, book: book)
irb(main):049:1> end
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)
   (0.5ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:12:05.610003', '2020-06-30 15:12:05.610003')
   (2.8ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 2, '2020-06-30 15:12:05.617125', '2020-06-30 15:12:05.617125')
   (1.7ms)  COMMIT
   (0.3ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 3, '2020-06-30 15:12:05.622432', '2020-06-30 15:12:05.622432')
   (1.8ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 4, '2020-06-30 15:12:05.627957', '2020-06-30 15:12:05.627957')
   (2.0ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 5, '2020-06-30 15:12:05.634191', '2020-06-30 15:12:05.634191')
   (1.8ms)  COMMIT

Bei der zweiten Implementierung können 10 SELECTs weggelassen werden !! Beachten Sie, dass sich die ausgegebene SQL ändert, je nachdem, ob Sie dies wissen!

Bonus

Wie oben erwähnt, wird die Existenz von Gehorsam_to beim Erstellen überprüft, aber es scheint, dass das von create übergebene Modell und das Ergebnis von SELECT kurz zuvor nicht nur für die Existenzprüfung verwendet, sondern auch ordnungsgemäß in der Zuordnung festgelegt werden.

SQL wird auch dann nicht ausgegeben, wenn Sie sich auf die Zuordnung des durch create erstellten Modells beziehen.

irb(main):051:0> review = Review.create!(user: user, book: book)
   (2.3ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:20:01.755647', '2020-06-30 15:20:01.755647')
   (2.5ms)  COMMIT
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
#Da das zum Zeitpunkt der Erstellung übergebene Modell festgelegt ist, wird SELECT auch dann nicht ausgeführt, wenn auf die Zuordnung verwiesen wird.
irb(main):052:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):053:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">

Recommended Posts

[Rails] Vermeiden Sie das SELECT, das beim Erstellen eines Modells mit Gehört_zu Definiert ausgegeben wird!
Benennungsregeln beim Erstellen neuer Controller und Modelle mit Schienen
Ein Memorandum beim Erstellen eines REST-Service mit Spring Boot
[Review] Beim Erstellen einer Webanwendung mit Rails, Syntaxfehler, unerwartetes ')', Erwartung => ...]}% ","% # {params [: content]}% "]) ...
Grobe Prozedur verbalisierte Ausgabe beim Erstellen einer App mit Rails
Zusammenfassung der ersten Arbeiten beim Erstellen einer App mit Rails
So geben Sie db beim Erstellen einer App mit Rails an
Vorhandene Datensätze verschwinden beim Erstellen des has_one-Modells (Rails).
[Rails] Erstellen eines Suchfelds
So benennen Sie ein Modell mit externen Schlüsseleinschränkungen in Rails um
Das Erstellen eines neuen Benutzers mit Schienen war verärgert über das unbekannte Attribut "Passwort" für den Benutzer.
Erstellen einer Timer-App mit Schlamm
Vorsichtsmaßnahmen beim Erstellen von PostgreSQL mit Docker-Compose
Einstellungen, die beim Betrieb einer Produktionsumgebung mit Rails vorgenommen werden sollten
Eine Sammlung von Methoden, die häufig beim Bearbeiten der Zeit mit TimeWithZone of Rails verwendet werden