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

Instance Method Details

#build_package_content_entriesArray<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.

Returns:

  • (Array<Hash>)

    entries with :name_form (display name) and
    :sku_form (SKU) string values

See Also:



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.

Parameters:

  • group_entries (Array<Hash>)

    entries with :name_form and
    :sku_form keys

  • char_limit (Integer)

    maximum allowed length of the joined string

Returns:

  • (String, nil)

    the ; -joined group, or nil when it cannot be
    condensed to fit char_limit



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.

Parameters:

Returns:

  • (Array<Array<Hash>>)

    one array of entries per group



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_htmlActiveSupport::SafeBuffer

Renders the package contents as an HTML unordered list.

Returns:

  • (ActiveSupport::SafeBuffer)

    <ul> of the package contents,
    marked HTML-safe

See Also:



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_nameString?

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.

Returns:

  • (String, nil)

    the short name, or nil when untranslated



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.

Parameters:

  • char_limit (Integer) (defaults to: 150)

    maximum characters allowed per output string

  • max_unique_items (Integer, nil) (defaults to: nil)

    optional cap on how many strings
    to return; nil or blank returns one ungrouped entry per content entry

Returns:

  • (Array<String>)

    package content strings; empty when no contents
    exist, max_unique_items is non-positive, or grouping cannot fit

See Also:



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_friendlyString

Joins the ungrouped package contents into a single display string.

Returns:

  • (String)

    package contents joined by "; "

See Also:



153
154
155
# File 'app/concerns/models/item_package_content_helper.rb', line 153

def package_contents_friendly
  package_contents_array.join('; ')
end