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.
Ruby: 2.6.6
Rails: 6.0.2.2
Nous créons un site pour publier des recettes.
Il existe un DB de recette et un DB de catégorie.
La recette a plusieurs catégories. (Tableau intermédiaire)
La catégorie est une catégorie à plusieurs niveaux. Exemple: viande> poulet> viande
Implémenté avec ascendance (modèle d'énumération d'itinéraire)
Un compteur est implémenté pour voir combien de recettes il y a dans une catégorie.
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
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
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.
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.
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.
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.
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é.
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.