Class: VariantGroup
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- VariantGroup
- Includes:
- Models::Auditable
- Defined in:
- app/models/variant_group.rb
Overview
Canonical, marketplace-agnostic product family — the single definition of
"these items are variants of one product" (Phase 1 of
doc/tasks/202607051428_VARIANT_GROUPING_UNIFICATION.md).
The variant AXES (Voltage, Size, …) live on the group (axis_tokens), and
every channel derives its grouping from this one model:
- Website — Item#item_grouping_info resolves the picker from the
family's membership;sort_keys/use_product_picturedrive its display. - Amazon — an override whose catalog is a Seller Central catalog IS the
per-marketplace parent listing (external_sku/ASIN/theme). - Wayfair — #to_wayfair_variant_grouping feeds submitV2's
variantGrouping::groupReferenceIdwith #slug. - Google / OpenAI feeds — #slug is the
itemGroupId.
Membership is explicit (VariantGroupMember) rather than path-derived, so
families that share a SKU prefix or product line but are different products
(Flex Rolls vs shower mats) stay separate.
Catalog inheritance: a group with catalog_id: nil is the DEFAULT
family. A catalog that groups differently gets an OVERRIDE — a child group
with catalog set and parent pointing at the default, AT MOST ONE ACTIVE
per (family, catalog). The override carries the catalog's grouping data
(parent SKU, variation ASIN, theme, schema); item-level marketplace data
(e.g. the Wayfair display SKU an item is listed under) lives on
catalog_items instead. Resolution (#resolved_for,
Item#variant_group_for) is override-first with fallback to the default,
and an override with no explicit members INHERITS the default's members
until members are added.
Defined Under Namespace
Classes: AmazonDefaults
Constant Summary collapse
- DEFAULT_SORT_KEYS =
The sort order every consumer falls back to when a family has no explicit
sort_keys — the universal facet-era default (Www::ProductCatalogPresenter
applies the same pair). ['price asc', 'item_sku asc'].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#catalog_id ⇒ Object
readonly
ONE ACTIVE override per catalog per default family — the override IS the catalog's grouping decision and (for marketplace catalogs) its parent listing.
-
#external_id ⇒ Object
readonly
Marketplace listing id (Amazon ASIN) for an amazon-catalog override; unique per catalog.
-
#external_sku ⇒ Object
readonly
sku/asin are reused across NA marketplaces, so listing identity is unique per catalog (DB partial indexes
idx_variant_groups_external_{sku,id}_per_catalog). -
#name ⇒ Object
readonly
Human-readable family name (normalized); the display label across channels.
-
#slug ⇒ Object
readonly
Stable, unique cross-channel identifier — Wayfair
groupReferenceId, Google/OpenAIitemGroupId.
Belongs to collapse
-
#catalog ⇒ Catalog
The catalog an override targets — catalogs ARE the channel+region (Amazon US/CA Seller Central, Wayfair US/CA, website catalogs 1/2).
-
#cloned_from ⇒ VariantGroup
The row this Amazon relist was version-cloned from (see #deep_dup); nil unless cloned.
-
#parent ⇒ VariantGroup
The default family this override belongs to; nil on a default group.
-
#primary_item ⇒ Item
The member flagged Primary for Wayfair (the family's lead child); optional.
Methods included from Models::Auditable
Has many collapse
-
#catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
Per-catalog listing rows that name this group as their variation parent.
-
#clones ⇒ ActiveRecord::Relation<VariantGroup>
Relist copies made from this row via #deep_dup.
-
#edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
Transport logs for this group's #edi_documents.
-
#edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
EDI documents exchanged for this parent listing.
-
#items ⇒ ActiveRecord::Relation<Item>
The member items, through #variant_group_members.
-
#overrides ⇒ ActiveRecord::Relation<VariantGroup>
Per-catalog override children of this default family (at most one ACTIVE per catalog).
-
#variant_group_members ⇒ ActiveRecord::Relation<VariantGroupMember>
Explicit, ordered membership rows (channel-agnostic).
Has and belongs to many collapse
-
#amazon_browse_nodes ⇒ ActiveRecord::Relation<AmazonBrowseNode>
Amazon browse nodes (recommended categories) for this parent listing.
Class Method Summary collapse
-
.active ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are active.
-
.amazon_channel ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are amazon channel.
-
.amazon_rows_for_select ⇒ Array<Array(String, Integer)>
["parent SKU - name", id]pairs of Amazon parent-listing rows for search selects (replaces AmazonVariation.variations_for_select). -
.asin_search ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are asin search.
-
.axis_token_options ⇒ Array<String>
Every known spec token, for the axis_tokens picker.
-
.defaults ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are defaults.
-
.select_options ⇒ Array<Array(String, Integer)>
["name (slug)", id]pairs for selects (mirrors AmazonMarketplace.select_options). -
.sort_key_options ⇒ Array<String>
Sortable options for the sort_keys picker: catalog view columns plus spec-token sorts (e.g. "spec:width:in asc").
Instance Method Summary collapse
-
#amazon_channel? ⇒ Boolean
Whether this row is an Amazon parent listing (its catalog is a Seller Central catalog).
-
#amazon_delete_variation_listing(catalog) ⇒ Hash
Deletes this parent listing from Amazon via the catalog's orchestrator.
-
#amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true) ⇒ Edi::Amazon::JsonListingGenerator::BaseGenerator
Builds the Amazon Listings JSON generator for this parent variation in the given marketplace, bound to that marketplace's catalog_item.
-
#amazon_marketplace ⇒ AmazonMarketplace?
The marketplace behind this row's catalog.
-
#amazon_product_type_in_effect ⇒ String?
Submittable Listings product type: the explicit schema (e.g. TOWEL_HOLDER) when set, otherwise the desired catalog type.
-
#amazon_pull_listing_information(catalog) ⇒ Hash
Pulls this parent listing's current data from Amazon via the catalog's orchestrator.
-
#amazon_send_put_listing_information(catalog) ⇒ Hash
Sends (PUT) this parent listing's data to Amazon via the catalog's orchestrator.
-
#asin ⇒ String?
The ASIN Amazon assigned this parent listing.
-
#axis_tokens ⇒ Array<String>
The ordered variant axes (spec tokens, e.g. ["voltage", "size"]).
-
#axis_values_for(item) ⇒ Hash{String=>Object}
Per-member axis values, for the UI matrix and channel adapters.
-
#catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id) ⇒ ActiveRecord::Relation<CatalogItem>, Array
This group's catalog_items in the Seller Central catalog for the given Amazon marketplace.
-
#deep_dup ⇒ VariantGroup
Clone this Amazon parent listing for a relist: next -vN external SKU, ASIN dropped (Amazon assigns a fresh one), browse nodes carried over.
-
#effective_items ⇒ ActiveRecord::Relation<Item>
Effective items (see #effective_members for the inheritance rule).
-
#effective_members ⇒ ActiveRecord::Relation<VariantGroupMember>
Effective members for this group: an override with no explicit members inherits the default family's members.
-
#effective_sort_keys ⇒ Array<String>
The variant-picker sort order.
-
#inherits_members? ⇒ Boolean
Whether this override has no explicit members and is currently serving its default family's members.
-
#items_via_catalog ⇒ ActiveRecord::Relation<Item>
Items listed under this Amazon parent, via their catalog_items — the per-marketplace membership the feeds act on (distinct from the family's variant_group_members, which is channel-agnostic).
-
#listing_catalog ⇒ Catalog?
The catalog this row lists into (an override's catalog IS its listing target).
-
#new_override(catalog) ⇒ VariantGroup
A prefilled, unsaved override of this default family for the given catalog — derives a conventional name.
-
#override? ⇒ Boolean
Whether this group is a per-catalog override of a default family.
-
#override_for(catalog) ⇒ VariantGroup?
The override governing the given catalog's grouping — at most one ACTIVE row exists per (family, catalog).
-
#possible_product_types ⇒ Array<String>
Distinct Amazon product types across this parent's member catalog_items — the candidate values for the listing's product-type picker.
-
#reported_vendor_sku(_orchestrator_partner) ⇒ String
The vendor SKU reported to the marketplace API — the parent listing's external SKU, regardless of partner.
-
#resolved_for(catalog) ⇒ VariantGroup
The group that governs the given catalog: this family's override for the catalog when one exists, otherwise the default itself (inheritance).
-
#sku ⇒ String?
The listing SKU this row represents on its channel (Amazon parent SKU).
-
#to_s ⇒ String
"name [id]" for selects and logs.
-
#to_wayfair_variant_grouping(item) ⇒ Edi::Wayfair::VariantGrouping
The Wayfair submitV2 grouping directive for a member of this family.
-
#variation_theme_attributes ⇒ Array<Symbol>
Array of attribute symbols for the current variation theme, e.g.
-
#wayfair_catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
The family's Wayfair catalog rows (US + CA), for the per-catalog panel.
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
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
#catalog_id ⇒ Object (readonly)
ONE ACTIVE override per catalog per default family — the override IS the
catalog's grouping decision and (for marketplace catalogs) its parent
listing. Inactive rows are exempt so retired Amazon relist listings can
accumulate as ASIN history. Item-level marketplace data (e.g. the Wayfair
display SKU an item is listed under) lives on catalog_items, never here.
DB partial index idx_variant_groups_one_active_override_per_catalog.
Validations (if => -> { parent_id.present? && !inactive? } ):
- Uniqueness ({ scope: :parent_id, conditions: -> { active } })
89 90 |
# File 'app/models/variant_group.rb', line 89 validates :catalog_id, uniqueness: { scope: :parent_id, conditions: -> { active } }, if: -> { parent_id.present? && !inactive? } |
#external_id ⇒ Object (readonly)
Marketplace listing id (Amazon ASIN) for an amazon-catalog override; unique per catalog.
Validations:
- Uniqueness ({ scope: :catalog_id })
- Allow_nil
96 |
# File 'app/models/variant_group.rb', line 96 validates :external_id, uniqueness: { scope: :catalog_id }, allow_nil: true |
#external_sku ⇒ Object (readonly)
sku/asin are reused across NA marketplaces, so listing identity is unique
per catalog (DB partial indexes idx_variant_groups_external_{sku,id}_per_catalog).
Validations (if => #amazon_channel? ):
Validations:
- Uniqueness ({ scope: :catalog_id })
- Allow_nil
93 |
# File 'app/models/variant_group.rb', line 93 validates :external_sku, presence: true, if: :amazon_channel? |
#name ⇒ Object (readonly)
Human-readable family name (normalized); the display label across channels.
Validations:
79 |
# File 'app/models/variant_group.rb', line 79 validates :name, presence: true |
#slug ⇒ Object (readonly)
Stable, unique cross-channel identifier — Wayfair groupReferenceId,
Google/OpenAI itemGroupId. Derived once from #name, then frozen.
Validations:
82 |
# File 'app/models/variant_group.rb', line 82 validates :slug, presence: true, uniqueness: true |
Class Method Details
.active ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are active. Active Record Scope
111 |
# File 'app/models/variant_group.rb', line 111 scope :active, -> { where(inactive: false) } |
.amazon_channel ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are amazon channel. Active Record Scope
110 |
# File 'app/models/variant_group.rb', line 110 scope :amazon_channel, -> { joins(:catalog).where.not(catalogs: { amazon_marketplace_id: nil }) } |
.amazon_rows_for_select ⇒ Array<Array(String, Integer)>
["parent SKU - name", id] pairs of Amazon parent-listing rows for search
selects (replaces AmazonVariation.variations_for_select).
143 144 145 |
# File 'app/models/variant_group.rb', line 143 def self.amazon_rows_for_select amazon_channel.where.not(external_sku: nil).order(:external_sku).map { |row| ["#{row.external_sku} - #{row.name}", row.id] } end |
.asin_search ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are asin search. Active Record Scope
113 114 115 116 117 |
# File 'app/models/variant_group.rb', line 113 scope :asin_search, ->(asin) { where(external_id: asin).or( where("EXISTS (SELECT 1 FROM jsonb_each(COALESCE(channel_settings->'retailer_information', '{}'::jsonb)) AS info WHERE info.value->>'asin' = ?)", asin) ) } |
.axis_token_options ⇒ Array<String>
Every known spec token, for the axis_tokens picker.
127 128 129 |
# File 'app/models/variant_group.rb', line 127 def self. ProductSpecification. end |
.defaults ⇒ ActiveRecord::Relation<VariantGroup>
A relation of VariantGroups that are defaults. Active Record Scope
108 |
# File 'app/models/variant_group.rb', line 108 scope :defaults, -> { where(parent_id: nil, catalog_id: nil) } |
.select_options ⇒ Array<Array(String, Integer)>
["name (slug)", id] pairs for selects (mirrors AmazonMarketplace.select_options).
121 122 123 |
# File 'app/models/variant_group.rb', line 121 def self. order(:name).map { |group| ["#{group.name} (#{group.slug})", group.id] } end |
.sort_key_options ⇒ Array<String>
Sortable options for the sort_keys picker: catalog view columns plus
spec-token sorts (e.g. "spec:width:in asc").
134 135 136 137 138 |
# File 'app/models/variant_group.rb', line 134 def self. = ViewProductCatalog.columns.map(&:name).sort.flat_map { |n| ["#{n} asc", "#{n} desc"] } = ProductSpecification..flat_map { |t| ["spec:#{t} asc", "spec:#{t} desc"] } + end |
Instance Method Details
#amazon_browse_nodes ⇒ ActiveRecord::Relation<AmazonBrowseNode>
Amazon browse nodes (recommended categories) for this parent listing.
66 67 |
# File 'app/models/variant_group.rb', line 66 has_and_belongs_to_many :amazon_browse_nodes, join_table: 'amazon_browse_nodes_variant_groups' |
#amazon_channel? ⇒ Boolean
Returns whether this row is an Amazon parent listing (its
catalog is a Seller Central catalog).
266 267 268 |
# File 'app/models/variant_group.rb', line 266 def amazon_channel? catalog&.amazon_marketplace_id.present? end |
#amazon_delete_variation_listing(catalog) ⇒ Hash
Deletes this parent listing from Amazon via the catalog's orchestrator.
396 397 398 399 400 401 402 403 404 405 |
# File 'app/models/variant_group.rb', line 396 def amazon_delete_variation_listing(catalog) return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog&.load_orchestrator).respond_to?(:delete_listing_from_catalog_item) res = orchestrator.delete_listing_from_catalog_item(amazon_variation: self) if res.any?(false) { status: :error, message: "#{self}: could not send DELETE listing data to Amazon!" } else { status: :success, message: "#{self}: sent DELETE listing data to Amazon!" } end end |
#amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true) ⇒ Edi::Amazon::JsonListingGenerator::BaseGenerator
Builds the Amazon Listings JSON generator for this parent variation in the
given marketplace, bound to that marketplace's catalog_item.
354 355 356 357 |
# File 'app/models/variant_group.rb', line 354 def amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true) # rubocop:disable Lint/UnusedMethodArgument catalog_item = catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id).first Edi::Amazon::JsonListingGenerator::Factory.generator_for_variation(self, catalog_item:, marketplace_id:, attribute_actions:, language_tag:, business_price_available:) end |
#amazon_marketplace ⇒ AmazonMarketplace?
Returns the marketplace behind this row's catalog.
271 272 273 |
# File 'app/models/variant_group.rb', line 271 def amazon_marketplace catalog&.amazon_marketplace end |
#amazon_product_type_in_effect ⇒ String?
Submittable Listings product type: the explicit schema (e.g. TOWEL_HOLDER)
when set, otherwise the desired catalog type. The listing feed submits
this; never an unlaunched catalog PT (Listings rejects 4000003).
309 310 311 |
# File 'app/models/variant_group.rb', line 309 def amazon_product_type_in_effect amazon_product_schema.presence || amazon_desired_product_type end |
#amazon_pull_listing_information(catalog) ⇒ Hash
Pulls this parent listing's current data from Amazon via the catalog's orchestrator.
364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'app/models/variant_group.rb', line 364 def amazon_pull_listing_information(catalog) # A Vendor Central catalog can resolve to the order-only Edi::MftGateway::Orchestrator, # which lacks the listing methods — respond_to? guards both nil and wrong-orchestrator. return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog&.load_orchestrator).respond_to?(:pull_amazon_variation_listing_information) res = orchestrator.pull_amazon_variation_listing_information(self) if res.any? { |h| h.values.any?(false) } { status: :error, message: "#{self}: could not pull listing data from Amazon!" } else { status: :success, message: "#{self}: listing data pulled from Amazon!" } end end |
#amazon_send_put_listing_information(catalog) ⇒ Hash
Sends (PUT) this parent listing's data to Amazon via the catalog's orchestrator.
381 382 383 384 385 386 387 388 389 390 |
# File 'app/models/variant_group.rb', line 381 def amazon_send_put_listing_information(catalog) return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog&.load_orchestrator).respond_to?(:push_listing_from_amazon_variation) res = orchestrator.push_listing_from_amazon_variation(self) if res.any?(false) { status: :error, message: "#{self}: could not send put listing data to Amazon!" } else { status: :success, message: "#{self}: sent put listing data to Amazon!" } end end |
#asin ⇒ String?
Returns the ASIN Amazon assigned this parent listing.
283 284 285 |
# File 'app/models/variant_group.rb', line 283 def asin external_id end |
#axis_tokens ⇒ Array<String>
The ordered variant axes (spec tokens, e.g. ["voltage", "size"]). Owned by
the group; an override inherits its default's axes when it has none.
211 212 213 214 215 216 |
# File 'app/models/variant_group.rb', line 211 def axis_tokens own = Array(self[:axis_tokens]).compact_blank return own if own.any? override? ? parent.axis_tokens : [] end |
#axis_values_for(item) ⇒ Hash{String=>Object}
Per-member axis values, for the UI matrix and channel adapters.
236 237 238 |
# File 'app/models/variant_group.rb', line 236 def axis_values_for(item) axis_tokens.index_with { |token| item.spec_value(token) }.compact end |
#catalog ⇒ Catalog
The catalog an override targets — catalogs ARE the channel+region
(Amazon US/CA Seller Central, Wayfair US/CA, website catalogs 1/2). A
default family carries no catalog. An amazon-catalog override doubles as
the per-marketplace parent listing: external_sku = parent SKU,
external_id = ASIN, channel_settings = Amazon-specific definition.
53 |
# File 'app/models/variant_group.rb', line 53 belongs_to :catalog, optional: true |
#catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
Per-catalog listing rows that name this group as their variation parent.
60 |
# File 'app/models/variant_group.rb', line 60 has_many :catalog_items, dependent: :nullify, inverse_of: :variant_group |
#catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id) ⇒ ActiveRecord::Relation<CatalogItem>, Array
This group's catalog_items in the Seller Central catalog for the given
Amazon marketplace.
338 339 340 341 342 343 |
# File 'app/models/variant_group.rb', line 338 def catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id) catalog = Catalog.for_amazon_seller_marketplace_identifier(marketplace_id) return [] unless catalog catalog_items.where(catalog_id: catalog.id) end |
#cloned_from ⇒ VariantGroup
The row this Amazon relist was version-cloned from (see #deep_dup); nil unless cloned.
55 |
# File 'app/models/variant_group.rb', line 55 belongs_to :cloned_from, class_name: 'VariantGroup', optional: true |
#clones ⇒ ActiveRecord::Relation<VariantGroup>
Relist copies made from this row via #deep_dup.
57 58 |
# File 'app/models/variant_group.rb', line 57 has_many :clones, class_name: 'VariantGroup', foreign_key: :cloned_from_id, dependent: :nullify, inverse_of: :cloned_from |
#deep_dup ⇒ VariantGroup
Clone this Amazon parent listing for a relist: next -vN external SKU,
ASIN dropped (Amazon assigns a fresh one), browse nodes carried over.
The clone REPLACES this row as the catalog's active override — the caller
must retire the original (see VariantGroupsController#clone), or saving
the copy trips the one-active-override-per-catalog uniqueness.
422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'app/models/variant_group.rb', line 422 def deep_dup raise ArgumentError, 'only Amazon parent listings (external_sku set) can be version-cloned' if external_sku.blank? deep_clone(include: :amazon_browse_nodes, except: %i[external_id slug]) do |original, copy| if copy.is_a?(VariantGroup) copy.external_sku = VariantGroup::AmazonDefaults.next_version_sku(original.external_sku) copy.channel_settings = original.channel_settings.except('retailer_information') copy.cloned_from = original copy.name = "#{original.name} (v#{copy.external_sku[/v(\d+)$/, 1]})" end end end |
#edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
Transport logs for this group's #edi_documents.
64 |
# File 'app/models/variant_group.rb', line 64 has_many :edi_communication_logs, through: :edi_documents |
#edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
EDI documents exchanged for this parent listing.
62 |
# File 'app/models/variant_group.rb', line 62 has_many :edi_documents, dependent: :destroy |
#effective_items ⇒ ActiveRecord::Relation<Item>
Effective items (see #effective_members for the inheritance rule).
204 205 206 |
# File 'app/models/variant_group.rb', line 204 def effective_items Item.where(id: effective_members.select(:item_id)) end |
#effective_members ⇒ ActiveRecord::Relation<VariantGroupMember>
Effective members for this group: an override with no explicit members
inherits the default family's members.
198 199 200 |
# File 'app/models/variant_group.rb', line 198 def effective_members inherits_members? ? parent.variant_group_members : variant_group_members end |
#effective_sort_keys ⇒ Array<String>
The variant-picker sort order. Same inheritance rules as #axis_tokens;
empty means the caller's default applies.
226 227 228 229 230 231 |
# File 'app/models/variant_group.rb', line 226 def effective_sort_keys own = Array(self[:sort_keys]).compact_blank return own if own.any? override? ? parent.effective_sort_keys : [] end |
#inherits_members? ⇒ Boolean
Returns whether this override has no explicit members and is
currently serving its default family's members.
191 192 193 |
# File 'app/models/variant_group.rb', line 191 def inherits_members? override? && variant_group_members.load.none? end |
#items ⇒ ActiveRecord::Relation<Item>
The member items, through #variant_group_members.
46 |
# File 'app/models/variant_group.rb', line 46 has_many :items, through: :variant_group_members |
#items_via_catalog ⇒ ActiveRecord::Relation<Item>
Items listed under this Amazon parent, via their catalog_items — the
per-marketplace membership the feeds act on (distinct from the family's
variant_group_members, which is channel-agnostic).
327 328 329 330 331 |
# File 'app/models/variant_group.rb', line 327 def items_via_catalog Item.joins(store_items: :catalog_items) .where(catalog_items: { variant_group_id: id }) .distinct end |
#listing_catalog ⇒ Catalog?
The catalog this row lists into (an override's catalog IS its listing
target). Drives the pull / push / delete listing actions.
290 291 292 |
# File 'app/models/variant_group.rb', line 290 def listing_catalog catalog end |
#new_override(catalog) ⇒ VariantGroup
A prefilled, unsaved override of this default family for the given
catalog — derives a conventional name. Used by the
"Create override" flow; the override starts empty (serving the default's
members).
158 159 160 161 |
# File 'app/models/variant_group.rb', line 158 def new_override(catalog) overrides.new(catalog: catalog, name: catalog ? "#{name} (#{catalog.name} override)" : nil) end |
#override? ⇒ Boolean
Returns whether this group is a per-catalog override of a default family.
148 149 150 |
# File 'app/models/variant_group.rb', line 148 def override? parent_id.present? end |
#override_for(catalog) ⇒ VariantGroup?
The override governing the given catalog's grouping — at most one ACTIVE
row exists per (family, catalog). Reads the loaded association when
present so collection renders can preload :overrides and stay N+1-free.
180 181 182 183 184 185 186 187 |
# File 'app/models/variant_group.rb', line 180 def override_for(catalog) target_catalog_id = catalog.respond_to?(:id) ? catalog.id : catalog.to_i if overrides.loaded? overrides.detect { |override| override.catalog_id == target_catalog_id && !override.inactive? } else overrides.active.find_by(catalog_id: target_catalog_id) end end |
#overrides ⇒ ActiveRecord::Relation<VariantGroup>
Per-catalog override children of this default family (at most one ACTIVE per catalog).
41 42 |
# File 'app/models/variant_group.rb', line 41 has_many :overrides, class_name: 'VariantGroup', foreign_key: :parent_id, dependent: :destroy, inverse_of: :parent |
#parent ⇒ VariantGroup
The default family this override belongs to; nil on a default group.
39 |
# File 'app/models/variant_group.rb', line 39 belongs_to :parent, class_name: 'VariantGroup', optional: true, inverse_of: :overrides |
#possible_product_types ⇒ Array<String>
Distinct Amazon product types across this parent's member catalog_items —
the candidate values for the listing's product-type picker.
316 317 318 319 320 321 |
# File 'app/models/variant_group.rb', line 316 def possible_product_types CatalogItem.joins(store_item: :item) .merge(items_via_catalog).where.not(amazon_desired_product_type: nil) .distinct.order(:amazon_desired_product_type) .pluck(:amazon_desired_product_type) end |
#primary_item ⇒ Item
The member flagged Primary for Wayfair (the family's lead child); optional.
Validated to be an actual member — see #primary_item_must_be_member.
37 |
# File 'app/models/variant_group.rb', line 37 belongs_to :primary_item, class_name: 'Item', optional: true |
#reported_vendor_sku(_orchestrator_partner) ⇒ String
The vendor SKU reported to the marketplace API — the parent listing's
external SKU, regardless of partner.
412 413 414 |
# File 'app/models/variant_group.rb', line 412 def reported_vendor_sku(_orchestrator_partner) external_sku end |
#resolved_for(catalog) ⇒ VariantGroup
The group that governs the given catalog: this family's override for the
catalog when one exists, otherwise the default itself (inheritance).
Calling this on an override delegates through its default first.
168 169 170 171 172 173 |
# File 'app/models/variant_group.rb', line 168 def resolved_for(catalog) base = override? ? parent : self return base if catalog.blank? base.override_for(catalog) || base end |
#sku ⇒ String?
The listing SKU this row represents on its channel (Amazon parent SKU).
Canonical reader for the listing generators.
278 279 280 |
# File 'app/models/variant_group.rb', line 278 def sku external_sku end |
#to_s ⇒ String
Returns "name [id]" for selects and logs.
436 437 438 |
# File 'app/models/variant_group.rb', line 436 def to_s "#{name} [#{id}]" end |
#to_wayfair_variant_grouping(item) ⇒ Edi::Wayfair::VariantGrouping
The Wayfair submitV2 grouping directive for a member of this family.
243 244 245 246 247 248 249 |
# File 'app/models/variant_group.rb', line 243 def to_wayfair_variant_grouping(item) Edi::Wayfair::VariantGrouping.new( group_reference_id: slug, categories: axis_values_for(item).transform_keys { |t| Edi::Wayfair::VariantGrouping::TOKEN_CATEGORY_MAP[t.to_s.downcase] || t.to_s.titleize }, primary: item.id == primary_item_id ) end |
#variant_group_members ⇒ ActiveRecord::Relation<VariantGroupMember>
Explicit, ordered membership rows (channel-agnostic).
44 |
# File 'app/models/variant_group.rb', line 44 has_many :variant_group_members, -> { order(:position, :id) }, dependent: :destroy, inverse_of: :variant_group |
#variation_theme_attributes ⇒ Array<Symbol>
Array of attribute symbols for the current variation theme,
e.g. "SIZE_NAME/COLOR_NAME" => [:size, :color].
297 298 299 300 301 302 303 |
# File 'app/models/variant_group.rb', line 297 def variation_theme_attributes return [] if variation_theme_name.blank? variation_theme_name.split('/').filter_map do |component| VariantGroup::AmazonDefaults::VARIATION_THEME_ATTRIBUTE_MAP[component] end.uniq end |
#wayfair_catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
The family's Wayfair catalog rows (US + CA), for the per-catalog panel.
Membership resolves per Wayfair catalog (each may carry its own override).
254 255 256 257 258 259 260 |
# File 'app/models/variant_group.rb', line 254 def wayfair_catalog_items scopes = CatalogConstants::WAYFAIR_CATALOGS.map do |catalog_id| CatalogItem.where(catalog_id: catalog_id, store_item: StoreItem.where(item_id: resolved_for(catalog_id).effective_members.select(:item_id))) end scopes.reduce(:or).includes(:item, :catalog) end |