Module: Models::ItemSpecificationHelper

Extended by:
ActiveSupport::Concern
Includes:
Memery
Included in:
Item
Defined in:
app/concerns/models/item_specification_helper.rb

Instance Method Summary collapse

Instance Method Details

#ampsObject



379
380
381
382
# File 'app/concerns/models/item_specification_helper.rb', line 379

def amps
  a = spec_value(:amps)
  a.is_a?(Numeric) ? a : nil
end

#amps=(val) ⇒ Object



384
385
386
# File 'app/concerns/models/item_specification_helper.rb', line 384

def amps=(val)
  create_or_set_spec_value(token: :amps, name: 'Amps', units: 'A', grouping: 'Electrical', text_blurb: val)
end

#amps_static?Boolean

Returns:

  • (Boolean)


516
517
518
# File 'app/concerns/models/item_specification_helper.rb', line 516

def amps_static?
  rendered_product_specifications.dig(:amps, :static).to_b
end

#area(output_unit: :sqft) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
# File 'app/concerns/models/item_specification_helper.rb', line 218

def area(output_unit: :sqft)
  v = spec_value(:coverage, output_unit:)
  v ||= spec_value(:sqft, output_unit: :output_unit)
  if v.nil? && length.present? && width.present?
    cv = (length(output_unit: :in) * width(output_unit: :in))

    cvunit = RubyUnits::Unit.new("#{cv} sqin")
    v = cvunit.convert_to(output_unit.to_s).scalar.to_f
  end
  v
end

#aspectObject



651
652
653
654
655
656
657
658
659
660
661
# File 'app/concerns/models/item_specification_helper.rb', line 651

def aspect
  return unless (height = spec_value(:height)).present? && (width == spec_value(:width)).present?

  if height.to_i == width.to_i
    'Vertical / Horizontal'
  elsif height.to_i > width.to_i
    'Vertical'
  else
    'Horizontal'
  end
end

#btu_per_hourObject



548
549
550
551
552
553
554
555
556
# File 'app/concerns/models/item_specification_helper.rb', line 548

def btu_per_hour
  return unless (w = watts.presence)
  return unless w.to_f.positive?

  # If we have a thermal efficiency we will use it to calculate or assume 100
  efficiency = spec_value(:thermal_output_efficiency)&.to_f || 100.0
  # Apply the BTU conversion
  (w.to_f * (efficiency / 100.0) * RoomConfiguration::BTU_PER_HOUR_PER_WATT).round
end

#calculate_amps(precision: 2) ⇒ Object



407
408
409
# File 'app/concerns/models/item_specification_helper.rb', line 407

def calculate_amps(precision: 2)
  ohms_law_calculator.current&.round(precision)
end

#calculate_coverage_for_membrane(output_unit: :sqft) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'app/concerns/models/item_specification_helper.rb', line 204

def calculate_coverage_for_membrane(output_unit: :sqft)
  coverage = 0.0
  if is_membrane?
    coverage = spec_value(:coverage, output_unit:)
  elsif is_kit?
    kit_target_item_relations.includes(:target_item).each do |tir|
      next if tir.target_item.nil? || !tir.target_item.is_membrane?

      coverage += tir.target_item.spec_value(:coverage, output_unit:) * tir.quantity
    end
  end
  coverage&.positive? ? coverage.round(2) : nil
end

#calculate_geometric_sqftObject



583
584
585
# File 'app/concerns/models/item_specification_helper.rb', line 583

def calculate_geometric_sqft
  calculate_sqft(heated_area: false)
end

#calculate_heated_area_for_panels(output_unit: :sqft) ⇒ Object

Calculates the coverage area for a radiant panel based on the watts, output_unit can be :sqm for square meters.



185
186
187
188
189
190
191
192
193
194
# File 'app/concerns/models/item_specification_helper.rb', line 185

def calculate_heated_area_for_panels(output_unit: :sqft)
  return unless (w = watts).present?
  return unless is_radiant_panel?

  coverage_panel_in_sq_ft = (w.to_f / 7).ceil
  return coverage_panel_in_sq_ft if output_unit == :sqft

  coverage_unitized = "#{coverage_panel_in_sq_ft} sqft"
  RubyUnits::Unit.new(coverage_unitized).convert_to(output_unit.to_s).scalar.to_f.round
end

#calculate_heated_area_sqft_for_panelsObject



196
197
198
# File 'app/concerns/models/item_specification_helper.rb', line 196

def calculate_heated_area_sqft_for_panels
  calculate_heated_area_for_panels(output_unit: :sqft)
end

#calculate_heated_area_sqm_for_panelsObject



200
201
202
# File 'app/concerns/models/item_specification_helper.rb', line 200

def calculate_heated_area_sqm_for_panels
  calculate_heated_area_for_panels(output_unit: :sqm)
end

#calculate_kilowatts(precision: 2) ⇒ Object



474
475
476
477
478
# File 'app/concerns/models/item_specification_helper.rb', line 474

def calculate_kilowatts(precision: 2)
  return unless (w = watts || calculate_watts)

  (w.to_f / 1000).round(precision)
end

#calculate_number_of_fixing_strips_at_3_inObject



287
288
289
# File 'app/concerns/models/item_specification_helper.rb', line 287

def calculate_number_of_fixing_strips_at_3_in
  calculate_number_of_fixing_strips_at_cable_spacing(3)
end

#calculate_number_of_fixing_strips_at_4_inObject



291
292
293
# File 'app/concerns/models/item_specification_helper.rb', line 291

def calculate_number_of_fixing_strips_at_4_in
  calculate_number_of_fixing_strips_at_cable_spacing(4)
end

#calculate_number_of_fixing_strips_at_5_inObject



295
296
297
# File 'app/concerns/models/item_specification_helper.rb', line 295

def calculate_number_of_fixing_strips_at_5_in
  calculate_number_of_fixing_strips_at_cable_spacing(5)
end

#calculate_number_of_fixing_strips_at_cable_spacing(spacing_in_inches = 4) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/concerns/models/item_specification_helper.rb', line 274

def calculate_number_of_fixing_strips_at_cable_spacing(spacing_in_inches = 4)
  return unless (cable_length = linear_ft)

  coefficients = { 3 => 0.1875, 4 => 0.225, 5 => 0.3125 }
  raise "Invalid spacing #{spacing_in_inches}, must be #{coefficients.keys.join(', ')}" unless spacing_in_inches.in?(coefficients.keys)

  coefficient = coefficients[spacing_in_inches]
  qty_fixing_strips = ((cable_length * coefficient) + 6)
  qty_fixing_strips = (qty_fixing_strips / 10).ceil * 10
  # minimum 30
  [30, qty_fixing_strips].max
end

#calculate_ohms(precision: 2) ⇒ Object



488
489
490
# File 'app/concerns/models/item_specification_helper.rb', line 488

def calculate_ohms(precision: 2)
  ohms_law_calculator.resistance&.round(precision)
end

#calculate_ohms_per_ftObject



480
481
482
483
484
485
486
# File 'app/concerns/models/item_specification_helper.rb', line 480

def calculate_ohms_per_ft
  l = spec_value(:heating_cable_length, output_unit: 'ft') || spec_value(:length, output_unit: 'ft')
  o = ohms
  return unless o.is_a?(Numeric) && l.is_a?(Numeric) && o.positive? && l.positive?

  (o.to_f / l).round(3)
end

#calculate_size_2d(output_unit: nil, precision: nil) ⇒ Object



421
422
423
424
425
426
427
428
429
# File 'app/concerns/models/item_specification_helper.rb', line 421

def calculate_size_2d(output_unit: nil, precision: nil)
  output_unit ||= spec_unit(:width)
  precision ||= { mm: 0, in: 1, m: 2, ft: 2 }[output_unit&.to_sym]
  s = [
    spec_value(:width, output_unit:, precision:),
    spec_value(:length, output_unit:, precision:) || spec_value(:height, output_unit:, precision:)
  ].join(' x ')
  "#{s} #{output_unit}"
end

#calculate_size_2d_ftObject



462
463
464
# File 'app/concerns/models/item_specification_helper.rb', line 462

def calculate_size_2d_ft
  calculate_size_2d(output_unit: 'ft', precision: 2)
end

#calculate_size_2d_inObject



458
459
460
# File 'app/concerns/models/item_specification_helper.rb', line 458

def calculate_size_2d_in
  calculate_size_2d(output_unit: 'in', precision: 0)
end

#calculate_size_2d_metric_cmObject



470
471
472
# File 'app/concerns/models/item_specification_helper.rb', line 470

def calculate_size_2d_metric_cm
  calculate_size_2d(output_unit: 'cm', precision: 0)
end

#calculate_size_2d_metric_mmObject



466
467
468
# File 'app/concerns/models/item_specification_helper.rb', line 466

def calculate_size_2d_metric_mm
  calculate_size_2d(output_unit: 'mm', precision: 0)
end

#calculate_size_3d(output_unit: nil, precision: nil) ⇒ Object



431
432
433
434
435
436
437
438
439
440
# File 'app/concerns/models/item_specification_helper.rb', line 431

def calculate_size_3d(output_unit: nil, precision: nil)
  output_unit ||= spec_unit(:width)
  precision ||= { mm: 0, in: 1, m: 2, ft: 2 }[output_unit&.to_sym]
  s = [
    spec_value(:width, output_unit:, precision:),
    spec_value(:length, output_unit:, precision:) || spec_value(:height, output_unit:, precision:),
    spec_value(:depth, output_unit:, precision:)
  ].join(' x ')
  "#{s} #{output_unit}"
end

#calculate_size_3d_ftObject



454
455
456
# File 'app/concerns/models/item_specification_helper.rb', line 454

def calculate_size_3d_ft
  calculate_size_3d(output_unit: 'ft', precision: 2)
end

#calculate_size_3d_inObject



450
451
452
# File 'app/concerns/models/item_specification_helper.rb', line 450

def calculate_size_3d_in
  calculate_size_3d(output_unit: 'in', precision: 0)
end

#calculate_size_3d_metric_cmObject



446
447
448
# File 'app/concerns/models/item_specification_helper.rb', line 446

def calculate_size_3d_metric_cm
  calculate_size_3d(output_unit: 'cm', precision: 0)
end

#calculate_size_3d_metric_mmObject



442
443
444
# File 'app/concerns/models/item_specification_helper.rb', line 442

def calculate_size_3d_metric_mm
  calculate_size_3d(output_unit: 'mm', precision: 0)
end

#calculate_sqft(cable_spacing: nil, heated_area: true) ⇒ Object



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'app/concerns/models/item_specification_helper.rb', line 587

def calculate_sqft(cable_spacing: nil, heated_area: true)
  calculated_area = nil
  effective_width = BigDecimal(width, 2) if width
  effective_length = BigDecimal(length, 2) if length
  if effective_length && effective_length.positive?
    case product_category.surface_calculation_method
    when 'standard'
      if effective_width && effective_width.positive?
        if (is_slab_heat_mat? || is_snow_melting_mat?) && heated_area
          # Apply padding rule of 3" (1.5" per side bleed out for heat)
          effective_width += 3.0
        end
        calculated_area = (effective_length * effective_width / 144.0) if effective_width.positive?
      end
    when 'cable_spacing'
      cable_spacing = calculate_ideal_cable_spacing unless cable_spacing && (cable_spacing > 0.0)
      effective_width = (cable_spacing || effective_width).to_f
      calculated_area = (effective_length * effective_width / 144.0) if effective_width.positive?
    end
  end
  static_area = rendered_product_specifications.dig(:coverage, :raw) if rendered_product_specifications.dig(:coverage, :static).to_b
  effective_area = calculated_area || static_area
  effective_area = BigDecimal(effective_area, 2) if effective_area
  effective_area
end

#calculate_thermal_power_per_hourObject

This is like btu per hour but kW



559
560
561
562
563
564
565
566
567
# File 'app/concerns/models/item_specification_helper.rb', line 559

def calculate_thermal_power_per_hour
  return unless (w = watts.presence)
  return unless w.to_f.positive?

  # If we have a thermal efficiency we will use it to calculate or assume 100
  efficiency = spec_value(:thermal_output_efficiency)&.to_f || 100.0
  # Convert to kw/h
  (w.to_f * (efficiency / 100.0) / 1000).round(2)
end

#calculate_volts(precision: 0) ⇒ Object

The voltage V in volts (V) is equal to the current I in amps (A) times the resistance R in ohms (Ω):



412
413
414
# File 'app/concerns/models/item_specification_helper.rb', line 412

def calculate_volts(precision: 0)
  ohms_law_calculator.voltage&.round(precision)
end

#calculate_watts(precision: 0) ⇒ Object

The power P in watts (W) is equal to the voltage V in volts (V) times the current I in amps (A):



417
418
419
# File 'app/concerns/models/item_specification_helper.rb', line 417

def calculate_watts(precision: 0)
  ohms_law_calculator.power&.round(precision)
end

#calculate_watts_per_sqft(precision: 2) ⇒ Object



573
574
575
576
577
578
579
580
581
# File 'app/concerns/models/item_specification_helper.rb', line 573

def calculate_watts_per_sqft(precision: 2)
  tw = watts || calculate_watts
  sqft = spec_value(:coverage) || calculate_sqft
  return unless tw&.positive? && sqft&.positive?

  wps = (BigDecimal(tw.to_s) / sqft).round(precision)
  wps = wps.to_i if wps.frac.zero? # Makes 15.0 => 15
  wps
end

#can_use_amps_for_calculation?Boolean

Returns:

  • (Boolean)


520
521
522
# File 'app/concerns/models/item_specification_helper.rb', line 520

def can_use_amps_for_calculation?
  amps_static? && amps && amps > 0
end

#can_use_ohms_for_calculation?Boolean

Returns:

  • (Boolean)


504
505
506
# File 'app/concerns/models/item_specification_helper.rb', line 504

def can_use_ohms_for_calculation?
  ohms_static? && ohms && ohms > 0
end

#can_use_voltage_for_calculation?Boolean

Returns:

  • (Boolean)


512
513
514
# File 'app/concerns/models/item_specification_helper.rb', line 512

def can_use_voltage_for_calculation?
  voltage_static? && voltage && voltage > 0
end

#can_use_watts_for_calculation?Boolean

Returns:

  • (Boolean)


496
497
498
# File 'app/concerns/models/item_specification_helper.rb', line 496

def can_use_watts_for_calculation?
  watts_static? && watts && watts > 0
end

#cold_lead_lengthObject



176
177
178
# File 'app/concerns/models/item_specification_helper.rb', line 176

def cold_lead_length
  spec_value(:cold_lead_length)
end

#cold_lead_length=(val) ⇒ Object



180
181
182
# File 'app/concerns/models/item_specification_helper.rb', line 180

def cold_lead_length=(val)
  create_or_set_spec_value(token: :cold_lead_length, name: 'Cold Lead Length', units: 'in', grouping: 'Electrical', text_blurb: val)
end

#control_capacityObject



540
541
542
# File 'app/concerns/models/item_specification_helper.rb', line 540

def control_capacity
  (maximum_current || 0) * (num_poles || 1)
end

#coverage_at_3_5_inObject



244
245
246
247
248
# File 'app/concerns/models/item_specification_helper.rb', line 244

def coverage_at_3_5_in
  return unless l = linear_ft

  (l.to_f * 0.291667).round(1)
end

#coverage_at_3_75_inObject



250
251
252
253
254
# File 'app/concerns/models/item_specification_helper.rb', line 250

def coverage_at_3_75_in
  return unless l = linear_ft

  (l.to_f * 0.3125).round(1)
end

#coverage_at_3_inObject



238
239
240
241
242
# File 'app/concerns/models/item_specification_helper.rb', line 238

def coverage_at_3_in
  return unless l = linear_ft

  (l.to_f * 0.25).round(1)
end

#coverage_at_4_5_inObject



262
263
264
265
266
# File 'app/concerns/models/item_specification_helper.rb', line 262

def coverage_at_4_5_in
  return unless l = linear_ft

  (l.to_f * 0.375).round(1)
end

#coverage_at_4_inObject



256
257
258
259
260
# File 'app/concerns/models/item_specification_helper.rb', line 256

def coverage_at_4_in
  return unless l = linear_ft

  (l.to_f * 0.333333).round(1)
end

#coverage_at_5_inObject



268
269
270
271
272
# File 'app/concerns/models/item_specification_helper.rb', line 268

def coverage_at_5_in
  return unless l = linear_ft

  (l.to_f * 0.416667).round(1)
end

#coverage_formattedObject



639
640
641
# File 'app/concerns/models/item_specification_helper.rb', line 639

def coverage_formatted
  spec_output(:coverage)
end

#create_or_set_spec_value(grouping:, text_blurb:, token: nil, name: nil, method: 'text', units: nil, visibility: 'open_visibility', locale: nil, skip_spec_refresh: false, link_to_product_line: false) ⇒ Object

Raises:

  • (ArgumentError)


63
64
65
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/concerns/models/item_specification_helper.rb', line 63

def create_or_set_spec_value(grouping:, text_blurb:, token: nil, name: nil, method: 'text', units: nil, visibility: 'open_visibility', locale: nil, skip_spec_refresh: false, link_to_product_line: false)
  return if text_blurb.blank? # No point

  raise ArgumentError, 'Must specify token or name or both but not none' if token.blank? && name.blank?

  # If we have a name but no token, we derive it
  token ||= ProductSpecification.generate_token_from_name_and_grouping(name, grouping)
  token = token.to_sym
  # If we have a token but no name, we derive the name
  name ||= ProductSpecification.generate_name_from_token_and_grouping(token, grouping)

  attrs = { name: name, token: token, method: method, visibility: visibility, grouping: grouping }
  # if locale is specified but is en-US, we automatically will fall back to en, it makes it easier this way
  locale = :en if locale == :'en-US'
  localized_field_units = nil
  localized_field_text_blurb = nil
  %w[units text_blurb].each do |fn|
    fnl = "#{fn}"
    fnl << if locale.present?
             "_#{LocaleUtility.parameterized_locale(locale)}"
           else
             '_en'
           end
    localized_field_units = fnl.to_sym if fn == 'units'
    localized_field_text_blurb = fnl.to_sym if fn == 'text_blurb'
    attrs[fnl.to_sym] = binding.local_variable_get(fn.to_sym)
  end
  attrs[:grouping] = grouping
  attrs[:skip_spec_refresh] = true if skip_spec_refresh
  ps = direct_product_specifications.find_by(token:)
  ps ||= ProductSpecification.find_by(token: token, product_line_id: primary_product_line_id, product_category_id: product_category_id) if link_to_product_line
  if ps
    ps.do_not_compact_translation_container = true
    # Check if this localized value is really necessary? we check if it is the same as the fallback (in english only)
    if locale.present? && locale != :en && locale.to_s.start_with?('en') &&
       ps.text_blurb_en == attrs[localized_field_text_blurb] &&
       ps.units_en == attrs[localized_field_units]

      # Delete those localized attributes we don't need them
      attrs.delete(localized_field_text_blurb)
      attrs.delete(localized_field_units)
      # Then the localized attribute are simply the same as the fallback and can be removed
      logger.info 'Localized version of attributes is same as fallback, ignoring'
    else
      logger.debug("Updating product specification")
      ps.update(attrs)
    end

  elsif text_blurb.present? # in the context of this method, an empty value doesn't make sense
    # Build a new ProductSpecification and associate it with this item
    logger.debug("Creating product specification")

    ps = ProductSpecification.new(attrs)
    ps.do_not_compact_translation_container = true
    if link_to_product_line
      ps.product_line_id = primary_product_line_id
      ps.product_category_id = product_category_id
    else
      ps.items << self unless ps.items.include?(self)
    end
    ps.save!
  end
  ps
end

#finishObject



569
570
571
# File 'app/concerns/models/item_specification_helper.rb', line 569

def finish
  rendered_product_specifications.dig(:finish, :output)
end

#has_spec?(token) ⇒ Boolean

Returns:

  • (Boolean)


9
10
11
12
13
# File 'app/concerns/models/item_specification_helper.rb', line 9

def has_spec?(token)
  return false if rendered_product_specifications.nil?

  rendered_product_specifications[token.to_sym].present?
end

#is_dual_voltage?Boolean

Returns:

  • (Boolean)


544
545
546
# File 'app/concerns/models/item_specification_helper.rb', line 544

def is_dual_voltage?
  spec_value(:dual_voltage) and spec_value(:dual_voltage).to_s.downcase.first != 'f'
end

#length(output_unit: :in) ⇒ Object



141
142
143
# File 'app/concerns/models/item_specification_helper.rb', line 141

def length(output_unit: :in)
  spec_value(:length, output_unit:)
end

#length=(val, unit: :in) ⇒ Object



145
146
147
# File 'app/concerns/models/item_specification_helper.rb', line 145

def length=(val, unit: :in)
  create_or_set_spec_value(token: :length, name: 'Length', units: unit.to_s, grouping: 'Product Dimensions', text_blurb: val)
end

#length_formattedObject



627
628
629
# File 'app/concerns/models/item_specification_helper.rb', line 627

def length_formatted
  spec_output(:length)
end

#linear_ftObject

memoize :calculate_sqft



614
615
616
617
618
619
620
# File 'app/concerns/models/item_specification_helper.rb', line 614

def linear_ft
  return unless get_self_and_kit_items.any?(&:is_cable_heating_element?)

  return unless length.present?

  length(output_unit: :ft)
end

#maximum_currentObject



532
533
534
# File 'app/concerns/models/item_specification_helper.rb', line 532

def maximum_current
  spec_value(:maximum_current) || spec_value(:maximum_current_per_relay) if is_control? || is_power?
end

#num_polesObject



536
537
538
# File 'app/concerns/models/item_specification_helper.rb', line 536

def num_poles
  spec_value(:numbers_of_poles) if is_relay_panel? || is_legacy_relay?
end

#ohmsObject



351
352
353
354
# File 'app/concerns/models/item_specification_helper.rb', line 351

def ohms
  o = spec_value(:ohms)
  o.is_a?(Numeric) ? o.round(4) : nil
end

#ohms=(val) ⇒ Object



356
357
358
# File 'app/concerns/models/item_specification_helper.rb', line 356

def ohms=(val)
  create_or_set_spec_value(token: :ohms, name: 'Ohms', units: 'ohm', grouping: 'Electrical', text_blurb: val)
end

#ohms_law_calculatorObject



396
397
398
399
400
401
402
403
404
405
# File 'app/concerns/models/item_specification_helper.rb', line 396

def ohms_law_calculator
  @ohms_calc ||= begin
    attrs = {}
    attrs[:resistance] = ohms if can_use_ohms_for_calculation?
    attrs[:voltage] = voltage if can_use_voltage_for_calculation?
    attrs[:power] = watts if can_use_watts_for_calculation?
    attrs[:current] = amps if can_use_amps_for_calculation?
    OhmsLawCalculator.new(attrs).calc
  end
end

#ohms_static?Boolean

Returns:

  • (Boolean)


500
501
502
# File 'app/concerns/models/item_specification_helper.rb', line 500

def ohms_static?
  rendered_product_specifications.dig(:ohms, :static).to_b
end

#plan_groupingObject



524
525
526
# File 'app/concerns/models/item_specification_helper.rb', line 524

def plan_grouping
  spec_value(:plan_grouping)
end

#plan_grouping=(val) ⇒ Object



528
529
530
# File 'app/concerns/models/item_specification_helper.rb', line 528

def plan_grouping=(val)
  create_or_set_spec_value(token: :plan_grouping, name: 'Plan Grouping', grouping: 'Logistics', visibility: 'internal', text_blurb: val)
end

#set_spec_value(token, value) ⇒ Object

Finds a direct product spec and sets the value.



54
55
56
57
58
59
60
61
# File 'app/concerns/models/item_specification_helper.rb', line 54

def set_spec_value(token, value)
  token = token.to_sym
  ds = direct_product_specifications.where(token:).first
  logger.error("No direct_product_specifications present for #{sku} with token #{token}, cannot set spec value") unless ds
  ds&.update_attribute(:text_blurb, value)
  update_rendered_product_specifications
  value
end

#spec(token) ⇒ Object

Retrieves a spec from the rendered product specification
Performs a little bit of value casting on the raw output before returning



17
18
19
20
21
22
23
# File 'app/concerns/models/item_specification_helper.rb', line 17

def spec(token)
  return if rendered_product_specifications.nil?
  return unless (ps = rendered_product_specifications[token.to_sym])

  ps[:raw] = TypeCoercer.coerce(ps[:raw])
  ps
end

#spec_image(token) ⇒ Object



135
136
137
138
139
# File 'app/concerns/models/item_specification_helper.rb', line 135

def spec_image(token)
  return unless (iid = spec(token)&.dig(:image_id))

  Image.find(iid)
end

#spec_output(token, locale: Mobility.locale) ⇒ Object

returns the formatted output from the spec



129
130
131
132
133
# File 'app/concerns/models/item_specification_helper.rb', line 129

def spec_output(token, locale: Mobility.locale)
  Mobility.with_locale(locale) do
    spec(token)&.dig(:output)&.dup
  end
end

#spec_refresh_in_progressObject



25
26
27
# File 'app/concerns/models/item_specification_helper.rb', line 25

def spec_refresh_in_progress
  BackgroundJobStatus.search(worker_klass: 'ItemAttributeWorker', args: id).present?
end

#spec_unit(token) ⇒ Object



46
47
48
49
50
51
# File 'app/concerns/models/item_specification_helper.rb', line 46

def spec_unit(token)
  u = spec(token)&.dig(:units)&.dup
  # Sqft fudging to handle leftover bad data until it transitions over in all product_specifications.units
  u = 'sqft' if /Sq\.? ?Ft\.?/i.match?(u)
  u
end

#spec_value(token, output_unit: nil, precision: nil) ⇒ Object

returns the raw value from the spec



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'app/concerns/models/item_specification_helper.rb', line 30

def spec_value(token, output_unit: nil, precision: nil)
  return unless (v = spec(token)&.dig(:raw)&.dup)

  current_unit_symbol = spec_unit(token)
  if current_unit_symbol.present? && output_unit.present? && current_unit_symbol.to_s != output_unit.to_s

    current_unit_val = RubyUnits::Unit.new("#{v} #{current_unit_symbol}")
    # We're using desired_unit for testing compatibility
    desired_unit = RubyUnits::Unit.new("1 #{output_unit}")
    # only convert if units are compatible
    v = current_unit_val.convert_to(output_unit.to_s).scalar.to_f if current_unit_val.compatible?(desired_unit)
  end
  v = v.round(precision) if precision.present? && v.respond_to?(:round)
  TypeCoercer.coerce(v)
end

#specs_for_identifier_labelObject



643
644
645
646
647
648
649
# File 'app/concerns/models/item_specification_helper.rb', line 643

def specs_for_identifier_label
  hsh = {}
  specifications_for_item_label.each do |rps|
    hsh[rps.name] = rps.output
  end
  hsh
end

#sqftObject



230
231
232
# File 'app/concerns/models/item_specification_helper.rb', line 230

def sqft
  area(output_unit: :sqft)
end

#sqft=(val) ⇒ Object



234
235
236
# File 'app/concerns/models/item_specification_helper.rb', line 234

def sqft=(val)
  create_or_set_spec_value(token: :coverage, name: 'Coverage', units: 'sqft', grouping: 'Product Dimensions', text_blurb: val)
end

#token_specs_values_for_liquid(include_legacy: false) ⇒ Object

Renders a hash with a token spec value (possibly in multiple dimensions)
and its value. This hash can be used for liquid merges



667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'app/concerns/models/item_specification_helper.rb', line 667

def token_specs_values_for_liquid(include_legacy: false)
  options = {}
  options[:item_name] = name
  options[:detailed_description_html] = detailed_description_html
  options[:short_description] = short_description
  options[:feature_1] = feature_1
  options[:feature_2] = feature_2
  options[:feature_3] = feature_3
  options[:feature_4] = feature_4
  options[:feature_5] = feature_5

  rendered_product_specifications&.each do |k, _v|
    options[k] = spec_output(k)
    options["#{k}_formatted"] = spec_output(k) if include_legacy # This is for legacy
    if spec_unit(k)
      options["#{k}_raw"] = spec_value(k)
      options["#{k}_units"] = spec_unit(k)
    end
    supported_dim_units = %i[ft in m cm mm]
    supported_weight_units = %i[lb oz kg g]
    supported_coverage_units = %i[sqft sqm]
    current_unit = spec_unit(k)&.to_sym
    target_units = if supported_dim_units.include?(current_unit)
                     supported_dim_units
                   elsif supported_weight_units.include?(current_unit)
                     supported_weight_units
                   elsif supported_coverage_units.include?(current_unit)
                     supported_coverage_units
                   else
                     []
                   end
    # We iterate through each dimension unit we care to have a translation for and pull the value
    target_units.each do |unit|
      next unless (val = begin
        spec_value(k, output_unit: unit)
      rescue StandardError
        nil
      end)

      options["#{k}_#{unit}_raw"] = val
      options["#{k}_#{unit}_units"] = unit.to_s
    end
  end
  # Now combine computed methods
  ProductSpecification.methods_for_select.each do |method_string|
    method_symbolized = method_string.to_sym
    next if method_symbolized == :text

    begin
      options["#{method_symbolized}"] ||= send(method_symbolized)
    rescue StandardError
      # Ignore this
    end
  end
  options.with_indifferent_access.compact
end

#voltageObject



360
361
362
363
# File 'app/concerns/models/item_specification_helper.rb', line 360

def voltage
  v = spec_value(:voltage)
  v.is_a?(Numeric) ? v : nil
end

#voltage=(val) ⇒ Object



365
366
367
# File 'app/concerns/models/item_specification_helper.rb', line 365

def voltage=(val)
  create_or_set_spec_value(token: :voltage, name: 'Voltage', units: 'V', grouping: 'Electrical', text_blurb: val)
end

#voltage_formattedObject



631
632
633
# File 'app/concerns/models/item_specification_helper.rb', line 631

def voltage_formatted
  spec_output(:voltage)
end

#voltage_static?Boolean

Returns:

  • (Boolean)


508
509
510
# File 'app/concerns/models/item_specification_helper.rb', line 508

def voltage_static?
  rendered_product_specifications.dig(:voltage, :static).to_b
end

#voltsObject



369
370
371
372
# File 'app/concerns/models/item_specification_helper.rb', line 369

def volts
  logger.warn 'Deprecated method volts, use voltage instead'
  voltage
end

#volts=(val) ⇒ Object



374
375
376
377
# File 'app/concerns/models/item_specification_helper.rb', line 374

def volts=(val)
  logger.warn 'Deprecated method volts, use voltage instead'
  create_or_set_spec_value(token: :voltage, name: 'Voltage', units: 'V', grouping: 'Electrical', text_blurb: val)
end

#vopObject



388
389
390
# File 'app/concerns/models/item_specification_helper.rb', line 388

def vop
  spec_value(:vop)
end

#wattsObject



341
342
343
344
345
# File 'app/concerns/models/item_specification_helper.rb', line 341

def watts
  w = spec_value(:watts, output_unit: 'W') # Force watts in case this is expressed in kilowatts

  w.is_a?(Numeric) ? w : nil
end

#watts=(val) ⇒ Object



347
348
349
# File 'app/concerns/models/item_specification_helper.rb', line 347

def watts=(val)
  create_or_set_spec_value(token: :watts, name: 'Watts', units: 'W', grouping: 'Electrical', text_blurb: val)
end

#watts_formattedObject



635
636
637
# File 'app/concerns/models/item_specification_helper.rb', line 635

def watts_formatted
  spec_output(:watts)
end

#watts_per_linear_feet(precision: 2) ⇒ Object



335
336
337
338
339
# File 'app/concerns/models/item_specification_helper.rb', line 335

def watts_per_linear_feet(precision: 2)
  return unless length && watts

  (BigDecimal(watts.to_s) / (length.to_f / 12)).truncate(precision)
end

#watts_per_sqftObject



392
393
394
# File 'app/concerns/models/item_specification_helper.rb', line 392

def watts_per_sqft
  spec_value(:watts_per_sqft) || calculate_watts_per_sqft
end

#watts_per_sqft_at_3_5_in_spacingObject



305
306
307
308
309
# File 'app/concerns/models/item_specification_helper.rb', line 305

def watts_per_sqft_at_3_5_in_spacing
  return unless watts && coverage_at_3_5_in

  (BigDecimal(watts.to_s) / coverage_at_3_5_in).truncate(1)
end

#watts_per_sqft_at_3_75_in_spacingObject



311
312
313
314
315
# File 'app/concerns/models/item_specification_helper.rb', line 311

def watts_per_sqft_at_3_75_in_spacing
  return unless watts && coverage_at_3_75_in

  (BigDecimal(watts.to_s) / coverage_at_3_75_in).truncate(1)
end

#watts_per_sqft_at_3_in_spacingObject



299
300
301
302
303
# File 'app/concerns/models/item_specification_helper.rb', line 299

def watts_per_sqft_at_3_in_spacing
  return unless watts && coverage_at_3_in

  (BigDecimal(watts.to_s) / coverage_at_3_in).truncate(1)
end

#watts_per_sqft_at_4_5_in_spacingObject



323
324
325
326
327
# File 'app/concerns/models/item_specification_helper.rb', line 323

def watts_per_sqft_at_4_5_in_spacing
  return unless watts && coverage_at_4_5_in

  (BigDecimal(watts.to_s) / coverage_at_4_5_in).truncate(1)
end

#watts_per_sqft_at_4_in_spacingObject



317
318
319
320
321
# File 'app/concerns/models/item_specification_helper.rb', line 317

def watts_per_sqft_at_4_in_spacing
  return unless watts && coverage_at_4_in

  (BigDecimal(watts.to_s) / coverage_at_4_in).truncate(1)
end

#watts_per_sqft_at_5_in_spacingObject



329
330
331
332
333
# File 'app/concerns/models/item_specification_helper.rb', line 329

def watts_per_sqft_at_5_in_spacing
  return unless watts && coverage_at_5_in

  (BigDecimal(watts.to_s) / coverage_at_5_in).truncate(1)
end

#watts_static?Boolean

Returns:

  • (Boolean)


492
493
494
# File 'app/concerns/models/item_specification_helper.rb', line 492

def watts_static?
  rendered_product_specifications.dig(:watts, :static).to_b
end

#width(output_unit: :in) ⇒ Object



149
150
151
# File 'app/concerns/models/item_specification_helper.rb', line 149

def width(output_unit: :in)
  spec_value(:width, output_unit:)
end

#width=(val, unit: :in) ⇒ Object



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

def width=(val, unit: :in)
  create_or_set_spec_value(token: :width, name: 'Width', units: unit.to_s, grouping: 'Product Dimensions', text_blurb: val)
end

#width_by_length_textObject



157
158
159
160
161
# File 'app/concerns/models/item_specification_helper.rb', line 157

def width_by_length_text
  return unless length && width

  "#{format('%.2g', width.to_f / 12)}′ x #{format('%d', length.to_f / 12)}"
end

#width_by_length_text_2Object



163
164
165
166
167
# File 'app/concerns/models/item_specification_helper.rb', line 163

def width_by_length_text_2
  return unless length && width

  "#{format('%.2g', width.to_f / 12)}′ x #{format('%02d', length.to_f / 12)}"
end

#width_by_length_text_3Object

For larger rolls



170
171
172
173
174
# File 'app/concerns/models/item_specification_helper.rb', line 170

def width_by_length_text_3
  return unless length && width

  "#{format('%.2g', width.to_f / 12)}′ x #{format('%03d', length.to_f / 12)}"
end

#width_formattedObject



623
624
625
# File 'app/concerns/models/item_specification_helper.rb', line 623

def width_formatted
  spec_output(:width)
end