Skip to content

Commit be7febe

Browse files
authored
Refactor Value to use dedicated dist serializers (#564)
### TL;DR Follow up to #531: moves Value serialization logic into dedicated serializer classes for JSON and TXT formats. ### What changed? - Created new `JsonSerializer` and `TxtSerializer` classes for Value serialization - Moved JSON and TXT serialization logic from `Value` model to respective serializer classes - Updated `GenerateDistCommand` to use new serializer classes - Added comprehensive tests for both serializer classes - Removed serialization-related tests from `value_test.rb`
2 parents bc96b03 + dfc640e commit be7febe

File tree

8 files changed

+403
-241
lines changed

8 files changed

+403
-241
lines changed

dev/lib/product_taxonomy.rb

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def data_path = DATA_PATH
3535
require_relative "product_taxonomy/models/serializers/attribute/docs/reversed_serializer"
3636
require_relative "product_taxonomy/models/serializers/attribute/docs/search_serializer"
3737
require_relative "product_taxonomy/models/serializers/value/data/localizations_serializer"
38+
require_relative "product_taxonomy/models/serializers/value/dist/json_serializer"
39+
require_relative "product_taxonomy/models/serializers/value/dist/txt_serializer"
3840
require_relative "product_taxonomy/commands/command"
3941
require_relative "product_taxonomy/commands/generate_dist_command"
4042
require_relative "product_taxonomy/commands/find_unmapped_external_categories_command"

dev/lib/product_taxonomy/commands/generate_dist_command.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def generate_txt_file(locale:, type:)
5353
when "categories" then Category.to_txt(version: @version, locale:)
5454
when "attributes" then Attribute.to_txt(version: @version, locale:)
5555
when "taxonomy" then return
56-
when "attribute_values" then Value.to_txt(version: @version, locale:)
56+
when "attribute_values" then Serializers::Value::Dist::TxtSerializer.serialize_all(version: @version, locale:)
5757
end
5858

5959
File.write("#{OUTPUT_PATH}/#{locale}/#{type}.txt", txt_data + "\n")
@@ -68,7 +68,7 @@ def generate_json_file(locale:, type:)
6868
when "taxonomy"
6969
@categories_json_by_locale[locale].merge(@attributes_json_by_locale[locale])
7070
when "attribute_values"
71-
Value.to_json(version: @version, locale:)
71+
Serializers::Value::Dist::JsonSerializer.serialize_all(version: @version, locale:)
7272
end
7373

7474
File.write("#{OUTPUT_PATH}/#{locale}/#{type}.json", JSON.pretty_generate(json_data) + "\n")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module ProductTaxonomy
4+
module Serializers
5+
module Value
6+
module Dist
7+
class JsonSerializer
8+
class << self
9+
def serialize_all(version:, locale: "en")
10+
{
11+
"version" => version,
12+
"values" => ProductTaxonomy::Value.all_values_sorted.map { serialize(_1, locale:) },
13+
}
14+
end
15+
16+
# @param value [Value]
17+
# @param locale [String] The locale to use for localization.
18+
# @return [Hash]
19+
def serialize(value, locale: "en")
20+
{
21+
"id" => value.gid,
22+
"name" => value.name(locale:),
23+
"handle" => value.handle,
24+
}
25+
end
26+
end
27+
end
28+
end
29+
end
30+
end
31+
end
Original file line numberDiff line numberDiff line change
@@ -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

dev/lib/product_taxonomy/models/value.rb

+4-51
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,6 @@ def load_from_source(source_data)
2929
end
3030
end
3131

32-
# Get the JSON representation of all values.
33-
#
34-
# @param version [String] The version of the taxonomy.
35-
# @param locale [String] The locale to use for localized attributes.
36-
# @return [Hash] The JSON representation of all values.
37-
def to_json(version:, locale: "en")
38-
{
39-
"version" => version,
40-
"values" => all_values_sorted.map { _1.to_json(locale:) },
41-
}
42-
end
43-
44-
# Get the TXT representation of all values.
45-
#
46-
# @param version [String] The version of the taxonomy.
47-
# @param locale [String] The locale to use for localized attributes.
48-
# @param padding [Integer] The padding to use for the GID. Defaults to the length of the longest GID.
49-
# @return [String] The TXT representation of all values.
50-
def to_txt(version:, locale: "en", padding: longest_gid_length)
51-
header = <<~HEADER
52-
# Shopify Product Taxonomy - Attribute Values: #{version}
53-
# Format: {GID} : {Value name} [{Attribute name}]
54-
HEADER
55-
[
56-
header,
57-
*all_values_sorted.map { _1.to_txt(padding:, locale:) },
58-
].join("\n")
59-
end
60-
6132
# Reset all class-level state
6233
def reset
6334
@localizations = nil
@@ -79,13 +50,10 @@ def sort_by_localized_name(values, locale: "en")
7950
end
8051
end
8152

82-
private
83-
84-
def longest_gid_length
85-
largest_id = hashed_by(:id).keys.max
86-
find_by(id: largest_id).gid.length
87-
end
88-
53+
# Sort values according to their English name, taking "other" into account.
54+
#
55+
# @param values [Array<Value>] The values to sort.
56+
# @return [Array<Value>] The sorted values.
8957
def all_values_sorted
9058
all.sort_by do |value|
9159
[
@@ -136,20 +104,5 @@ def primary_attribute
136104
def full_name(locale: "en")
137105
"#{name(locale:)} [#{primary_attribute.name(locale:)}]"
138106
end
139-
140-
#
141-
# Serialization
142-
#
143-
def to_json(locale: "en")
144-
{
145-
"id" => gid,
146-
"name" => name(locale:),
147-
"handle" => handle,
148-
}
149-
end
150-
151-
def to_txt(padding: 0, locale: "en")
152-
"#{gid.ljust(padding)} : #{full_name(locale:)}"
153-
end
154107
end
155108
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
module ProductTaxonomy
6+
module Serializers
7+
module Value
8+
module Dist
9+
class JsonSerializerTest < TestCase
10+
setup do
11+
@value = ProductTaxonomy::Value.new(
12+
id: 1,
13+
name: "Black",
14+
friendly_id: "color__black",
15+
handle: "color__black"
16+
)
17+
@attribute = ProductTaxonomy::Attribute.new(
18+
id: 1,
19+
name: "Color",
20+
friendly_id: "color",
21+
handle: "color",
22+
description: "Color",
23+
values: [@value]
24+
)
25+
ProductTaxonomy::Attribute.add(@attribute)
26+
end
27+
28+
test "serialize returns the JSON representation of the value" do
29+
expected_json = {
30+
"id" => "gid://shopify/TaxonomyValue/1",
31+
"name" => "Black",
32+
"handle" => "color__black"
33+
}
34+
assert_equal expected_json, JsonSerializer.serialize(@value)
35+
end
36+
37+
test "serialize returns the localized JSON representation of the value" do
38+
stub_localizations
39+
40+
expected_json = {
41+
"id" => "gid://shopify/TaxonomyValue/1",
42+
"name" => "Nom en français",
43+
"handle" => "color__black"
44+
}
45+
assert_equal expected_json, JsonSerializer.serialize(@value, locale: "fr")
46+
end
47+
48+
test "serialize_all returns the JSON representation of all values" do
49+
ProductTaxonomy::Value.add(@value)
50+
51+
expected_json = {
52+
"version" => "1.0",
53+
"values" => [
54+
{
55+
"id" => "gid://shopify/TaxonomyValue/1",
56+
"name" => "Black",
57+
"handle" => "color__black"
58+
}
59+
]
60+
}
61+
assert_equal expected_json, JsonSerializer.serialize_all(version: "1.0")
62+
end
63+
64+
test "serialize_all returns the JSON representation of all values sorted by name with 'Other' at the end" do
65+
add_values_for_sorting
66+
67+
expected_json = {
68+
"version" => "1.0",
69+
"values" => [
70+
{
71+
"id" => "gid://shopify/TaxonomyValue/4",
72+
"name" => "Aaaa",
73+
"handle" => "color__aaa"
74+
},
75+
{
76+
"id" => "gid://shopify/TaxonomyValue/2",
77+
"name" => "Bbbb",
78+
"handle" => "color__bbb"
79+
},
80+
{
81+
"id" => "gid://shopify/TaxonomyValue/1",
82+
"name" => "Cccc",
83+
"handle" => "color__ccc"
84+
},
85+
{
86+
"id" => "gid://shopify/TaxonomyValue/3",
87+
"name" => "Other",
88+
"handle" => "color__other"
89+
}
90+
]
91+
}
92+
assert_equal expected_json, JsonSerializer.serialize_all(version: "1.0")
93+
end
94+
95+
test "serialize_all sorts localized values according to their sort order in English" do
96+
add_values_for_sorting
97+
stub_localizations_for_sorting
98+
99+
expected_json = {
100+
"version" => "1.0",
101+
"values" => [
102+
{
103+
"id" => "gid://shopify/TaxonomyValue/4",
104+
"name" => "Zzzz",
105+
"handle" => "color__aaa"
106+
},
107+
{
108+
"id" => "gid://shopify/TaxonomyValue/2",
109+
"name" => "Xxxx",
110+
"handle" => "color__bbb"
111+
},
112+
{
113+
"id" => "gid://shopify/TaxonomyValue/1",
114+
"name" => "Yyyy",
115+
"handle" => "color__ccc"
116+
},
117+
{
118+
"id" => "gid://shopify/TaxonomyValue/3",
119+
"name" => "Autre",
120+
"handle" => "color__other"
121+
}
122+
]
123+
}
124+
assert_equal expected_json, JsonSerializer.serialize_all(version: "1.0", locale: "fr")
125+
end
126+
127+
private
128+
129+
def stub_localizations
130+
fr_yaml = <<~YAML
131+
fr:
132+
values:
133+
color__black:
134+
name: "Nom en français"
135+
YAML
136+
Dir.stubs(:glob)
137+
.with(File.join(ProductTaxonomy.data_path, "localizations", "values", "*.yml"))
138+
.returns(["fake/path/fr.yml"])
139+
YAML.stubs(:safe_load_file).with("fake/path/fr.yml").returns(YAML.safe_load(fr_yaml))
140+
141+
Dir.stubs(:glob)
142+
.with(File.join(ProductTaxonomy.data_path, "localizations", "attributes", "*.yml"))
143+
.returns([])
144+
end
145+
146+
def add_values_for_sorting
147+
[
148+
ProductTaxonomy::Value.new(id: 1, name: "Cccc", friendly_id: "color__ccc", handle: "color__ccc"),
149+
ProductTaxonomy::Value.new(id: 2, name: "Bbbb", friendly_id: "color__bbb", handle: "color__bbb"),
150+
ProductTaxonomy::Value.new(id: 3, name: "Other", friendly_id: "color__other", handle: "color__other"),
151+
ProductTaxonomy::Value.new(id: 4, name: "Aaaa", friendly_id: "color__aaa", handle: "color__aaa")
152+
].each { ProductTaxonomy::Value.add(_1) }
153+
end
154+
155+
def stub_localizations_for_sorting
156+
fr_yaml = <<~YAML
157+
fr:
158+
values:
159+
color__aaa:
160+
name: "Zzzz"
161+
color__bbb:
162+
name: "Xxxx"
163+
color__ccc:
164+
name: "Yyyy"
165+
color__other:
166+
name: "Autre"
167+
YAML
168+
Dir.stubs(:glob)
169+
.with(File.join(ProductTaxonomy.data_path, "localizations", "values", "*.yml"))
170+
.returns(["fake/path/fr.yml"])
171+
YAML.stubs(:safe_load_file).with("fake/path/fr.yml").returns(YAML.safe_load(fr_yaml))
172+
173+
Dir.stubs(:glob)
174+
.with(File.join(ProductTaxonomy.data_path, "localizations", "attributes", "*.yml"))
175+
.returns([])
176+
end
177+
end
178+
end
179+
end
180+
end
181+
end

0 commit comments

Comments
 (0)