Module: Models::Kittable
- Extended by:
- ActiveSupport::Concern
- Includes:
- Memery
- Included in:
- Item
- Defined in:
- app/concerns/models/kittable.rb
Overview
Kit behavior for Item: associations to kit components and parent kits,
kit content strings for display/specs, and validations/callbacks that keep
kits and their components consistent (store presence, box quantities,
weight/dimension consolidation).
Has many collapse
-
#kit_components ⇒ ActiveRecord::Relation<Item>
Component items that make up this kit.
-
#kit_components_for_specs ⇒ ActiveRecord::Relation<Item>
Component items included in spec rendering for this kit.
-
#kit_parents ⇒ ActiveRecord::Relation<Item>
Kits that include this item as a component.
-
#kit_source_item_relations ⇒ ActiveRecord::Relation<ItemRelation>
Relations from kits that include this item as a component.
-
#kit_target_item_relations ⇒ ActiveRecord::Relation<ItemRelation>
Relations from this kit to its component items.
-
#kit_target_item_relations_for_specs ⇒ ActiveRecord::Relation<ItemRelation>
Relations from this kit to component items marked for spec display.
Class Method Summary collapse
-
.kits ⇒ ActiveRecord::Relation<Models::Kittable>
A relation of Models::Kittables that are kits.
-
.non_kits ⇒ ActiveRecord::Relation<Models::Kittable>
A relation of Models::Kittables that are non kits.
Instance Method Summary collapse
-
#box_contents ⇒ Array<String>
Per-box human-readable contents strings (up to 3 boxes).
-
#consolidate_linked_kits ⇒ void
Recalculates and persists weight/dimension rollups on every parent kit.
-
#get_kit_item_ids ⇒ Array<Integer>
Ids of the kit component items (excluding self).
-
#get_kit_items(spec_only: nil) ⇒ ActiveRecord::Relation<Item>
The kit component items, or an empty relation for non-kit items.
-
#get_multi_box_item_contents_item_id_hash_array ⇒ Array<Hash{String => Integer}>
Per-box component quantities keyed by component item id.
-
#get_self_and_kit_item_ids ⇒ Array<Integer>
Ids of self plus all kit components.
-
#get_self_and_kit_items(spec_only: nil) ⇒ Array<Item>
Pulls the item and its kit components.
-
#is_part_of_a_kit? ⇒ Boolean
Whether this item is a component of at least one kit.
-
#is_part_of_an_active_kit? ⇒ Boolean
Whether this item is a component of at least one active kit.
-
#kit_contents(short = false, with_brackets = false) ⇒ String?
Human-readable description of this kit's contents.
-
#shipping_dimensions_changed? ⇒ Boolean
Whether any weight or shipping dimension attribute changed on this save.
-
#store_item_for(store_id, location) ⇒ StoreItem
Finds or creates this item's StoreItem for the given store and location.
Instance Attribute Details
#kit_components ⇒ ActiveRecord::Relation<Item>
Component items that make up this kit.
28 |
# File 'app/concerns/models/kittable.rb', line 28 has_many :kit_components, through: :kit_target_item_relations, source: :target_item |
#kit_components_for_specs ⇒ ActiveRecord::Relation<Item>
Component items included in spec rendering for this kit.
32 |
# File 'app/concerns/models/kittable.rb', line 32 has_many :kit_components_for_specs, through: :kit_target_item_relations_for_specs, source: :target_item |
#kit_parents ⇒ ActiveRecord::Relation<Item>
Kits that include this item as a component.
40 |
# File 'app/concerns/models/kittable.rb', line 40 has_many :kit_parents, through: :kit_source_item_relations, source: :source_item |
#kit_source_item_relations ⇒ ActiveRecord::Relation<ItemRelation>
Relations from kits that include this item as a component.
36 |
# File 'app/concerns/models/kittable.rb', line 36 has_many :kit_source_item_relations, -> { kit_components }, class_name: 'ItemRelation', foreign_key: 'target_item_id', inverse_of: :target_item, dependent: :destroy |
#kit_target_item_relations ⇒ ActiveRecord::Relation<ItemRelation>
Relations from this kit to its component items.
20 |
# File 'app/concerns/models/kittable.rb', line 20 has_many :kit_target_item_relations, -> { kit_components }, class_name: 'ItemRelation', foreign_key: 'source_item_id', inverse_of: :source_item, dependent: :destroy |
#kit_target_item_relations_for_specs ⇒ ActiveRecord::Relation<ItemRelation>
Relations from this kit to component items marked for spec display.
24 |
# File 'app/concerns/models/kittable.rb', line 24 has_many :kit_target_item_relations_for_specs, -> { kit_components_for_specs }, class_name: 'ItemRelation', foreign_key: 'source_item_id', inverse_of: :source_item, dependent: :destroy |
Class Method Details
.kits ⇒ ActiveRecord::Relation<Models::Kittable>
A relation of Models::Kittables that are kits. Active Record Scope
42 |
# File 'app/concerns/models/kittable.rb', line 42 scope :kits, -> { where(is_kit: true) } |
.non_kits ⇒ ActiveRecord::Relation<Models::Kittable>
A relation of Models::Kittables that are non kits. Active Record Scope
43 |
# File 'app/concerns/models/kittable.rb', line 43 scope :non_kits, -> { where(is_kit: false) } |
Instance Method Details
#box_contents ⇒ Array<String>
Per-box human-readable contents strings (up to 3 boxes).
116 117 118 |
# File 'app/concerns/models/kittable.rb', line 116 def box_contents Array.new(3) { |i| kit_target_item_relations.filter_map { |ir| ir.box_quantities[i] > 0 ? "#{ir.box_quantities[i]} x #{ir.target_item.sku}" : nil }.to_sentence(last_word_connector: ' and ') } end |
#consolidate_linked_kits ⇒ void
This method returns an undefined value.
Recalculates and persists weight/dimension rollups on every parent kit.
Runs as an after_update callback when this component's shipping
dimensions changed; delegates each parent kit to Item::KitConsolidator.
90 91 92 |
# File 'app/concerns/models/kittable.rb', line 90 def consolidate_linked_kits kit_parents.each { |kp| Item::KitConsolidator.new(kp).consolidate_weights_and_dimensions.commit } end |
#get_kit_item_ids ⇒ Array<Integer>
Ids of the kit component items (excluding self).
181 182 183 |
# File 'app/concerns/models/kittable.rb', line 181 def get_kit_item_ids get_kit_items.ids end |
#get_kit_items(spec_only: nil) ⇒ ActiveRecord::Relation<Item>
The kit component items, or an empty relation for non-kit items.
165 166 167 168 169 |
# File 'app/concerns/models/kittable.rb', line 165 def get_kit_items(spec_only: nil) return Item.none unless is_kit? spec_only ? kit_components_for_specs : kit_components end |
#get_multi_box_item_contents_item_id_hash_array ⇒ Array<Hash{String => Integer}>
Per-box component quantities keyed by component item id.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'app/concerns/models/kittable.rb', line 124 def get_multi_box_item_contents_item_id_hash_array contents_item_id_hash_array = [] boxes.each_with_index do |_box, i| contents_hash = {} kit_target_item_relations.each do |ir| item_id = ir.target_item.id.to_s if ir.box_quantities[i] > 0 contents_hash[item_id] ||= 0 contents_hash[item_id] += ir.box_quantities[i] end end contents_item_id_hash_array << contents_hash end contents_item_id_hash_array end |
#get_self_and_kit_item_ids ⇒ Array<Integer>
Ids of self plus all kit components.
174 175 176 |
# File 'app/concerns/models/kittable.rb', line 174 def get_self_and_kit_item_ids get_self_and_kit_items.map(&:id) end |
#get_self_and_kit_items(spec_only: nil) ⇒ Array<Item>
Pulls the item and its kit components. If you specify spec only then only those target items marked include_in_spec will be pulled
This allows greater control of how specs are rendered
Always returns an Array. For non-kit items, returns [self] without a DB query.
For kit items, returns self + kit components ordered by category priority then SKU.
Result is memoized per spec_only variant to avoid N+1 queries when called repeatedly
(e.g. once per ProductSpecification in Item::SpecificationsMasher).
150 151 152 153 154 155 156 157 158 |
# File 'app/concerns/models/kittable.rb', line 150 def get_self_and_kit_items(spec_only: nil) if is_kit? item_ids = [id] item_ids += spec_only ? kit_components_for_spec_ids : kit_component_ids Item.where(id: item_ids).joins(:product_category).order(Item[:id].not_eq(id), ProductCategory[:priority], Item[:sku]).to_a else [self] end end |
#is_part_of_a_kit? ⇒ Boolean
Whether this item is a component of at least one kit.
56 57 58 |
# File 'app/concerns/models/kittable.rb', line 56 def is_part_of_a_kit? kit_parents.present? end |
#is_part_of_an_active_kit? ⇒ Boolean
Whether this item is a component of at least one active kit.
63 64 65 |
# File 'app/concerns/models/kittable.rb', line 63 def is_part_of_an_active_kit? kit_parents.active.present? end |
#kit_contents(short = false, with_brackets = false) ⇒ String?
Human-readable description of this kit's contents.
100 101 102 103 104 105 106 107 108 109 110 |
# File 'app/concerns/models/kittable.rb', line 100 def kit_contents(short = false, with_brackets = false) contents = nil if kit_target_item_relations.any? contents = '' contents += "The #{sku} is " unless short contents += 'composed of ' contents += kit_target_item_relations.map { |ir| "#{ir.quantity} x #{ir.target_item.sku}" }.to_sentence(last_word_connector: ' and ') contents = "(#{contents})" if with_brackets end contents end |
#shipping_dimensions_changed? ⇒ Boolean
Whether any weight or shipping dimension attribute changed on this save.
70 71 72 |
# File 'app/concerns/models/kittable.rb', line 70 def shipping_dimensions_changed? base_weight_changed? or shipping_weight_changed? or shipping_length_changed? or shipping_width_changed? or shipping_height_changed? end |
#store_item_for(store_id, location) ⇒ StoreItem
Finds or creates this item's StoreItem for the given store and location.
79 80 81 |
# File 'app/concerns/models/kittable.rb', line 79 def store_item_for(store_id, location) store_items.where(store_id:, location:).first_or_create(qty_available: 0, is_discontinued: false, unit_cogs: 0, qty_committed: 0) end |