Class: ProductCategory
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ProductCategory
- Defined in:
- app/models/product_category.rb
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
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Name is required.
-
#url ⇒ Object
readonly
URL slug must be unique across categories.
Attributes included from Models::LtreePathBuilder
Has many collapse
-
#items ⇒ ActiveRecord::Relation<Item>
Items filed under this category.
-
#product_specifications ⇒ ActiveRecord::Relation<ProductSpecification>
Specification templates attached to this category.
Methods included from Models::LtreeLineage
Has and belongs to many collapse
-
#digital_assets ⇒ ActiveRecord::Relation<DigitalAsset>
Digital assets (images, documents) linked to this category.
-
#product_lines ⇒ ActiveRecord::Relation<ProductLine>
Product lines this category belongs to.
Class Method Summary collapse
-
.all_non_publication_goods_ids ⇒ Array<Integer>
IDs of all goods categories that are not publications.
-
.all_publication_ids ⇒ Array<Integer>
IDs of all categories under the publications root.
-
.all_reviewable_ids ⇒ Object
Uses ltree for efficient hierarchy queries (single query each).
-
.available_to_public ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are available to public.
-
.by_name_with_descendants ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are by name with descendants.
-
.by_url_with_descendants ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are by url with descendants.
-
.custom_product_ids ⇒ Object
Returns IDs of all custom product categories.
-
.for_sales_portal ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are for sales portal.
-
.for_support_portal ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are for support portal.
-
.goods ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are goods.
-
.no_follow_ups_ids ⇒ Object
Returns a list of product category ids which do not require order follow ups.
-
.non_publications ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are non publications.
-
.non_publications_select_options ⇒ Array<Array(String, Integer)>
Non-publication categories as [lineage_expanded, id] pairs for select boxes.
-
.product_categories_hash(product_categories: nil, selected_product_category: nil, selected_product_category_ids: nil) ⇒ Array<Hash>
Legacy cache methods removed - ltree queries are fast enough.
-
.publication_select_options ⇒ Array<Array(String, Integer)>
Publication categories as [decorated name, id] pairs for select boxes.
-
.publications ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are publications.
-
.publications_root_id ⇒ Object
Returns the root publications category ID (cached).
-
.reviewable ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are reviewable.
-
.sales_portal_sorted ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are sales portal sorted.
-
.select_options ⇒ Array<Array(String, Integer)>
All categories as [lineage_expanded, id] pairs for select boxes.
-
.services ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are services.
-
.support_portal_sorted ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are support portal sorted.
-
.syncs_items_on_ltree_change? ⇒ Boolean
Enable item ltree sync when category paths change.
Instance Method Summary collapse
-
#decorated_product_category_name ⇒ String
Category name decorated with glyphs for portal/reviewable flags.
-
#effective_nmfc_class ⇒ String?
NMFC freight class for this category, inheriting from ancestors when unset.
-
#effective_nmfc_code ⇒ String
NMFC freight code for this category, inheriting from ancestors when unset.
-
#follow_up? ⇒ Boolean
Whether this category requires an order follow-up.
-
#is_accessory? ⇒ Boolean
Whether this category sits under the Accessories root.
-
#is_control? ⇒ Boolean
Whether this category sits under the Controls root.
-
#is_custom_mat? ⇒ Boolean
Whether this category sits under the Custom Mats heating-elements branch.
-
#is_goods? ⇒ Boolean
Predicate methods using ltree paths for hierarchy checks Whether this category sits under the Goods root.
-
#is_heating_element? ⇒ Boolean
Whether this category sits under the Heating Elements root.
-
#is_insulation? ⇒ Boolean
Whether this category sits under the Insulations accessories branch.
-
#is_mirror? ⇒ Boolean
Whether this category sits under the Mirrors root.
-
#is_publication? ⇒ Boolean
Whether this category sits under the Publications root.
-
#is_relay_panel? ⇒ Boolean
Whether this category sits under the Power Relay Panels root.
-
#is_reviewable? ⇒ Boolean
Whether this category or any ancestor is reviewable.
-
#is_service? ⇒ Boolean
Whether this category sits under the Services root.
-
#is_shipping? ⇒ Boolean
Whether this category sits under the Shipping root.
-
#is_spare_parts? ⇒ Boolean
Whether this category sits under the Spare Parts root.
-
#is_upgrade? ⇒ Boolean
Whether this category sits under the Upgrades root.
-
#purge_cache ⇒ void
Enqueues a cache flush for this category after commit when it changed.
-
#root_product_category ⇒ ProductCategory
Nearest reviewable ancestor (or self when none).
-
#to_s ⇒ String
String representation of the category.
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater
Methods included from Models::LtreePathBuilder
#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!
Methods included from Models::LtreeLineage
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
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#name ⇒ Object (readonly)
Name is required.
Validations:
62 |
# File 'app/models/product_category.rb', line 62 validates :name, presence: true |
#url ⇒ Object (readonly)
URL slug must be unique across categories.
Validations:
64 |
# File 'app/models/product_category.rb', line 64 validates :url, uniqueness: true |
Class Method Details
.all_non_publication_goods_ids ⇒ Array<Integer>
IDs of all goods categories that are not publications.
188 189 190 |
# File 'app/models/product_category.rb', line 188 def self.all_non_publication_goods_ids where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_GOODS)).ids - all_publication_ids end |
.all_publication_ids ⇒ Array<Integer>
IDs of all categories under the publications root.
168 169 170 |
# File 'app/models/product_category.rb', line 168 def self.all_publication_ids where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_PUBLICATIONS)).ids end |
.all_reviewable_ids ⇒ Object
Uses ltree for efficient hierarchy queries (single query each)
159 160 161 162 163 164 |
# File 'app/models/product_category.rb', line 159 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)).ids end |
.available_to_public ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are available to public. Active Record Scope
104 |
# File 'app/models/product_category.rb', line 104 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
82 83 84 85 |
# File 'app/models/product_category.rb', line 82 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
87 88 89 90 91 92 |
# File 'app/models/product_category.rb', line 87 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
180 181 182 183 184 |
# File 'app/models/product_category.rb', line 180 def self.custom_product_ids Rails.cache.fetch(:product_category_custom_product_ids, expires_in: 1.day) do where(custom_product: true).ids end end |
.for_sales_portal ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are for sales portal. Active Record Scope
102 |
# File 'app/models/product_category.rb', line 102 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
99 |
# File 'app/models/product_category.rb', line 99 scope :for_support_portal, -> { where(show_in_support_portal: true) } |
.goods ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are goods. Active Record Scope
96 |
# File 'app/models/product_category.rb', line 96 scope :goods, -> { where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_GOODS)) } |
.no_follow_ups_ids ⇒ Object
Returns a list of product category ids which do not require order follow ups
289 290 291 292 293 |
# File 'app/models/product_category.rb', line 289 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
94 |
# File 'app/models/product_category.rb', line 94 scope :non_publications, -> { where.not(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_PUBLICATIONS)) } |
.non_publications_select_options ⇒ Array<Array(String, Integer)>
Non-publication categories as [lineage_expanded, id] pairs for select boxes.
122 123 124 |
# File 'app/models/product_category.rb', line 122 def self. ProductCategory.non_publications.order(:name).map { |pc| [pc., pc.id] } end |
.product_categories_hash(product_categories: nil, selected_product_category: nil, selected_product_category_ids: nil) ⇒ Array<Hash>
Legacy cache methods removed - ltree queries are fast enough
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'app/models/product_category.rb', line 132 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| = 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: , selected: false }, color: ('darkgreen' if ), dataAttr: { target: "/product_categories/#{pc.id}/edit" } } data << pc_hsh end data end |
.publication_select_options ⇒ Array<Array(String, Integer)>
Publication categories as [decorated name, id] pairs for select boxes.
116 117 118 |
# File 'app/models/product_category.rb', line 116 def self. 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
98 |
# File 'app/models/product_category.rb', line 98 scope :publications, -> { where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_PUBLICATIONS)) } |
.publications_root_id ⇒ Object
Returns the root publications category ID (cached)
173 174 175 176 177 |
# File 'app/models/product_category.rb', line 173 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
101 |
# File 'app/models/product_category.rb', line 101 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
103 |
# File 'app/models/product_category.rb', line 103 scope :sales_portal_sorted, -> { for_sales_portal.order(:priority) } |
.select_options ⇒ Array<Array(String, Integer)>
All categories as [lineage_expanded, id] pairs for select boxes.
110 111 112 |
# File 'app/models/product_category.rb', line 110 def self. ProductCategory.order(:lineage_expanded).pluck(:lineage_expanded, :id) end |
.services ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are services. Active Record Scope
97 |
# File 'app/models/product_category.rb', line 97 scope :services, -> { where(ProductCategory[:ltree_path_slugs].ltree_descendant(LtreePaths::PC_SERVICES)) } |
.support_portal_sorted ⇒ ActiveRecord::Relation<ProductCategory>
A relation of ProductCategories that are support portal sorted. Active Record Scope
100 |
# File 'app/models/product_category.rb', line 100 scope :support_portal_sorted, -> { for_support_portal.order(:priority) } |
.syncs_items_on_ltree_change? ⇒ Boolean
Enable item ltree sync when category paths change
57 58 59 |
# File 'app/models/product_category.rb', line 57 def self.syncs_items_on_ltree_change? true end |
Instance Method Details
#decorated_product_category_name ⇒ String
Category name decorated with glyphs for portal/reviewable flags.
319 320 321 322 323 324 325 326 |
# File 'app/models/product_category.rb', line 319 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>
Digital assets (images, documents) linked to this category.
72 |
# File 'app/models/product_category.rb', line 72 has_and_belongs_to_many :digital_assets |
#effective_nmfc_class ⇒ String?
NMFC freight class for this category, inheriting from ancestors when unset.
305 306 307 308 309 |
# File 'app/models/product_category.rb', line 305 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 ⇒ String
NMFC freight code for this category, inheriting from ancestors when unset.
297 298 299 300 301 |
# File 'app/models/product_category.rb', line 297 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 |
#follow_up? ⇒ Boolean
Whether this category requires an order follow-up.
284 285 286 |
# File 'app/models/product_category.rb', line 284 def follow_up? self.class.no_follow_ups_ids.exclude?(id) end |
#is_accessory? ⇒ Boolean
Whether this category sits under the Accessories root.
237 238 239 |
# File 'app/models/product_category.rb', line 237 def is_accessory? descendant_of_path?(LtreePaths::PC_ACCESSORIES) end |
#is_control? ⇒ Boolean
Whether this category sits under the Controls root.
225 226 227 |
# File 'app/models/product_category.rb', line 225 def is_control? descendant_of_path?(LtreePaths::PC_CONTROLS) end |
#is_custom_mat? ⇒ Boolean
Whether this category sits under the Custom Mats heating-elements branch.
266 267 268 |
# File 'app/models/product_category.rb', line 266 def is_custom_mat? descendant_of_path?(LtreePaths::PC_HEATING_ELEMENTS_CUSTOM_MATS) end |
#is_goods? ⇒ Boolean
Predicate methods using ltree paths for hierarchy checks
Whether this category sits under the Goods root.
195 196 197 |
# File 'app/models/product_category.rb', line 195 def is_goods? descendant_of_path?(LtreePaths::PC_GOODS) end |
#is_heating_element? ⇒ Boolean
Whether this category sits under the Heating Elements root.
219 220 221 |
# File 'app/models/product_category.rb', line 219 def is_heating_element? descendant_of_path?(LtreePaths::PC_HEATING_ELEMENTS) end |
#is_insulation? ⇒ Boolean
Whether this category sits under the Insulations accessories branch.
260 261 262 |
# File 'app/models/product_category.rb', line 260 def is_insulation? descendant_of_path?(LtreePaths::PC_ACCESSORIES_INSULATIONS) end |
#is_mirror? ⇒ Boolean
Whether this category sits under the Mirrors root.
272 273 274 |
# File 'app/models/product_category.rb', line 272 def is_mirror? descendant_of_path?(LtreePaths::PC_MIRRORS) end |
#is_publication? ⇒ Boolean
Whether this category sits under the Publications root.
213 214 215 |
# File 'app/models/product_category.rb', line 213 def is_publication? descendant_of_path?(LtreePaths::PC_PUBLICATIONS) end |
#is_relay_panel? ⇒ Boolean
Whether this category sits under the Power Relay Panels root.
254 255 256 |
# File 'app/models/product_category.rb', line 254 def is_relay_panel? descendant_of_path?(LtreePaths::PC_POWER_RELAY_PANELS) end |
#is_reviewable? ⇒ Boolean
Whether this category or any ancestor is reviewable.
243 244 245 246 247 248 249 250 |
# File 'app/models/product_category.rb', line 243 def is_reviewable? return true if reviewable? # Check if any ancestor is reviewable using ltree self.class.where(reviewable: true) .where(ProductCategory[:ltree_path_ids].ltree_ancestor(ltree_path_ids)) .exists? end |
#is_service? ⇒ Boolean
Whether this category sits under the Services root.
201 202 203 |
# File 'app/models/product_category.rb', line 201 def is_service? descendant_of_path?(LtreePaths::PC_SERVICES) end |
#is_shipping? ⇒ Boolean
Whether this category sits under the Shipping root.
207 208 209 |
# File 'app/models/product_category.rb', line 207 def is_shipping? descendant_of_path?(LtreePaths::PC_SHIPPING) end |
#is_spare_parts? ⇒ Boolean
Whether this category sits under the Spare Parts root.
278 279 280 |
# File 'app/models/product_category.rb', line 278 def is_spare_parts? descendant_of_path?(LtreePaths::PC_SPARE_PARTS) end |
#is_upgrade? ⇒ Boolean
Whether this category sits under the Upgrades root.
231 232 233 |
# File 'app/models/product_category.rb', line 231 def is_upgrade? descendant_of_path?(LtreePaths::PC_UPGRADES) end |
#items ⇒ ActiveRecord::Relation<Item>
Items filed under this category.
67 |
# File 'app/models/product_category.rb', line 67 has_many :items, inverse_of: :product_category |
#product_lines ⇒ ActiveRecord::Relation<ProductLine>
Product lines this category belongs to.
74 |
# File 'app/models/product_category.rb', line 74 has_and_belongs_to_many :product_lines |
#product_specifications ⇒ ActiveRecord::Relation<ProductSpecification>
Specification templates attached to this category.
69 |
# File 'app/models/product_category.rb', line 69 has_many :product_specifications, inverse_of: :product_category |
#purge_cache ⇒ void
This method returns an undefined value.
Enqueues a cache flush for this category after commit when it changed.
330 331 332 333 334 |
# File 'app/models/product_category.rb', line 330 def purge_cache return unless saved_changes? CacheWorker.perform_async('cache_class' => 'ProductCategory::CacheFlusher', 'params' => id) end |
#root_product_category ⇒ ProductCategory
Nearest reviewable ancestor (or self when none).
338 339 340 |
# File 'app/models/product_category.rb', line 338 def root_product_category @root_product_category ||= self_and_ancestors.where(reviewable: true).order(level: :desc, priority: :asc).first || self end |
#to_s ⇒ String
String representation of the category.
313 314 315 |
# File 'app/models/product_category.rb', line 313 def to_s || generate_full_name end |