Class: ProductTaxonomy::Category
- Inherits:
-
Object
- Object
- ProductTaxonomy::Category
- Includes:
- ActiveModel::Validations, FormattedValidationErrors
- Defined in:
- lib/product_taxonomy/models/category.rb
Constant Summary
Constants included from Indexed
Class Attribute Summary collapse
-
.verticals ⇒ Object
readonly
Returns the value of attribute verticals.
Instance Attribute Summary collapse
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#children ⇒ Object
readonly
Returns the value of attribute children.
-
#defined_return_reasons ⇒ Object
readonly
Returns the value of attribute defined_return_reasons.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#inherits_return_reasons ⇒ Object
readonly
Returns the value of attribute inherits_return_reasons.
-
#parent ⇒ Object
Returns the value of attribute parent.
-
#return_reasons ⇒ Object
readonly
Returns the value of attribute return_reasons.
-
#secondary_children ⇒ Object
readonly
Returns the value of attribute secondary_children.
-
#secondary_parents ⇒ Object
Returns the value of attribute secondary_parents.
Class Method Summary collapse
-
.all_depth_first ⇒ Array<Category>
Get all categories in depth-first order.
-
.load_from_source(source_data) ⇒ Object
Load categories from source data.
-
.reset ⇒ Object
Reset all class-level state.
Instance Method Summary collapse
-
#add_attribute(attribute) ⇒ Object
Add an attribute to the category.
-
#add_child(child) ⇒ Object
Add a child to the category.
-
#add_return_reason(return_reason) ⇒ Object
Add a return reason to the category.
-
#add_secondary_child(child) ⇒ Object
Add a secondary child to the category.
-
#ancestors ⇒ Array<Category>
The ancestors of the category.
-
#descendant_of?(category) ⇒ Boolean
Whether the category is a descendant of another category.
-
#descendants ⇒ Object
The descendants of the category.
-
#descendants_and_self ⇒ Array<Category>
The descendants of the category and the category itself.
-
#friendly_name ⇒ String
The friendly name of the category.
-
#full_name(locale: "en") ⇒ String
The full name of the category.
-
#gid ⇒ String
The global ID of the category.
-
#id_parts ⇒ Array<String, Integer>
Split an ID into its parts.
-
#initialize(id:, name:, attributes: [], return_reasons: [], parent: nil) ⇒ Category
constructor
A new instance of Category.
-
#inspect ⇒ Object
Information.
-
#leaf? ⇒ Boolean
Whether the category is a leaf category.
-
#level ⇒ Integer
The level of the category.
-
#next_child_id ⇒ String
The next child ID for the category.
-
#resolve_return_reasons ⇒ Object
Copy return reasons from the closest ancestor that defines its own, when this category inherits.
-
#root ⇒ Category
The root category in this category's tree.
-
#root? ⇒ Boolean
Whether the category is the root category.
-
#traverse {|Category| ... } ⇒ Object
Iterate over the category and all its descendants.
Methods included from Localized
localizations, validate_localizations!
Methods included from Indexed
add, all, create_validate_and_add!, duplicate?, extended, find_by, find_by!, hashed_by, hashed_models, size
Methods included from FormattedValidationErrors
Constructor Details
#initialize(id:, name:, attributes: [], return_reasons: [], parent: nil) ⇒ Category
Returns a new instance of Category.
117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/product_taxonomy/models/category.rb', line 117 def initialize(id:, name:, attributes: [], return_reasons: [], parent: nil) @id = id @name = name @children = [] @secondary_children = [] @attributes = attributes @inherits_return_reasons = return_reasons == :inherit @defined_return_reasons = @inherits_return_reasons ? [] : return_reasons.dup @return_reasons = @defined_return_reasons.dup @parent = parent @secondary_parents = [] end |
Class Attribute Details
.verticals ⇒ Object (readonly)
Returns the value of attribute verticals.
11 12 13 |
# File 'lib/product_taxonomy/models/category.rb', line 11 def verticals @verticals end |
Instance Attribute Details
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def attributes @attributes end |
#children ⇒ Object (readonly)
Returns the value of attribute children.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def children @children end |
#defined_return_reasons ⇒ Object (readonly)
Returns the value of attribute defined_return_reasons.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def defined_return_reasons @defined_return_reasons end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def id @id end |
#inherits_return_reasons ⇒ Object (readonly)
Returns the value of attribute inherits_return_reasons.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def inherits_return_reasons @inherits_return_reasons end |
#parent ⇒ Object
Returns the value of attribute parent.
109 110 111 |
# File 'lib/product_taxonomy/models/category.rb', line 109 def parent @parent end |
#return_reasons ⇒ Object (readonly)
Returns the value of attribute return_reasons.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def return_reasons @return_reasons end |
#secondary_children ⇒ Object (readonly)
Returns the value of attribute secondary_children.
102 103 104 |
# File 'lib/product_taxonomy/models/category.rb', line 102 def secondary_children @secondary_children end |
#secondary_parents ⇒ Object
Returns the value of attribute secondary_parents.
109 110 111 |
# File 'lib/product_taxonomy/models/category.rb', line 109 def secondary_parents @secondary_parents end |
Class Method Details
.all_depth_first ⇒ Array<Category>
Get all categories in depth-first order.
61 62 63 |
# File 'lib/product_taxonomy/models/category.rb', line 61 def all_depth_first verticals.flat_map(&:descendants_and_self) end |
.load_from_source(source_data) ⇒ Object
Load categories from source data.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/product_taxonomy/models/category.rb', line 16 def load_from_source(source_data) raise ArgumentError, "source_data must be an array" unless source_data.is_a?(Array) # First pass: Create all nodes and add to index source_data.each do |item| Category.create_validate_and_add!( id: item["id"], name: item["name"], attributes: Array(item["attributes"]).map { Attribute.find_by(friendly_id: _1) || _1 }, return_reasons: parse_return_reasons(item["return_reasons"]), ) end # Second pass: Build relationships source_data.each do |item| parent = Category.find_by(id: item["id"]) add_children(type: "children", item:, parent:) add_children(type: "secondary_children", item:, parent:) end # Third pass: Validate all nodes, sort contents, and collect root nodes for verticals @verticals = Category.all.each_with_object([]) do |node, root_nodes| node.validate!(:category_tree_loaded) node.children.sort_by!(&:name) node.attributes.sort_by!(&:name) # `return_reasons` order is intentionally preserved from `data/categories/*.yml`. root_nodes << node if node.root? end @verticals.sort_by!(&:name) # Fourth pass: derive each category's effective return reasons — inherited from the closest defining ancestor, # falling back to the global reasons when nothing is defined. Category.all.each(&:resolve_return_reasons) end |
.reset ⇒ Object
Reset all class-level state
52 53 54 55 56 |
# File 'lib/product_taxonomy/models/category.rb', line 52 def reset @localizations = nil @hashed_models = nil @verticals = nil end |
Instance Method Details
#add_attribute(attribute) ⇒ Object
Add an attribute to the category
159 160 161 |
# File 'lib/product_taxonomy/models/category.rb', line 159 def add_attribute(attribute) @attributes << attribute end |
#add_child(child) ⇒ Object
Add a child to the category
137 138 139 140 141 142 143 |
# File 'lib/product_taxonomy/models/category.rb', line 137 def add_child(child) @children << child return unless child.is_a?(Category) child.parent = self end |
#add_return_reason(return_reason) ⇒ Object
Add a return reason to the category. Explicitly adding a reason means the category defines its own reasons rather than inheriting them, so the first add on an inheriting category drops the inherited reasons.
167 168 169 170 171 172 173 174 |
# File 'lib/product_taxonomy/models/category.rb', line 167 def add_return_reason(return_reason) if @inherits_return_reasons @inherits_return_reasons = false @defined_return_reasons = [] end @defined_return_reasons << return_reason resolve_return_reasons end |
#add_secondary_child(child) ⇒ Object
Add a secondary child to the category
148 149 150 151 152 153 154 |
# File 'lib/product_taxonomy/models/category.rb', line 148 def add_secondary_child(child) @secondary_children << child return unless child.is_a?(Category) child.secondary_parents << self end |
#ancestors ⇒ Array<Category>
The ancestors of the category
226 227 228 229 230 |
# File 'lib/product_taxonomy/models/category.rb', line 226 def ancestors return [] if root? [parent] + parent.ancestors end |
#descendant_of?(category) ⇒ Boolean
Whether the category is a descendant of another category
260 261 262 |
# File 'lib/product_taxonomy/models/category.rb', line 260 def descendant_of?(category) ancestors.include?(category) end |
#descendants ⇒ Object
The descendants of the category
273 274 275 |
# File 'lib/product_taxonomy/models/category.rb', line 273 def descendants children.flat_map { |child| [child] + child.descendants } end |
#descendants_and_self ⇒ Array<Category>
The descendants of the category and the category itself
280 281 282 |
# File 'lib/product_taxonomy/models/category.rb', line 280 def descendants_and_self [self] + descendants end |
#friendly_name ⇒ String
The friendly name of the category
287 288 289 |
# File 'lib/product_taxonomy/models/category.rb', line 287 def friendly_name "#{id}_#{IdentifierFormatter.format_friendly_id(name)}" end |
#full_name(locale: "en") ⇒ String
The full name of the category
235 236 237 238 239 |
# File 'lib/product_taxonomy/models/category.rb', line 235 def full_name(locale: "en") return name(locale:) if root? parent.full_name(locale:) + " > " + name(locale:) end |
#gid ⇒ String
The global ID of the category
244 245 246 |
# File 'lib/product_taxonomy/models/category.rb', line 244 def gid "gid://shopify/TaxonomyCategory/#{id}" end |
#id_parts ⇒ Array<String, Integer>
Split an ID into its parts.
251 252 253 254 |
# File 'lib/product_taxonomy/models/category.rb', line 251 def id_parts parts = id.split("-") [parts.first] + parts[1..].map(&:to_i) end |
#inspect ⇒ Object
Information
191 192 193 |
# File 'lib/product_taxonomy/models/category.rb', line 191 def inspect "#<#{self.class.name} id=#{id} name=#{name}>" end |
#leaf? ⇒ Boolean
Whether the category is a leaf category
205 206 207 |
# File 'lib/product_taxonomy/models/category.rb', line 205 def leaf? children.empty? end |
#level ⇒ Integer
The level of the category
212 213 214 |
# File 'lib/product_taxonomy/models/category.rb', line 212 def level ancestors.size end |
#next_child_id ⇒ String
The next child ID for the category
294 295 296 297 298 |
# File 'lib/product_taxonomy/models/category.rb', line 294 def next_child_id largest_child_id = children.map { _1.id.split("-").last.to_i }.max || 0 "#{id}-#{largest_child_id + 1}" end |
#resolve_return_reasons ⇒ Object
Copy return reasons from the closest ancestor that defines its own, when this category inherits. No-op for categories that define their own reasons or have no defining ancestor.
178 179 180 181 182 183 184 185 186 |
# File 'lib/product_taxonomy/models/category.rb', line 178 def resolve_return_reasons @return_reasons = if inherits_return_reasons ancestors.find { |ancestor| !ancestor.inherits_return_reasons }&.defined_return_reasons&.dup || [] else defined_return_reasons.dup end @return_reasons = ReturnReason.global.dup if @return_reasons.empty? end |
#root ⇒ Category
The root category in this category's tree
219 220 221 |
# File 'lib/product_taxonomy/models/category.rb', line 219 def root ancestors.last || self end |
#root? ⇒ Boolean
Whether the category is the root category
198 199 200 |
# File 'lib/product_taxonomy/models/category.rb', line 198 def root? parent.nil? end |
#traverse {|Category| ... } ⇒ Object
Iterate over the category and all its descendants
267 268 269 270 |
# File 'lib/product_taxonomy/models/category.rb', line 267 def traverse(&block) yield self children.each { _1.traverse(&block) } end |