From c40ccb8e86a273fe87fedf013a9911f1adfa9c28 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Fri, 22 Dec 2023 14:27:27 +1100 Subject: [PATCH] Refactor, move product type matching to DfcProductTypeFactory It keeps SuppliedProductBuilder and move all the matching logic to its own class --- .../app/services/dfc_product_type_factory.rb | 63 +++++++++++++++++++ .../app/services/supplied_product_builder.rb | 51 +-------------- .../services/dfc_product_type_factory_spec.rb | 49 +++++++++++++++ .../services/supplied_product_builder_spec.rb | 50 ++------------- 4 files changed, 119 insertions(+), 94 deletions(-) create mode 100644 engines/dfc_provider/app/services/dfc_product_type_factory.rb create mode 100644 engines/dfc_provider/spec/services/dfc_product_type_factory_spec.rb diff --git a/engines/dfc_provider/app/services/dfc_product_type_factory.rb b/engines/dfc_provider/app/services/dfc_product_type_factory.rb new file mode 100644 index 0000000000..07c39c06cf --- /dev/null +++ b/engines/dfc_provider/app/services/dfc_product_type_factory.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'singleton' + +class DfcProductTypeFactory + include Singleton + + def self.for(dfc_id) + instance.for(dfc_id) + end + + def initialize + @product_types = {} + + populate_product_types + end + + def for(dfc_id) + return nil if @product_types[dfc_id].nil? + + call_dfc_product_type(@product_types[dfc_id]) + end + + private + + def populate_product_types + DfcLoader.connector.PRODUCT_TYPES.topConcepts.each do |product_type| + stack = [] + record_type(stack, product_type.to_s) + end + end + + def record_type(stack, product_type) + name = product_type.to_s + current_stack = stack.dup.push(name) + + type = call_dfc_product_type(current_stack) + + id = type.semanticId + @product_types[id] = current_stack + + # Narrower product types are defined as class method on the current product type object + narrowers = type.methods(false).sort + + # Leaf node + return if narrowers.empty? + + narrowers.each do |narrower| + # recursive call + record_type(current_stack, narrower) + end + end + + # Callproduct type method ie: DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK + def call_dfc_product_type(product_type_path) + type = DfcLoader.connector.PRODUCT_TYPES + product_type_path.each do |pt| + type = type.public_send(pt) + end + + type + end +end diff --git a/engines/dfc_provider/app/services/supplied_product_builder.rb b/engines/dfc_provider/app/services/supplied_product_builder.rb index 7d04a8413f..ea475da1ec 100644 --- a/engines/dfc_provider/app/services/supplied_product_builder.rb +++ b/engines/dfc_provider/app/services/supplied_product_builder.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SuppliedProductBuilder < DfcBuilder - PRODUCT_TYPES = {} # rubocop:disable Style/MutableConstant - def self.supplied_product(variant) id = urls.enterprise_supplied_product_url( enterprise_id: variant.product.supplier_id, @@ -63,51 +61,7 @@ class SuppliedProductBuilder < DfcBuilder def self.product_type(variant) taxon_dfc_id = variant.product.primary_taxon&.dfc_id - return nil if taxon_dfc_id.nil? - - populate_product_types if PRODUCT_TYPES.empty? - - return nil if PRODUCT_TYPES[taxon_dfc_id].nil? - - call_dfc_product_type(PRODUCT_TYPES[taxon_dfc_id]) - end - - def self.populate_product_types - DfcLoader.connector.PRODUCT_TYPES.topConcepts.each do |product_type| - stack = [] - record_type(stack, product_type.to_s) - end - end - - def self.record_type(stack, product_type) - name = product_type.to_s - current_stack = stack.dup.push(name) - - type = call_dfc_product_type(current_stack) - - id = type.semanticId - PRODUCT_TYPES[id] = current_stack - - # Narrower product types are defined as class method on the current product type object - narrowers = type.methods(false).sort - - # Leaf node - return if narrowers.empty? - - narrowers.each do |narrower| - # recursive call - record_type(current_stack, narrower) - end - end - - # Callproduct type method ie: DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK - def self.call_dfc_product_type(product_type_path) - type = DfcLoader.connector.PRODUCT_TYPES - product_type_path.each do |pt| - type = type.public_send(pt) - end - - type + DfcProductTypeFactory.for(taxon_dfc_id) end def self.taxon(supplied_product) @@ -115,6 +69,5 @@ class SuppliedProductBuilder < DfcBuilder Spree::Taxon.find_by(dfc_id: ) end - private_class_method :product_type, :populate_product_types, :record_type, :call_dfc_product_type, - :taxon + private_class_method :product_type, :taxon end diff --git a/engines/dfc_provider/spec/services/dfc_product_type_factory_spec.rb b/engines/dfc_provider/spec/services/dfc_product_type_factory_spec.rb new file mode 100644 index 0000000000..31096f4f9c --- /dev/null +++ b/engines/dfc_provider/spec/services/dfc_product_type_factory_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +describe DfcProductTypeFactory do + describe ".for" do + let(:dfc_id) { + "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#drink" + } + + it "assigns a top level product type" do + drink = DfcLoader.connector.PRODUCT_TYPES.DRINK + + expect(described_class.for(dfc_id)).to eq drink + end + + context "with second level product type" do + let(:dfc_id) { + "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#soft-drink" + } + + it "assigns a second level product type" do + soft_drink = DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK + + expect(described_class.for(dfc_id)).to eq soft_drink + end + end + + context "with leaf level product type" do + let(:dfc_id) { + "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#lemonade" + } + + it "assigns a leaf level product type" do + lemonade = DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK.LEMONADE + + expect(described_class.for(dfc_id)).to eq lemonade + end + end + + context "with non existing product type" do + let(:dfc_id) { "other" } + + it "returns nil" do + expect(described_class.for(dfc_id)).to be_nil + end + end + end +end diff --git a/engines/dfc_provider/spec/services/supplied_product_builder_spec.rb b/engines/dfc_provider/spec/services/supplied_product_builder_spec.rb index 770e72a95f..b8e41df742 100644 --- a/engines/dfc_provider/spec/services/supplied_product_builder_spec.rb +++ b/engines/dfc_provider/spec/services/supplied_product_builder_spec.rb @@ -15,8 +15,8 @@ describe SuppliedProductBuilder do let(:taxon) { build( :taxon, - name: "Drink", - dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#drink" + name: "Soft Drink", + dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#soft-drink" ) } @@ -54,50 +54,10 @@ describe SuppliedProductBuilder do context "product_type mapping" do subject(:product) { builder.supplied_product(variant) } - it "assigns a top level product type" do - drink = DfcLoader.connector.PRODUCT_TYPES.DRINK + it "assigns a product type" do + soft_drink = DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK - expect(product.productType).to eq drink - end - - context "with second level product type" do - let(:taxon) { - build( - :taxon, - name: "Soft Drink", - dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#soft-drink" - ) - } - - it "assigns a second level product type" do - soft_drink = DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK - - expect(product.productType).to eq soft_drink - end - end - - context "with leaf level product type" do - let(:taxon) { - build( - :taxon, - name: "Lemonade", - dfc_id: "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#lemonade" - ) - } - - it "assigns a leaf level product type" do - lemonade = DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK.LEMONADE - - expect(product.productType).to eq lemonade - end - end - - context "with non existing product type" do - let(:taxon) { build(:taxon, name: "other", dfc_id: "other") } - - it "returns nil" do - expect(product.productType).to be_nil - end + expect(product.productType).to eq soft_drink end context "when no taxon set" do