[RUBY] [Rails] Implémenter un compteur qui compte la catégorie parente lors de l'enregistrement d'une catégorie enfant (ascendance, counter_culture)

Lorsqu'il existe une catégorie à plusieurs niveaux comme viande> poulet> viande Après avoir enregistré la catégorie de viande dans la recette, j'ai implémenté une fonction pour compter la catégorie parent viande> poulet également.

Je n'ai pas pu trouver un article qui ajoutait un compteur à une catégorie à plusieurs niveaux utilisant l'ascendance, alors je l'ai cherché moi-même et l'ai mis en œuvre, donc je vais l'expliquer.

Si vous avez une personne similaire, veuillez vous y référer.

introduction

environnement

Ruby: 2.6.6
Rails: 6.0.2.2

supposition

Gemme que vous utilisez

ancestry: 3.0.7
counter_culture: 2.5.1

Cliquez ici pour l'introduction de counter_culture. Agrégation du nombre d'enregistrements associés (cache de compteur) --Qiita

Statut actuel

Recipes Table

Table name: recipes

 id            :bigint       not null, primary key
 title         :string       not null
 description   :string       not null

Tableau intermédiaire entre la recette et la catégorie Plusieurs catégories sont enregistrées pour une recette.

Table name: recipe_categories

 id          :bigint           not null, primary key
 recipe_id   :bigint           not null
 category_id :bigint           not null

Categories Table

Table name: categories

 id            :bigint           not null, primary key
 name          :string           not null
 ancestry      :string
 recipes_count :integer          default(0), not null

Model

class Recipe < ApplicationRecord
  has_many :recipe_categories, dependent: :destroy
end
class Category < ApplicationRecord
  has_ancestry
  has_many :recipe_categories, dependent: :destroy
end
class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count
end

problème actuel

S'il s'agit d'une relation 1: 1, elle sera comptée simplement en mettant ce qui suit dans le tableau intermédiaire.

counter_culture :category, column_name: :recipes_count

Cette fois, je voudrais ajouter une fonction qui compte la catégorie parente en même temps.

Par exemple Il y a une catégorie de viande dans la recette du poulet Teruyaki.

Recipe.find(1)
=> #<Recipe
 id: 1,
 title: "Poulet teriyaki"
>

RecipeCategory.find(1)
=> #<RecipeCategory
 id: 1,
 recipe_id: 1,
 category_id: 20
>

Category.find(20)
=> #<Category
 id: 20,
 name: "Viande de poitrine",
 ancestry: "1/10",
 recipes_count: 1
>

La catégorie de viande a la relation suivante.

id:1 > id:10 > id:20 Viande> Poulet> Poitrine

La "viande" associée à un compteur 1: 1 est comptée, mais pas la "viande" et le "poulet". J'ai essayé de le mettre en œuvre pour que les catégories parentales «viande» et «poulet» soient également comptées.

Comment implémenter des compteurs pour les catégories parent et enfant

En conclusion, je l'ai implémenté comme ça.

class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count,
    foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }
end

Foreign_key_values est une option pour écraser les clés externes.

Le "category_id: 20" normalement associé devient la clé externe, et le Category id: 20 count augmente ou diminue. Si vous passez un nombre au format tableau à Foreign_key_values, la valeur transmise sera utilisée comme clé externe pour augmenter ou diminuer le nombre de toutes les cibles.

Puisque nous voulons compter toutes les catégories parent et enfant, nous l'implémentons en passant la valeur de «[1, 10, 20]» dans l'exemple.

Description de la mise en œuvre

J'ai évoqué la référence officielle et l'article qui a été traduit et expliqué. GitHub - magnusvk/counter_culture: Turbo-charged counter caches for your Rails app. Cache de compteur haute performance pour Rails gem'counter_culture 'README (traduction) | TechRacho

foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }

proc { |category_id| } Passez d'abord l'argument avec proc, à ce moment c'est la clé externe normale (20 dans ce cas)

Category.find(category_id) Obtenez l'objet d'enregistrement cible.

.path_ids Si vous faites path_ids avec la fonction d'ascendance, vous pouvez obtenir l'ID de la relation parent-enfant de l'objet dans une liste.

Référence: [Français] Gem Ancestry Official Document --Qiita

Lorsque vous l'exécutez, vous pouvez voir que => [1, 10, 20] est renvoyé.

Category.find(20).path_ids
  Category Load (1.0ms)  SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT $2  [["id", 20], ["LIMIT", 1]]
=> [1, 10, 20]

En passant [1, 10, 20] à Foreign_key_values:, toutes les catégories parent-enfant peuvent maintenant être comptées.

Implémentation de la fonction de recalcul du comptage

counter_culture a une fonction pour recalculer le nombre, counter_culture_fix_counts. Je l'utilise pour compter les données existantes et corriger les écarts de comptage, mais je ne peux pas utiliser cette fonctionnalité avec l'option Foreign_key_values.

RecipeCategory.counter_culture_fix_counts
=> Fixing counter caches is not supported when using :foreign_key_values;
you may skip this relation with :skip_unsupported => true

Après avoir enquêté, lors de l'utilisation de Foreign_key_values, il semble qu'il n'y ait pas d'autre choix que d'implémenter la fonction de recalcul par moi-même, alors j'ai essayé de l'implémenter.

Dès la conclusion, je l'ai implémenté comme ça. Puisque tout a été recalculé, cela peut prendre un certain temps s'il y a de nombreux cas. Veuillez me faire savoir s'il existe une bonne méthode de mise en œuvre.

class RecipeCategory < ApplicationRecord

  #...
  
  #Recettes de catégorie_Recalculer tous les comptes
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    #Calculez le nombre de catégories=> { 1: 10, 10: 3, 20: 1 }
    count_categories = target_categories.group_by(&:itself).transform_values(&:size)
    count_categories.each do |category_id, count|
      count_up_categories = Category.find(category_id).path_ids
      Category.update_counters(count_up_categories, recipes_count: count)
    end
  end
end

Le contenu en cours d'exécution est le suivant.

  1. Réglez tous les compteurs sur 0
  2. Obtenez category_id dans la table intermédiaire sous forme de tableau
  3. Calculez le nombre => {1: 10, 10: 3, 20: 1}
  4. Obtenez un tableau d'ID de catégorie parent-enfant pour chaque catégorie
  5. Augmentez le nombre de toutes les catégories parent-enfant du nombre

Pour la méthode de calcul du nombre de 3, je me suis référé à l'article suivant. Comptez le nombre d'éléments identiques dans le blog array-patorash

Pour savoir comment augmenter le nombre de 5 par le nombre, reportez-vous à l'article suivant. [Rails + MySQL] Implémentation du compteur [Sinon, je veux en créer un nouveau, et s'il y en a un, je veux l'incrémenter de manière appropriée] --Qiita ActiveRecord::CounterCache::ClassMethods

Maintenant, lorsque vous exécutez RecipeCategory.fix_counts, le décompte sera recalculé.

Conclusion

En définissant RecipeCategory sur ce qui suit, nous avons pu implémenter la fonction de compteur pour la catégorie parent-enfant.

class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count,
                             foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }

  #Recettes de catégorie_Recalculer tous les comptes
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    #Calculez le nombre de catégories=> { 100: 3, 102: 2 }
    count_categories = target_categories.group_by(&:itself).transform_values(&:size)
    count_categories.each do |category_id, count|
      count_up_categories = Category.find(category_id).path_ids
      Category.update_counters(count_up_categories, recipes_count: count)
    end
  end
end

Veuillez vous y référer. Si vous avez des FB ou des améliorations, veuillez nous en informer dans les commentaires.

Recommended Posts

[Rails] Implémenter un compteur qui compte la catégorie parente lors de l'enregistrement d'une catégorie enfant (ascendance, counter_culture)
[Ruby on Rails] Implémentez un graphique circulaire qui spécifie le pourcentage de couleurs