shopify_product_taxonomy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/product_taxonomy/alphanumeric_sorter.rb +113 -0
- data/lib/product_taxonomy/identifier_formatter.rb +28 -0
- data/lib/product_taxonomy/loader.rb +31 -0
- data/lib/product_taxonomy/localizations_validator.rb +31 -0
- data/lib/product_taxonomy/models/attribute.rb +170 -0
- data/lib/product_taxonomy/models/category.rb +313 -0
- data/lib/product_taxonomy/models/extended_attribute.rb +47 -0
- data/lib/product_taxonomy/models/integration_version.rb +296 -0
- data/lib/product_taxonomy/models/mapping_rule.rb +114 -0
- data/lib/product_taxonomy/models/mixins/formatted_validation_errors.rb +23 -0
- data/lib/product_taxonomy/models/mixins/indexed.rb +133 -0
- data/lib/product_taxonomy/models/mixins/localized.rb +70 -0
- data/lib/product_taxonomy/models/serializers/attribute/data/data_serializer.rb +48 -0
- data/lib/product_taxonomy/models/serializers/attribute/data/localizations_serializer.rb +34 -0
- data/lib/product_taxonomy/models/serializers/attribute/dist/json_serializer.rb +41 -0
- data/lib/product_taxonomy/models/serializers/attribute/dist/txt_serializer.rb +42 -0
- data/lib/product_taxonomy/models/serializers/attribute/docs/base_and_extended_serializer.rb +41 -0
- data/lib/product_taxonomy/models/serializers/attribute/docs/reversed_serializer.rb +55 -0
- data/lib/product_taxonomy/models/serializers/attribute/docs/search_serializer.rb +32 -0
- data/lib/product_taxonomy/models/serializers/category/data/data_serializer.rb +29 -0
- data/lib/product_taxonomy/models/serializers/category/data/full_names_serializer.rb +26 -0
- data/lib/product_taxonomy/models/serializers/category/data/localizations_serializer.rb +34 -0
- data/lib/product_taxonomy/models/serializers/category/dist/json_serializer.rb +60 -0
- data/lib/product_taxonomy/models/serializers/category/dist/txt_serializer.rb +42 -0
- data/lib/product_taxonomy/models/serializers/category/docs/search_serializer.rb +33 -0
- data/lib/product_taxonomy/models/serializers/category/docs/siblings_serializer.rb +39 -0
- data/lib/product_taxonomy/models/serializers/value/data/data_serializer.rb +28 -0
- data/lib/product_taxonomy/models/serializers/value/data/localizations_serializer.rb +34 -0
- data/lib/product_taxonomy/models/serializers/value/dist/json_serializer.rb +31 -0
- data/lib/product_taxonomy/models/serializers/value/dist/txt_serializer.rb +38 -0
- data/lib/product_taxonomy/models/taxonomy.rb +11 -0
- data/lib/product_taxonomy/models/value.rb +147 -0
- data/lib/product_taxonomy/version.rb +6 -0
- data/lib/product_taxonomy.rb +50 -0
- metadata +124 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ProductTaxonomy
|
|
4
|
+
module Serializers
|
|
5
|
+
module Value
|
|
6
|
+
module Dist
|
|
7
|
+
class TxtSerializer
|
|
8
|
+
class << self
|
|
9
|
+
def serialize_all(version:, locale: "en", padding: longest_gid_length)
|
|
10
|
+
header = <<~HEADER
|
|
11
|
+
# Shopify Product Taxonomy - Attribute Values: #{version}
|
|
12
|
+
# Format: {GID} : {Value name} [{Attribute name}]
|
|
13
|
+
|
|
14
|
+
HEADER
|
|
15
|
+
|
|
16
|
+
header + ProductTaxonomy::Value.all_values_sorted.map { serialize(_1, padding:, locale:) }.join("\n")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param value [Value]
|
|
20
|
+
# @param padding [Integer] The padding to use for the GID.
|
|
21
|
+
# @param locale [String] The locale to use for localization.
|
|
22
|
+
# @return [String]
|
|
23
|
+
def serialize(value, padding: 0, locale: "en")
|
|
24
|
+
"#{value.gid.ljust(padding)} : #{value.full_name(locale:)}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def longest_gid_length
|
|
30
|
+
largest_id = ProductTaxonomy::Value.hashed_by(:id).keys.max
|
|
31
|
+
ProductTaxonomy::Value.find_by(id: largest_id).gid.length
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ProductTaxonomy
|
|
4
|
+
# An attribute value in the product taxonomy. Referenced by a {ProductTaxonomy::Attribute}. For example, an
|
|
5
|
+
# attribute called "Color" could have values "Red", "Blue", and "Green".
|
|
6
|
+
class Value
|
|
7
|
+
include ActiveModel::Validations
|
|
8
|
+
include FormattedValidationErrors
|
|
9
|
+
extend Localized
|
|
10
|
+
extend Indexed
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Load values from source data. By default, this data is deserialized from a YAML file in the `data` directory.
|
|
14
|
+
#
|
|
15
|
+
# @param source_data [Array<Hash>] The source data to load values from.
|
|
16
|
+
def load_from_source(source_data)
|
|
17
|
+
raise ArgumentError, "source_data must be an array" unless source_data.is_a?(Array)
|
|
18
|
+
|
|
19
|
+
source_data.each do |value_data|
|
|
20
|
+
raise ArgumentError, "source_data must contain hashes" unless value_data.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
Value.create_validate_and_add!(
|
|
23
|
+
id: value_data["id"],
|
|
24
|
+
name: value_data["name"],
|
|
25
|
+
friendly_id: value_data["friendly_id"],
|
|
26
|
+
handle: value_data["handle"],
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Reset all class-level state
|
|
32
|
+
def reset
|
|
33
|
+
@localizations = nil
|
|
34
|
+
@hashed_models = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Sort values by their localized name.
|
|
38
|
+
#
|
|
39
|
+
# @param values [Array<Value>] The values to sort.
|
|
40
|
+
# @param locale [String] The locale to sort by.
|
|
41
|
+
# @return [Array<Value>] The sorted values.
|
|
42
|
+
def sort_by_localized_name(values, locale: "en")
|
|
43
|
+
values.sort_by.with_index do |value, idx|
|
|
44
|
+
[
|
|
45
|
+
value.name(locale: "en").downcase == "other" ? 1 : 0,
|
|
46
|
+
*AlphanumericSorter.normalize_value(value.name(locale:)),
|
|
47
|
+
idx,
|
|
48
|
+
]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Sort values according to their English name, taking "other" into account.
|
|
53
|
+
#
|
|
54
|
+
# @param values [Array<Value>] The values to sort.
|
|
55
|
+
# @return [Array<Value>] The sorted values.
|
|
56
|
+
def all_values_sorted
|
|
57
|
+
all.sort_by do |value|
|
|
58
|
+
[
|
|
59
|
+
value.name(locale: "en").downcase == "other" ? 1 : 0,
|
|
60
|
+
value.name(locale: "en"),
|
|
61
|
+
value.id,
|
|
62
|
+
]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get the next ID for a newly created value.
|
|
67
|
+
#
|
|
68
|
+
# @return [Integer] The next ID.
|
|
69
|
+
def next_id = (all.max_by(&:id)&.id || 0) + 1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Validations that run when the value is created
|
|
73
|
+
validates :id, presence: true, numericality: { only_integer: true }, on: :create
|
|
74
|
+
validates :name, presence: true, on: :create
|
|
75
|
+
validates :friendly_id, presence: true, on: :create
|
|
76
|
+
validates :handle, presence: true, on: :create
|
|
77
|
+
validates_with ProductTaxonomy::Indexed::UniquenessValidator, attributes: [:friendly_id, :handle, :id], on: :create
|
|
78
|
+
|
|
79
|
+
# Validations that run when the taxonomy has been loaded
|
|
80
|
+
validate :friendly_id_prefix_resolves_to_attribute?, on: :taxonomy_loaded
|
|
81
|
+
validate :handle_prefix_matches_attribute?, on: :taxonomy_loaded
|
|
82
|
+
|
|
83
|
+
localized_attr_reader :name
|
|
84
|
+
|
|
85
|
+
attr_reader :id, :friendly_id, :handle
|
|
86
|
+
|
|
87
|
+
# @param id [Integer] The ID of the value.
|
|
88
|
+
# @param name [String] The name of the value.
|
|
89
|
+
# @param friendly_id [String] The friendly ID of the value.
|
|
90
|
+
# @param handle [String] The handle of the value.
|
|
91
|
+
def initialize(id:, name:, friendly_id:, handle:)
|
|
92
|
+
@id = id
|
|
93
|
+
@name = name
|
|
94
|
+
@friendly_id = friendly_id
|
|
95
|
+
@handle = handle
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def gid
|
|
99
|
+
"gid://shopify/TaxonomyValue/#{id}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get the primary attribute for this value.
|
|
103
|
+
#
|
|
104
|
+
# @return [ProductTaxonomy::Attribute] The primary attribute for this value.
|
|
105
|
+
def primary_attribute
|
|
106
|
+
@primary_attribute ||= Attribute.find_by(friendly_id: primary_attribute_friendly_id)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get the full name of the value, including the primary attribute.
|
|
110
|
+
#
|
|
111
|
+
# @param locale [String] The locale to get the name in.
|
|
112
|
+
# @return [String] The full name of the value.
|
|
113
|
+
def full_name(locale: "en")
|
|
114
|
+
"#{name(locale:)} [#{primary_attribute.name(locale:)}]"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def primary_attribute_friendly_id = friendly_id.split("__").first
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Validation
|
|
123
|
+
#
|
|
124
|
+
def friendly_id_prefix_resolves_to_attribute?
|
|
125
|
+
return if primary_attribute
|
|
126
|
+
|
|
127
|
+
errors.add(
|
|
128
|
+
:friendly_id,
|
|
129
|
+
:invalid_prefix,
|
|
130
|
+
message: "prefix \"#{primary_attribute_friendly_id}\" does not match the friendly_id of any attribute",
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def handle_prefix_matches_attribute?
|
|
135
|
+
return if primary_attribute.nil?
|
|
136
|
+
|
|
137
|
+
handle_prefix = handle.split("__").first
|
|
138
|
+
return if primary_attribute && primary_attribute.handle == handle_prefix
|
|
139
|
+
|
|
140
|
+
errors.add(
|
|
141
|
+
:handle,
|
|
142
|
+
:invalid_prefix,
|
|
143
|
+
message: "prefix \"#{handle_prefix}\" does not match primary attribute handle \"#{primary_attribute.handle}\"",
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/all"
|
|
4
|
+
require "active_model"
|
|
5
|
+
|
|
6
|
+
module ProductTaxonomy
|
|
7
|
+
DEFAULT_DATA_PATH = File.expand_path("../../data", __dir__)
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
attr_writer :data_path
|
|
11
|
+
|
|
12
|
+
def data_path
|
|
13
|
+
@data_path ||= DEFAULT_DATA_PATH
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require_relative "product_taxonomy/version"
|
|
19
|
+
require_relative "product_taxonomy/loader"
|
|
20
|
+
require_relative "product_taxonomy/alphanumeric_sorter"
|
|
21
|
+
require_relative "product_taxonomy/identifier_formatter"
|
|
22
|
+
require_relative "product_taxonomy/localizations_validator"
|
|
23
|
+
require_relative "product_taxonomy/models/mixins/localized"
|
|
24
|
+
require_relative "product_taxonomy/models/mixins/indexed"
|
|
25
|
+
require_relative "product_taxonomy/models/mixins/formatted_validation_errors"
|
|
26
|
+
require_relative "product_taxonomy/models/attribute"
|
|
27
|
+
require_relative "product_taxonomy/models/extended_attribute"
|
|
28
|
+
require_relative "product_taxonomy/models/value"
|
|
29
|
+
require_relative "product_taxonomy/models/category"
|
|
30
|
+
require_relative "product_taxonomy/models/taxonomy"
|
|
31
|
+
require_relative "product_taxonomy/models/mapping_rule"
|
|
32
|
+
require_relative "product_taxonomy/models/integration_version"
|
|
33
|
+
require_relative "product_taxonomy/models/serializers/category/data/data_serializer"
|
|
34
|
+
require_relative "product_taxonomy/models/serializers/category/data/localizations_serializer"
|
|
35
|
+
require_relative "product_taxonomy/models/serializers/category/data/full_names_serializer"
|
|
36
|
+
require_relative "product_taxonomy/models/serializers/category/docs/siblings_serializer"
|
|
37
|
+
require_relative "product_taxonomy/models/serializers/category/docs/search_serializer"
|
|
38
|
+
require_relative "product_taxonomy/models/serializers/category/dist/json_serializer"
|
|
39
|
+
require_relative "product_taxonomy/models/serializers/category/dist/txt_serializer"
|
|
40
|
+
require_relative "product_taxonomy/models/serializers/attribute/data/data_serializer"
|
|
41
|
+
require_relative "product_taxonomy/models/serializers/attribute/data/localizations_serializer"
|
|
42
|
+
require_relative "product_taxonomy/models/serializers/attribute/docs/base_and_extended_serializer"
|
|
43
|
+
require_relative "product_taxonomy/models/serializers/attribute/docs/reversed_serializer"
|
|
44
|
+
require_relative "product_taxonomy/models/serializers/attribute/docs/search_serializer"
|
|
45
|
+
require_relative "product_taxonomy/models/serializers/attribute/dist/json_serializer"
|
|
46
|
+
require_relative "product_taxonomy/models/serializers/attribute/dist/txt_serializer"
|
|
47
|
+
require_relative "product_taxonomy/models/serializers/value/data/data_serializer"
|
|
48
|
+
require_relative "product_taxonomy/models/serializers/value/data/localizations_serializer"
|
|
49
|
+
require_relative "product_taxonomy/models/serializers/value/dist/json_serializer"
|
|
50
|
+
require_relative "product_taxonomy/models/serializers/value/dist/txt_serializer"
|
metadata
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: shopify_product_taxonomy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shopify
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activemodel
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: json
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 2.16.0
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 2.16.0
|
|
55
|
+
description: A code-only gem providing Ruby classes and utilities to parse and process
|
|
56
|
+
Shopify's Standard Product Taxonomy data from YAML source files into an in-memory
|
|
57
|
+
Ruby object model. Data files are available at https://github.com/Shopify/product-taxonomy
|
|
58
|
+
email:
|
|
59
|
+
- gems@shopify.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- lib/product_taxonomy.rb
|
|
65
|
+
- lib/product_taxonomy/alphanumeric_sorter.rb
|
|
66
|
+
- lib/product_taxonomy/identifier_formatter.rb
|
|
67
|
+
- lib/product_taxonomy/loader.rb
|
|
68
|
+
- lib/product_taxonomy/localizations_validator.rb
|
|
69
|
+
- lib/product_taxonomy/models/attribute.rb
|
|
70
|
+
- lib/product_taxonomy/models/category.rb
|
|
71
|
+
- lib/product_taxonomy/models/extended_attribute.rb
|
|
72
|
+
- lib/product_taxonomy/models/integration_version.rb
|
|
73
|
+
- lib/product_taxonomy/models/mapping_rule.rb
|
|
74
|
+
- lib/product_taxonomy/models/mixins/formatted_validation_errors.rb
|
|
75
|
+
- lib/product_taxonomy/models/mixins/indexed.rb
|
|
76
|
+
- lib/product_taxonomy/models/mixins/localized.rb
|
|
77
|
+
- lib/product_taxonomy/models/serializers/attribute/data/data_serializer.rb
|
|
78
|
+
- lib/product_taxonomy/models/serializers/attribute/data/localizations_serializer.rb
|
|
79
|
+
- lib/product_taxonomy/models/serializers/attribute/dist/json_serializer.rb
|
|
80
|
+
- lib/product_taxonomy/models/serializers/attribute/dist/txt_serializer.rb
|
|
81
|
+
- lib/product_taxonomy/models/serializers/attribute/docs/base_and_extended_serializer.rb
|
|
82
|
+
- lib/product_taxonomy/models/serializers/attribute/docs/reversed_serializer.rb
|
|
83
|
+
- lib/product_taxonomy/models/serializers/attribute/docs/search_serializer.rb
|
|
84
|
+
- lib/product_taxonomy/models/serializers/category/data/data_serializer.rb
|
|
85
|
+
- lib/product_taxonomy/models/serializers/category/data/full_names_serializer.rb
|
|
86
|
+
- lib/product_taxonomy/models/serializers/category/data/localizations_serializer.rb
|
|
87
|
+
- lib/product_taxonomy/models/serializers/category/dist/json_serializer.rb
|
|
88
|
+
- lib/product_taxonomy/models/serializers/category/dist/txt_serializer.rb
|
|
89
|
+
- lib/product_taxonomy/models/serializers/category/docs/search_serializer.rb
|
|
90
|
+
- lib/product_taxonomy/models/serializers/category/docs/siblings_serializer.rb
|
|
91
|
+
- lib/product_taxonomy/models/serializers/value/data/data_serializer.rb
|
|
92
|
+
- lib/product_taxonomy/models/serializers/value/data/localizations_serializer.rb
|
|
93
|
+
- lib/product_taxonomy/models/serializers/value/dist/json_serializer.rb
|
|
94
|
+
- lib/product_taxonomy/models/serializers/value/dist/txt_serializer.rb
|
|
95
|
+
- lib/product_taxonomy/models/taxonomy.rb
|
|
96
|
+
- lib/product_taxonomy/models/value.rb
|
|
97
|
+
- lib/product_taxonomy/version.rb
|
|
98
|
+
homepage: https://github.com/Shopify/product-taxonomy
|
|
99
|
+
licenses:
|
|
100
|
+
- MIT
|
|
101
|
+
metadata:
|
|
102
|
+
bug_tracker_uri: https://github.com/Shopify/product-taxonomy/issues
|
|
103
|
+
source_code_uri: https://github.com/Shopify/product-taxonomy
|
|
104
|
+
allowed_push_host: https://rubygems.org
|
|
105
|
+
post_install_message:
|
|
106
|
+
rdoc_options: []
|
|
107
|
+
require_paths:
|
|
108
|
+
- lib
|
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - ">="
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '3.0'
|
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
requirements: []
|
|
120
|
+
rubygems_version: 3.5.3
|
|
121
|
+
signing_key:
|
|
122
|
+
specification_version: 4
|
|
123
|
+
summary: Load the complete Shopify Standard Product Taxonomy into memory as a tree
|
|
124
|
+
test_files: []
|