[RUBY] [Rails] Evitez le SELECT émis lors de la création d'un modèle avec appartient_to défini!

Problème

Saviez-vous que l'instruction SELECT est exécutée lorsque vous créez un modèle avec appartient_to défini dans Rails?

Par exemple, supposons que vous ayez le modèle suivant.

def User < ApplicationRecord
  has_many :reviews
end

def Review < ApplicationRecord
  belongs_to :user
  belongs_to :book
end

def Book < ApplicationRecord
  has_many :reviews
end

Si vous créez une révision à ce moment, SQL sera émis comme indiqué ci-dessous.

# id=1 utilisateur et un livre existent
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

Il est sélectionné pour les utilisateurs et les livres où appartient_to est défini.

La raison d'être SELECT est simple. appartient_to nécessite l'existence de modèles associés par défaut. Alors qu'en est-il des confirmations requises? Juste avant de le créer comme avant, il est sélectionné et vérifié.

Au fait, si l'existence du modèle associé est arbitraire, écrivez appartient_to: utilisateur, facultatif: vrai. Si vous l'écrivez ainsi, SELECT ne sera pas exécuté. Voir aussi le guide des rails https://railsguides.jp/association_basics.html#optional

Donc, le moyen d'éviter le SELECT mentionné dans le titre est de définir ʻoptional: true`! ... ne pas! !!

Bien sûr, SELECT ne sera plus émis, mais le réglage «optionnel: true» n'est pas approprié si le modèle associé est requis.

Solution

Alors, comment éviter SELECT sans définir ʻoptional: true`? Tout ce que vous avez à faire est de passer l'objet à créer.

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

En passant l'objet, l'existence peut être confirmée sans effectuer SELECT, donc l'instruction SELECT ne sera pas émise. Cependant, dans le cas de cet exemple, le nombre de SQL est le même car chaque modèle est sélectionné séparément juste avant !! Cependant, le nombre de SQL est complètement différent lors de la création de plusieurs avis du même utilisateur, par exemple.

Vous trouverez ci-dessous le code permettant de créer une revue des livres 1 à 5 pour user1 (partiellement omis pour améliorer la lisibilité). La première consiste à spécifier l'id et à créer.

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

Passez ensuite l'objet et créez-le.

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

Avec la deuxième implémentation, 10 SELECT peuvent être omis !! Gardez à l'esprit que le SQL émis dépend de votre connaissance ou non!

prime

Comme mentionné ci-dessus, l'existence d'appartient_to est vérifiée lors de la création, mais il semble que le modèle passé par create et le résultat de SELECT juste avant ne soient pas seulement utilisés pour le contrôle d'existence mais aussi correctement définis dans l'association.

SQL n'est pas émis même si vous faites référence à l'association du modèle créé par create.

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">
#Puisque le modèle passé au moment de la création est défini, SELECT n'est pas exécuté même si l'association est référencée.
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] Evitez le SELECT émis lors de la création d'un modèle avec appartient_to défini!
Règles de dénomination lors de la création de nouveaux contrôleurs et modèles avec rails
Un mémorandum lors de la création d'un service REST avec Spring Boot
[Révision] Lors de la création d'une application Web avec Rails, erreur de syntaxe, inattendue ')', expecting => ...]}% ","% # {params [: content]}% "]) ...
Sortie verbalisée de procédure approximative lors de la création d'une application avec Rails
Résumé du travail initial lors de la création d'une application avec Rails
Comment spécifier la base de données lors de la création d'une application avec des rails
Les enregistrements existants disparaissent lors de la construction du modèle has_one (rails)
[Rails] Création d'un champ de recherche
Comment renommer un modèle avec des contraintes de clé externes dans Rails
Créer un nouvel utilisateur avec des rails était en colère contre l'attribut inconnu "mot de passe" pour l'utilisateur.
Créer une application de minuterie avec de la boue
Précautions lors de la création de PostgreSQL avec docker-compose
Paramètres à définir lors de l'exploitation d'un environnement de production avec Rails
Une collection de méthodes souvent utilisées lors de la manipulation du temps avec TimeWithZone of Rails