-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add taxonomy mapping models and the logic to generate mappings from a…
… list of mapping rules
- Loading branch information
1 parent
fdbde72
commit e7cd970
Showing
15 changed files
with
41,298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
class GoogleProduct < Product | ||
store :payload, coder: JSON, accessors: [:product_category_id] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# frozen_string_literal: true | ||
|
||
class Integration < ApplicationRecord | ||
has_many :mapping_rules | ||
validates :name, presence: true | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# frozen_string_literal: true | ||
|
||
class MappingRule < ApplicationRecord | ||
belongs_to :integration | ||
belongs_to :input, polymorphic: true | ||
belongs_to :output, polymorphic: true | ||
|
||
PRESENT = "present" | ||
|
||
def apply(output_acc) | ||
output_hash = output.payload.as_json | ||
output_hash.delete_if { |_, value| value == [] } | ||
output_acc.merge(output_hash) do |key, old_val, new_val| | ||
intersection = old_val.intersection(new_val) | ||
if !old_val.empty? && intersection.empty? | ||
raise "Rules conflicted, please review the rules. | ||
rule: #{as_json}, key: #{key}, old_val: #{old_val}, new_val: #{new_val}" | ||
end | ||
|
||
intersection | ||
end | ||
end | ||
|
||
def match?(product_input) | ||
rule_input = input | ||
|
||
return false unless product_input[:product_category_id] == rule_input.product_category_id | ||
return true if rule_input.properties.blank? | ||
|
||
rule_input.properties.all? do |rule_attribute| | ||
product_input[:attributes].any? do |attribute| | ||
if attribute[:name] == rule_attribute["name"] | ||
values_match = attribute[:value] == rule_attribute["value"] | ||
# TODO: Handle inputs that are lists/multi-selections | ||
value_included = if rule_attribute["value"].is_a?(Array) | ||
rule_attribute["value"].include?(attribute[:value]) | ||
else | ||
false | ||
end | ||
value_present = attribute[:value].present? && rule_attribute["value"] == PRESENT | ||
|
||
values_match || value_included || value_present | ||
else | ||
false | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# frozen_string_literal: true | ||
|
||
class Product < ApplicationRecord | ||
has_many :mapping_rules | ||
serialize :payload, coder: JSON | ||
|
||
def as_json(options = {}) | ||
super(options.merge({ methods: :type })) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
class ShopifyProduct < Product | ||
store :payload, coder: JSON, accessors: [:product_category_id, :properties] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
module SourceData | ||
class ProductSerializer < ObjectSerializer | ||
def serialize(product) | ||
payload = {} | ||
if product.is_a?(ShopifyProduct) | ||
payload[:product_category_id] = product.product_category_id | ||
payload[:attributes] = product.properties.map(&:deep_symbolize_keys) if product.properties.present? | ||
end | ||
{ | ||
payload: payload, | ||
type: product.type, | ||
} | ||
end | ||
|
||
def deserialize(payload, product_type) | ||
if product_type == "ShopifyProduct" | ||
properties = payload.delete("attributes") | ||
payload["properties"] = properties | ||
end | ||
Product.new(payload: payload, type: product_type) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# frozen_string_literal: true | ||
|
||
class InputProductGenerator | ||
class << self | ||
def generate_inputs_for_categories(categories, rules) | ||
all_inputs = [] | ||
relevant_attributes = rules.flat_map do |rule| | ||
rule.input.properties&.map { |attribute| attribute["name"] } | ||
end.uniq | ||
|
||
categories.each do |category| | ||
category_attributes = category.properties | ||
.filter { |attribute| relevant_attributes.include?(attribute.id) } | ||
all_inputs << generate_inputs_from_category(category, category_attributes) | ||
end | ||
|
||
all_inputs.flatten | ||
end | ||
|
||
private | ||
|
||
def generate_inputs_from_category(category, relevant_category_attributes) | ||
# NOTE: Shopify attributes are optional so we need to account for that in the combination | ||
all_attributes_with_values = [] | ||
relevant_category_attributes.each do |attribute| | ||
all_attributes_with_values.push( | ||
attribute.property_values | ||
.map { |value| [attribute.id, value.id] } | ||
.append([attribute.id, nil]), | ||
) | ||
end | ||
|
||
attribute_combinations = if all_attributes_with_values.present? | ||
all_attributes_with_values[0].product(*all_attributes_with_values[1..]) | ||
else | ||
[[]] # Empty attribute set | ||
end | ||
|
||
attribute_combinations.map do |combination| | ||
{ | ||
product_category_id: category[:id], | ||
attributes: combination.map do |e| | ||
{ | ||
name: e.first, | ||
value: e.last, | ||
} | ||
end, | ||
} | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# frozen_string_literal: true | ||
|
||
class MappingBuilder | ||
class << self | ||
def build_mappings_for_vertical(mapping_rules:, vertical:) | ||
puts " → for #{Integration.find(mapping_rules.first.integration_id).name} in #{vertical.name}..." | ||
relevant_rules = mapping_rules.select { |rule| rule.input.product_category_id.start_with?(vertical.id) } | ||
return if relevant_rules.count.zero? | ||
|
||
inputs = InputProductGenerator.generate_inputs_for_categories(vertical.descendants_and_self, relevant_rules) | ||
|
||
build_mappings_from_inputs_and_rules(inputs, relevant_rules) | ||
end | ||
|
||
private | ||
|
||
def build_mappings_from_inputs_and_rules(inputs, rules) | ||
inputs.map do |input| | ||
mapping = { | ||
input: input, | ||
output: rules.filter { |rule| rule.match?(input) }.reduce({}) do |final_output, rule| | ||
rule.apply(final_output) | ||
end, | ||
} | ||
|
||
if mapping[:output].empty? | ||
nil | ||
elsif mapping[:input][:attributes].empty? | ||
mapping[:input].delete(:attributes) | ||
mapping | ||
else | ||
mapping | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.