Module: Models::ItemPackageContentHelper
- Extended by:
- ActiveSupport::Concern
- Included in:
- Item
- Defined in:
- app/concerns/models/item_package_content_helper.rb
Overview
ActiveSupport::Concern mixin: item package content helper.
Builds the human-readable list of a kit's (or single item's) package
contents — e.g. "(1) 120V 3x10 TempZone Roll; (1) nSpire Touch
Thermostat" — for product pages and feeds. Condenses entries into a
bounded number of groups, each within a character limit, falling back
from display names to SKUs when space runs out.
Instance Method Summary collapse
-
#build_package_content_entries ⇒ Array<Hash>
Builds the raw package content entries for this item.
-
#condense_group_to_char_limit(group_entries, char_limit) ⇒ String?
Joins a group of entries into one string within
char_limit. -
#group_entries_for_max_unique_items(entries, max_unique_items) ⇒ Array<Array<Hash>>
Distributes entries round-robin into exactly
max_unique_itemsgroups. -
#package_content_html ⇒ ActiveSupport::SafeBuffer
Renders the package contents as an HTML unordered list.
-
#package_content_short_name ⇒ String?
Short display name for this item in package contents.
-
#package_contents_array(char_limit: 150, max_unique_items: nil) ⇒ Array<String>
Builds the package content strings for this item, optionally grouped.
-
#package_contents_friendly ⇒ String
Joins the ungrouped package contents into a single display string.
Instance Method Details
#build_package_content_entries ⇒ Array<Hash>
Builds the raw package content entries for this item.
For kits, emits one entry per kit component (heating elements first,
then thermostats, then membranes, then everything else), skipping
components with no localized name, and appends a "(2) Floor Sensors"
entry for TCT/TRT/RCT kits (two sensors: one in the thermostat box,
one on the roll or cable). For non-kits, emits a single "(1)" entry
when a short name is available.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'app/concerns/models/item_package_content_helper.rb', line 66 def build_package_content_entries entries = [] if is_kit? components = target_item_relations.kit_components .includes(:target_item) .joins(target_item: :product_category) .sort_by do |tir| if tir.target_item.is_heating_element? 0 elsif tir.target_item.is_thermostat? 10 elsif tir.target_item.is_membrane? 20 else 100 end end entries += components.filter_map do |tir| # Skip components with no name in the current locale (see # package_content_short_name) instead of crashing or rendering "(1) ". next unless (name_part = tir.target_item.package_content_short_name) qty = tir.quantity.to_i name_form = "(#{qty}) #{name_part}" sku_form = "(#{qty}) #{tir.target_item.sku}" { name_form: name_form, sku_form: sku_form } end # TRT and TCT kits have two sensors, one in the thermostat box, one on the roll or cable. this should probably be a spec attribute if sku.start_with?('TCT', 'TRT', 'RCT') sensor_form = '(2) Floor Sensors' entries << { name_form: sensor_form, sku_form: sensor_form } end elsif (name_part = package_content_short_name) name_form = "(1) #{name_part}" sku_form = "(1) #{sku}" entries << { name_form: name_form, sku_form: sku_form } end entries end |
#condense_group_to_char_limit(group_entries, char_limit) ⇒ String?
Joins a group of entries into one string within char_limit.
Starts with every entry's name form; while the joined length exceeds
char_limit, swaps entries to their shorter SKU form, largest saving
first.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'app/concerns/models/item_package_content_helper.rb', line 129 def condense_group_to_char_limit(group_entries, char_limit) forms = group_entries.map { |e| { use: :name_form, name_len: e[:name_form].length, sku_len: e[:sku_form].length, ref: e } } current = forms.map { |f| f[:ref][f[:use]] } current_len = current.join('; ').length if current_len > char_limit candidates = forms.map { |f| [f, (f[:name_len] - f[:sku_len])] } .select { |(_f, delta)| delta.positive? } .sort_by { |(_f, delta)| -delta } candidates.each do |f, _delta| f[:use] = :sku_form current = forms.map { |ff| ff[:ref][ff[:use]] } current_len = current.join('; ').length break if current_len <= char_limit end end return nil if current_len > char_limit current.join('; ') end |
#group_entries_for_max_unique_items(entries, max_unique_items) ⇒ Array<Array<Hash>>
Distributes entries round-robin into exactly max_unique_items groups.
112 113 114 115 116 |
# File 'app/concerns/models/item_package_content_helper.rb', line 112 def group_entries_for_max_unique_items(entries, max_unique_items) groups = Array.new(max_unique_items) { [] } entries.each_with_index { |val, idx| groups[idx % max_unique_items] << val } groups end |
#package_content_html ⇒ ActiveSupport::SafeBuffer
Renders the package contents as an HTML unordered list.
187 188 189 |
# File 'app/concerns/models/item_package_content_helper.rb', line 187 def package_content_html %(<ul>#{package_contents_array.map { |c| "<li>#{c}</li>" }.join}</ul>).html_safe end |
#package_content_short_name ⇒ String?
Short display name for this item in package contents.
Returns nil when the item has no name in the current locale: name and
public_short_name are Mobility-translated, and locales without an :en
fallback (e.g. :fr) read nil when untranslated (AppSignal #2040). Callers
skip the entry rather than ship an English fallback under another
language's tag.
Cable fixing strips (see ItemConstants::TZ_CABLE_STRIP_SKUS) always read
"Cable Fixing Strips"; other items use the public name with punctuation
and trademark glyphs stripped, suffixed with the SKU.
170 171 172 173 174 175 176 177 178 179 180 |
# File 'app/concerns/models/item_package_content_helper.rb', line 170 def package_content_short_name if sku.in?(ItemConstants::TZ_CABLE_STRIP_SKUS) n = 'Cable Fixing Strips' else return if public_name.nil? n = public_name.gsub(/[,();™©Ⓒ]/, '').gsub('sq.ft.', 'sq ft').gsub('ft.', 'ft') n << " (#{sku})" end n end |
#package_contents_array(char_limit: 150, max_unique_items: nil) ⇒ Array<String>
Builds the package content strings for this item, optionally grouped.
Without max_unique_items, returns one entry per content entry,
substituting the SKU form when the name form exceeds char_limit.
With max_unique_items, round-robins entries into that many groups
and condenses each group to fit char_limit; when any group cannot
be made to fit, logs a warning and returns an empty array.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'app/concerns/models/item_package_content_helper.rb', line 30 def package_contents_array(char_limit: 150, max_unique_items: nil) entries = build_package_content_entries # If no grouping required, return individual entries respecting char limit per entry if max_unique_items.blank? return entries.map { |e| e[:name_form].length > char_limit ? e[:sku_form] : e[:name_form] } end max_unique_items = max_unique_items.to_i return [] if max_unique_items <= 0 groups = group_entries_for_max_unique_items(entries, max_unique_items) condensed = groups.map { |group_entries| condense_group_to_char_limit(group_entries, char_limit) } if condensed.any?(&:nil?) Rails.logger.warn( "package_contents_array: could not fit grouped contents within char_limit=#{char_limit} and max_unique_items=#{max_unique_items} for item id=#{id}, sku=#{sku}; returning empty array" ) return [] end condensed.compact_blank end |
#package_contents_friendly ⇒ String
Joins the ungrouped package contents into a single display string.
153 154 155 |
# File 'app/concerns/models/item_package_content_helper.rb', line 153 def package_contents_friendly package_contents_array.join('; ') end |