Class: ProductCategory
Overview
== Schema Information
Table name: product_categories
Database name: primary
id :integer not null, primary key
auto_expire_days :integer
available_to_public :boolean default(TRUE), not null
cached_ancestor_ids :integer default([]), not null, is an Array
children_count :integer
custom_product :boolean default(FALSE), not null
inactive :boolean default(FALSE), not null
legacy_publication :boolean
level :integer
lineage_expanded :text
ltree_path_ids :ltree
ltree_path_slugs :ltree
name :string(255)
nmfc_class :string
nmfc_code :string
priority :integer default(100)
restricted_for_sales :boolean default(FALSE), not null
reviewable :boolean
show_in_sales_portal :boolean default(FALSE), not null
show_in_support_portal :boolean default(TRUE), not null
skip_follow_up :boolean default(FALSE), not null
skip_siblings :boolean default(FALSE), not null
surface_calculation_method :string(255)
url :string(255)
created_at :datetime
updated_at :datetime
item_image_id :integer
parent_id :integer
Indexes
idx_product_categories_ltree_path_ids (ltree_path_ids) USING gist
idx_product_categories_ltree_path_slugs (ltree_path_slugs) USING gist
index_product_categories_on_cached_ancestor_ids (cached_ancestor_ids) USING gin
index_product_categories_on_url (url) UNIQUE
reviewable_id (reviewable,id)
Defined Under Namespace
Classes: CacheFlusher
Constant Summary
Models::Auditable::ALWAYS_IGNORED
Instance Attribute Summary collapse
#skip_ltree_rebuild
#children
Has and belongs to many
collapse
Class Method Summary
collapse
Instance Method Summary
collapse
#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater
#build_ltree_path_ids_value, #build_ltree_path_slugs_value, #build_slug_ltree_value, builds_ltree_path, #compute_ltree_ancestor_ids, #derive_cached_ancestor_ids, #ltree_descendant_ids, rebuild_all_ltree_paths!, rebuild_subtree_ltree_paths!
acts_as_ltree_lineage, ancestors_ids, #ancestors_ids, define_ltree_scopes, define_ordered_ltree_methods, #descendant_of_path?, descendants_ids, #descendants_ids, #generate_full_name, #generate_full_name_array, #lineage, #lineage_array, #lineage_simple, #ltree_ancestor_of?, #ltree_descendant_of?, #ltree_slug, #ltree_slug_path, #parent, #path_includes?, #root, #root?, #root_id, root_ids, self_ancestors_and_descendants_ids, #self_ancestors_and_descendants_ids, self_and_ancestors_ids, #self_and_ancestors_ids, #self_and_children, self_and_descendants_ids, #self_and_descendants_ids, #self_and_siblings, #siblings
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
#publish_event
Instance Attribute Details
#name ⇒ Object
60
|
# File 'app/models/product_category.rb', line 60
validates :name, presence: true
|
#url ⇒ Object
61
|
# File 'app/models/product_category.rb', line 61
validates :url, uniqueness: true
|
Class Method Details
.all_non_publication_goods_ids ⇒ Object
169
170
171
|
# File 'app/models/product_category.rb', line 169
def self.all_non_publication_goods_ids
where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_GOODS)).pluck(:id) - all_publication_ids
end
|
.all_publication_ids ⇒ Object
.all_reviewable_ids ⇒ Object
Uses ltree for efficient hierarchy queries (single query each)
144
145
146
147
148
149
|
# File 'app/models/product_category.rb', line 144
def self.all_reviewable_ids
paths = where(reviewable: true).pluck(:ltree_path_ids).compact
return [] if paths.empty?
where(ProductCategory[:ltree_path_ids].ltree_descendant(paths)).pluck(:id)
end
|
.available_to_public ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are available to public. Active Record Scope
98
|
# File 'app/models/product_category.rb', line 98
scope :available_to_public, -> { where(available_to_public: true) }
|
.by_name_with_descendants ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are by name with descendants. Active Record Scope
76
77
78
79
|
# File 'app/models/product_category.rb', line 76
scope :by_name_with_descendants, ->(pc_n) {
path = where(name: pc_n).pick(:ltree_path_ids)
path ? where(ProductCategory[:ltree_path_ids].ltree_descendant(path)) : none
}
|
.by_url_with_descendants ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are by url with descendants. Active Record Scope
81
82
83
84
85
86
|
# File 'app/models/product_category.rb', line 81
scope :by_url_with_descendants, ->(pc_url) {
pc = find_by(url: pc_url)
return none unless pc&.ltree_path_ids
where(ProductCategory[:ltree_path_ids].ltree_descendant(pc.ltree_path_ids))
}
|
.custom_product_ids ⇒ Object
Returns IDs of all custom product categories
163
164
165
166
167
|
# File 'app/models/product_category.rb', line 163
def self.custom_product_ids
Rails.cache.fetch(:product_category_custom_product_ids, expires_in: 1.day) do
where(custom_product: true).pluck(:id)
end
end
|
.for_sales_portal ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are for sales portal. Active Record Scope
96
|
# File 'app/models/product_category.rb', line 96
scope :for_sales_portal, -> { where(show_in_sales_portal: true) }
|
.for_support_portal ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are for support portal. Active Record Scope
93
|
# File 'app/models/product_category.rb', line 93
scope :for_support_portal, -> { where(show_in_support_portal: true) }
|
.goods ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are goods. Active Record Scope
.no_follow_ups_ids ⇒ Object
Returns a list of product category ids which do not require order follow ups
240
241
242
243
244
|
# File 'app/models/product_category.rb', line 240
def self.no_follow_ups_ids
Rails.cache.fetch([:product_categories_no_follow_ups_ids], expires_in: 1.month) do
ProductCategory.where(skip_follow_up: true).map(&:self_and_descendants_ids).flatten
end
end
|
.non_publications ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are non publications. Active Record Scope
.non_publications_select_options ⇒ Object
110
111
112
|
# File 'app/models/product_category.rb', line 110
def self.non_publications_select_options
ProductCategory.non_publications.order(:name).map { |pc| [pc.lineage_expanded, pc.id] }
end
|
.product_categories_hash(product_categories: nil, selected_product_category: nil, selected_product_category_ids: nil) ⇒ Object
Legacy cache methods removed - ltree queries are fast enough
See: doc/tasks/202512032250_LTREE_HIERARCHY_MIGRATION.md
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# File 'app/models/product_category.rb', line 117
def self.product_categories_hash(product_categories: nil, selected_product_category: nil, selected_product_category_ids: nil)
product_categories ||= ProductCategory.roots
data = []
selected_product_category_ids ||= selected_product_category&.self_and_ancestors_ids || []
product_categories.each do |pc|
expanded = selected_product_category_ids.include?(pc.id)
pc_hsh = {
text: pc.decorated_product_category_name,
nodes: product_categories_hash(product_categories: pc.children, selected_product_category_ids: selected_product_category_ids),
id: pc.url,
state: {
checked: false,
disabled: false,
expanded: expanded,
selected: false
},
color: ('darkgreen' if expanded),
dataAttr: {
target: "/product_categories/#{pc.id}/edit"
}
}
data << pc_hsh
end
data
end
|
.publication_select_options ⇒ Object
106
107
108
|
# File 'app/models/product_category.rb', line 106
def self.publication_select_options
ProductCategory.publications.order(:name).map { |pc| [pc.decorated_product_category_name, pc.id] }
end
|
.publications ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are publications. Active Record Scope
.publications_root_id ⇒ Object
Returns the root publications category ID (cached)
156
157
158
159
160
|
# File 'app/models/product_category.rb', line 156
def self.publications_root_id
Rails.cache.fetch(:product_category_publications_root_id, expires_in: 1.day) do
find_by(ltree_path_slugs: LtreePaths::PC_PUBLICATIONS)&.id
end
end
|
.reviewable ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are reviewable. Active Record Scope
95
|
# File 'app/models/product_category.rb', line 95
scope :reviewable, -> { where(id: all_reviewable_ids) }
|
.sales_portal_sorted ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are sales portal sorted. Active Record Scope
97
|
# File 'app/models/product_category.rb', line 97
scope :sales_portal_sorted, -> { for_sales_portal.order(:priority) }
|
.select_options ⇒ Object
102
103
104
|
# File 'app/models/product_category.rb', line 102
def self.select_options
ProductCategory.all.order('lineage_expanded').pluck(:lineage_expanded, :id)
end
|
.services ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are services. Active Record Scope
.support_portal_sorted ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are support portal sorted. Active Record Scope
94
|
# File 'app/models/product_category.rb', line 94
scope :support_portal_sorted, -> { for_support_portal.order(:priority) }
|
.syncs_items_on_ltree_change? ⇒ Boolean
Enable item ltree sync when category paths change
56
57
58
|
# File 'app/models/product_category.rb', line 56
def self.syncs_items_on_ltree_change?
true
end
|
Instance Method Details
#decorated_product_category_name ⇒ Object
262
263
264
265
266
267
268
269
|
# File 'app/models/product_category.rb', line 262
def decorated_product_category_name
display_name = name
display_name << " \u263c" if available_to_public
display_name << ' $' if show_in_sales_portal
display_name << ' ✆' if show_in_support_portal
display_name << ' ★' if reviewable
display_name
end
|
#digital_assets ⇒ ActiveRecord::Relation<DigitalAsset>
66
|
# File 'app/models/product_category.rb', line 66
has_and_belongs_to_many :digital_assets
|
#effective_nmfc_class ⇒ Object
252
253
254
255
256
|
# File 'app/models/product_category.rb', line 252
def effective_nmfc_class
return nmfc_class if nmfc_class.present?
self_and_ancestors.where.not(nmfc_class: nil).order('level desc, priority asc').pick(:nmfc_class)
end
|
#effective_nmfc_code ⇒ Object
246
247
248
249
250
|
# File 'app/models/product_category.rb', line 246
def effective_nmfc_code
return nmfc_code if nmfc_code.present?
self_and_ancestors.where.not(nmfc_code: nil).order('level desc, priority asc').pick(:nmfc_code) || ProductCategoryConstants::FALLBACK_NMFC_CODE
end
|
#facets ⇒ ActiveRecord::Relation<Facet>
68
|
# File 'app/models/product_category.rb', line 68
has_and_belongs_to_many :facets
|
#follow_up? ⇒ Boolean
235
236
237
|
# File 'app/models/product_category.rb', line 235
def follow_up?
self.class.no_follow_ups_ids.exclude?(id)
end
|
#is_accessory? ⇒ Boolean
202
203
204
|
# File 'app/models/product_category.rb', line 202
def is_accessory?
descendant_of_path?(LtreePaths::PC_ACCESSORIES)
end
|
#is_control? ⇒ Boolean
194
195
196
|
# File 'app/models/product_category.rb', line 194
def is_control?
descendant_of_path?(LtreePaths::PC_CONTROLS)
end
|
#is_custom_mat? ⇒ Boolean
#is_goods? ⇒ Boolean
Predicate methods using ltree paths for hierarchy checks
174
175
176
|
# File 'app/models/product_category.rb', line 174
def is_goods?
descendant_of_path?(LtreePaths::PC_GOODS)
end
|
#is_heating_element? ⇒ Boolean
#is_insulation? ⇒ Boolean
#is_mirror? ⇒ Boolean
227
228
229
|
# File 'app/models/product_category.rb', line 227
def is_mirror?
descendant_of_path?(LtreePaths::PC_MIRRORS)
end
|
#is_publication? ⇒ Boolean
186
187
188
|
# File 'app/models/product_category.rb', line 186
def is_publication?
descendant_of_path?(LtreePaths::PC_PUBLICATIONS)
end
|
#is_relay_panel? ⇒ Boolean
#is_reviewable? ⇒ Boolean
206
207
208
209
210
211
212
213
|
# File 'app/models/product_category.rb', line 206
def is_reviewable?
return true if reviewable?
self.class.where(reviewable: true)
.where(ProductCategory[:ltree_path_ids].ltree_ancestor(ltree_path_ids))
.exists?
end
|
#is_service? ⇒ Boolean
178
179
180
|
# File 'app/models/product_category.rb', line 178
def is_service?
descendant_of_path?(LtreePaths::PC_SERVICES)
end
|
#is_shipping? ⇒ Boolean
182
183
184
|
# File 'app/models/product_category.rb', line 182
def is_shipping?
descendant_of_path?(LtreePaths::PC_SHIPPING)
end
|
#is_spare_parts? ⇒ Boolean
231
232
233
|
# File 'app/models/product_category.rb', line 231
def is_spare_parts?
descendant_of_path?(LtreePaths::PC_SPARE_PARTS)
end
|
#is_upgrade? ⇒ Boolean
198
199
200
|
# File 'app/models/product_category.rb', line 198
def is_upgrade?
descendant_of_path?(LtreePaths::PC_UPGRADES)
end
|
#items ⇒ ActiveRecord::Relation<Item>
63
|
# File 'app/models/product_category.rb', line 63
has_many :items, inverse_of: :product_category
|
#product_lines ⇒ ActiveRecord::Relation<ProductLine>
67
|
# File 'app/models/product_category.rb', line 67
has_and_belongs_to_many :product_lines
|
#product_specifications ⇒ ActiveRecord::Relation<ProductSpecification>
64
|
# File 'app/models/product_category.rb', line 64
has_many :product_specifications, inverse_of: :product_category
|
#purge_cache ⇒ Object
271
272
273
274
275
|
# File 'app/models/product_category.rb', line 271
def purge_cache
return unless saved_changes?
CacheWorker.perform_async('cache_class' => 'ProductCategory::CacheFlusher', 'params' => id)
end
|
#root_product_category ⇒ Object
277
278
279
280
281
|
# File 'app/models/product_category.rb', line 277
def root_product_category
@root_product_category ||= begin
self_and_ancestors.where(reviewable: true).order('level desc, priority asc').first || self
end
end
|
#to_s ⇒ Object
258
259
260
|
# File 'app/models/product_category.rb', line 258
def to_s
lineage_expanded || generate_full_name
end
|