[RUBY] [Rails] Implementieren Sie einen Zähler, der die übergeordnete Kategorie zählt, wenn Sie eine untergeordnete Kategorie registrieren (Abstammung, counter_culture).

Wenn es eine mehrstufige Kategorie wie "Fleisch> Huhn> Fleisch" gibt Nachdem ich die Fleischkategorie im Rezept registriert hatte, implementierte ich eine Funktion, um auch die übergeordnete Kategorie "Fleisch> Huhn" zu zählen.

Ich konnte keinen Artikel finden, der einer mehrstufigen Kategorie mithilfe von Vorfahren einen Zähler hinzufügte. Deshalb habe ich ihn selbst gesucht und implementiert, damit ich ihn erläutern kann.

Wenn Sie eine ähnliche Person haben, beziehen Sie sich bitte darauf.

Einführung

Umgebung

Ruby: 2.6.6
Rails: 6.0.2.2

Annahme

Edelstein, den Sie verwenden

ancestry: 3.0.7
counter_culture: 2.5.1

Klicken Sie hier für die Einführung von counter_culture. Aggregation der Anzahl verwandter Datensätze (Counter-Cache) - Qiita

Aktueller Status

Recipes Table

Table name: recipes

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

Zwischentabelle zwischen Rezept und Kategorie Für ein Rezept sind mehrere Kategorien registriert.

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

aktuelles Problem

Wenn es sich um eine 1: 1-Beziehung handelt, wird sie gezählt, indem Folgendes in die Zwischentabelle eingetragen wird.

counter_culture :category, column_name: :recipes_count

Dieses Mal möchte ich eine Funktion hinzufügen, die gleichzeitig die übergeordnete Kategorie zählt.

Zum Beispiel Das Rezept für Teruyaki-Hühnchen enthält eine Fleischkategorie.

Recipe.find(1)
=> #<Recipe
 id: 1,
 title: "Teriyaki Hähnchen"
>

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

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

Die Fleischkategorie hat die folgende Beziehung.

id:1 > id:10 > id:20 Fleisch> Huhn> Brust

"Fleisch", das einem 1: 1-Zähler zugeordnet ist, wird gezählt, nicht jedoch "Fleisch" und "Huhn". Ich habe versucht, es so zu implementieren, dass auch die übergeordneten Kategorien "Fleisch" und "Huhn" gezählt werden.

So implementieren Sie Zähler für übergeordnete und untergeordnete Kategorien

Abschließend habe ich es so umgesetzt.

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 ist eine Option zum Überschreiben externer Schlüssel.

Die normalerweise zugeordnete "category_id: 20" wird zum externen Schlüssel, und die Anzahl der Kategorie-IDs: 20 erhöht oder verringert sich. Wenn Sie eine Zahl im Array-Format an "Foreign_key_values" übergeben, wird der übergebene Wert als externer Schlüssel verwendet, um die Anzahl aller Ziele zu erhöhen oder zu verringern.

Da wir alle übergeordneten und untergeordneten Kategorien zählen möchten, implementieren wir sie, indem wir im Beispiel den Wert "[1, 10, 20]" übergeben.

Implementierungsbeschreibung

Ich bezog mich auf die offizielle Referenz und den Artikel, der übersetzt und erklärt wurde. GitHub - magnusvk/counter_culture: Turbo-charged counter caches for your Rails app. Hochleistungszähler-Cache für Rails gem'counter_culture 'README (Übersetzung) | TechRacho

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

proc { |category_id| } Übergeben Sie zuerst das Argument mit proc, zu diesem Zeitpunkt ist es der normale externe Schlüssel (in diesem Fall 20).

Category.find(category_id) Holen Sie sich das Zieldatensatzobjekt.

.path_ids Wenn Sie "path_ids" mit der Funktion der Abstammung ausführen, können Sie die ID der Eltern-Kind-Beziehung des Objekts in einer Liste abrufen.

Referenz: [Übersetzung] Gem Ancestry Official Document --Qiita

Wenn Sie es ausführen, können Sie sehen, dass => [1, 10, 20] zurückgegeben wird.

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]

Durch Übergeben von "[1, 10, 20]" an "Foreign_key_values:" können jetzt alle Eltern-Kind-Kategorien gezählt werden.

Implementierung der Funktion zur Neuberechnung der Zählung

counter_culture hat eine Funktion zum Neuberechnen der Anzahl, counter_culture_fix_counts. Ich verwende es, um vorhandene Daten zu zählen und Zählabweichungen zu korrigieren, aber ich kann diese Funktion nicht mit der Option "Foreign_key_values" verwenden.

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

Nach der Untersuchung scheint es bei der Verwendung von "Foreign_key_values" keine andere Wahl zu geben, als die Neuberechnungsfunktion selbst zu implementieren, also habe ich versucht, sie zu implementieren.

Aus dem Fazit habe ich es so umgesetzt. Da alles neu berechnet wurde, kann es in vielen Fällen einige Zeit dauern. Bitte lassen Sie mich wissen, ob es eine gute Implementierungsmethode gibt.

class RecipeCategory < ApplicationRecord

  #...
  
  #Kategorie Rezepte_Berechnen Sie alle Zählungen neu
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    #Berechnen Sie die Anzahl der Kategorien=> { 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

Die Inhalte, die ausgeführt werden, sind wie folgt.

  1. Setzen Sie alle Zähler auf 0
  2. Holen Sie sich category_id in die Zwischentabelle als Array
  3. Berechnen Sie die Zahl => {1: 10, 10: 3, 20: 1}
  4. Rufen Sie für jede Kategorie ein Array von Eltern-Kind-Kategorie-IDs ab
  5. Erhöhen Sie die Anzahl aller Eltern-Kind-Kategorien um die Anzahl

Für die Methode zur Berechnung der Zahl 3 habe ich auf den folgenden Artikel verwiesen. Zählen Sie, wie viele identische Elemente in einem Array-Patorash-Blog enthalten sind

Informationen zum Erhöhen der Anzahl von 5 um die Zahl finden Sie im folgenden Artikel. [Rails + MySQL] Counter-Implementierung [Wenn nicht, möchte ich eine neue erstellen, und wenn ja, möchte ich sie entsprechend erhöhen] --Qiita ActiveRecord::CounterCache::ClassMethods

Wenn Sie nun "RecipeCategory.fix_counts" ausführen, wird die Anzahl neu berechnet.

Fazit

Durch Setzen von RecipeCategory auf Folgendes konnten wir die Zählerfunktion für die Eltern-Kind-Kategorie implementieren.

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 }

  #Kategorie Rezepte_Berechnen Sie alle Zählungen neu
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    #Berechnen Sie die Anzahl der Kategorien=> { 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

Bitte nehmen Sie Bezug darauf. Wenn Sie FBs oder Verbesserungen haben, teilen Sie uns dies bitte in den Kommentaren mit.

Recommended Posts

[Rails] Implementieren Sie einen Zähler, der die übergeordnete Kategorie zählt, wenn Sie eine untergeordnete Kategorie registrieren (Abstammung, counter_culture).
[Ruby on Rails] Implementieren Sie ein Kreisdiagramm, das den Prozentsatz der Farben angibt