Class: Www::ProductLinePresenter

Inherits:
BasePresenter
  • Object
show all
Includes:
ApplicationHelper, Memery, Presenters::ProductHelpers
Defined in:
app/presenters/www/product_line_presenter.rb

Overview

Presenter: product line presenter.

Constant Summary

Constants included from SeoHelper

SeoHelper::AWARDS, SeoHelper::CA_ADDRESS, SeoHelper::CA_BUSINESS_HOURS, SeoHelper::CA_CONTACT_POINT, SeoHelper::CA_CURRENCIES, SeoHelper::CA_DESCRIPTION, SeoHelper::CA_FOUNDING_DATE, SeoHelper::CA_GLOBAL_LOCATION_NUMBER, SeoHelper::CA_LEGAL_NAME, SeoHelper::CA_LOCAL_BUSINESS, SeoHelper::CA_ONLINE_STORE, SeoHelper::CA_RETURN_POLICY, SeoHelper::CA_SALES_DEPARTMENT, SeoHelper::CA_SERVICE_AREA, SeoHelper::CA_URL, SeoHelper::CA_VAT_ID, SeoHelper::CA_WAREHOUSE_DEPARTMENT, SeoHelper::CA_WAREHOUSE_HOURS, SeoHelper::COMPANY_EMAIL, SeoHelper::COMPANY_LOGO, SeoHelper::COMPANY_NAME, SeoHelper::COMPANY_SLOGAN, SeoHelper::EXPERTISE, SeoHelper::FAX_NUMBER, SeoHelper::GS1_COMPANY_PREFIX, SeoHelper::ISO6523_CODE, SeoHelper::PAYMENT_METHODS, SeoHelper::PHONE_NUMBER, SeoHelper::PRIMARY_NAICS, SeoHelper::REFUND_TYPE, SeoHelper::RETURN_FEES, SeoHelper::RETURN_METHOD, SeoHelper::RETURN_POLICY_CATEGORY, SeoHelper::SECONDARY_NAICS, SeoHelper::SOCIAL_PROFILES, SeoHelper::US_ADDRESS, SeoHelper::US_BUSINESS_HOURS, SeoHelper::US_CONTACT_POINT, SeoHelper::US_CURRENCIES, SeoHelper::US_DESCRIPTION, SeoHelper::US_FOUNDING_DATE, SeoHelper::US_GLOBAL_LOCATION_NUMBER, SeoHelper::US_IMAGE, SeoHelper::US_LEGAL_NAME, SeoHelper::US_LOCAL_BUSINESS, SeoHelper::US_ONLINE_STORE, SeoHelper::US_RETURN_POLICY, SeoHelper::US_SALES_DEPARTMENT, SeoHelper::US_SERVICE_AREA, SeoHelper::US_TAX_ID, SeoHelper::US_URL, SeoHelper::US_WAREHOUSE_DEPARTMENT, SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Delegated Instance Attributes collapse

Instance Method Summary collapse

Methods included from ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #embedded_tab_frame_id, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from SeoHelper

#add_page_schema, #add_webpage_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#accessoriesObject



300
301
302
303
# File 'app/presenters/www/product_line_presenter.rb', line 300

def accessories
  res = Item::Materials::CompatibleAccessories.new.process(item: main_item, product_line: true)
  available_products_scope_sorted.merge(res.items)
end

#any_single_productObject

Fallback to find any single product for the product line, without category filtering.
Used when main_product returns nil due to category constraints.



125
126
127
# File 'app/presenters/www/product_line_presenter.rb', line 125

def any_single_product
  available_products_scope.belongs_to_product_line_id(product_line.id).first
end

#available_products_scopeObject



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/presenters/www/product_line_presenter.rb', line 71

def available_products_scope
  catalog = Catalog.locale_to_catalog(I18n.locale)
  raise 'Invalid catalog locale' unless catalog

  # No need to JOIN item/product_category - we have ltree paths directly on the view
  scope = ViewProductCatalog
          .includes(:item, :catalog_item)
          .where(item_condition: 'new', catalog_id: catalog.id)

  scope = scope.excluding_refurbished if exclude_refurbished
  scope = scope.visible_to_public if visible_to_public

  scope
end

#available_products_scope_sortedObject



86
87
88
# File 'app/presenters/www/product_line_presenter.rb', line 86

def available_products_scope_sorted
  available_products_scope.order(:product_category_priority, :product_category_name, :item_name)
end


470
471
472
473
474
# File 'app/presenters/www/product_line_presenter.rb', line 470

def breadcrumb
  breadcrumb_links_array.map do |bc_opts|
    (:li, link_to(bc_opts[:name], bc_opts[:url], class: 'breadcrumb-item'))
  end.join.html_safe
end


466
467
468
# File 'app/presenters/www/product_line_presenter.rb', line 466

def breadcrumb_links_array
  h.catalog_breadcrumb_links(product_line)
end

#cache_keyObject



32
33
34
# File 'app/presenters/www/product_line_presenter.rb', line 32

def cache_key
  "#{product_line.cache_key}/#{I18n.locale}"
end

#can_be_purchased?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'app/presenters/www/product_line_presenter.rb', line 95

def can_be_purchased?
  product_line.show_in_sales_portal? && product_line.available_to_public && main_products_count.positive?
end


482
483
484
485
486
487
488
489
490
# File 'app/presenters/www/product_line_presenter.rb', line 482

def carousel_assets
  assets_images = images
  assets_videos = videos
  # Take one of each
  assets = [assets_images.shift, assets_videos.shift]
  assets += assets_images
  assets += assets_videos
  assets.compact
end

#control_electrical_rough_in_kitsObject

Alias for Main_product_presenter#control_electrical_rough_in_kits

Returns:

  • (Object)

    Main_product_presenter#control_electrical_rough_in_kits

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#controlsObject

Alias for Main_product_presenter#controls

Returns:

  • (Object)

    Main_product_presenter#controls

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#couponObject



237
238
239
# File 'app/presenters/www/product_line_presenter.rb', line 237

def coupon
  main_products.find(&:coupon)&.coupon
end

#coupon_dataObject



599
600
601
602
603
604
605
606
# File 'app/presenters/www/product_line_presenter.rb', line 599

def coupon_data
  # Reuse memoized main_products instead of re-querying
  promo = main_products_presenters.find(&:coupon)
  return unless promo&.coupon

  c = promo.coupon
  { code: c.code, title: c.title, description: c.description }
end

#currencyObject

Alias for Main_product#currency

Returns:

  • (Object)

    Main_product#currency

See Also:



14
# File 'app/presenters/www/product_line_presenter.rb', line 14

delegate :currency, :currency_symbol, :product_category, to: :main_product, allow_nil: true

#currency_symbolObject

Alias for Main_product#currency_symbol

Returns:

  • (Object)

    Main_product#currency_symbol

See Also:



14
# File 'app/presenters/www/product_line_presenter.rb', line 14

delegate :currency, :currency_symbol, :product_category, to: :main_product, allow_nil: true

#description_htmlObject



310
311
312
# File 'app/presenters/www/product_line_presenter.rb', line 310

def description_html
  product_line.description_html.presence&.html_safe
end

#display_best_product_tahoe?Object

Alias for Main_product_presenter#display_best_product_tahoe?

Returns:

  • (Object)

    Main_product_presenter#display_best_product_tahoe?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_dealers?Object

Alias for Main_product_presenter#display_dealers?

Returns:

  • (Object)

    Main_product_presenter#display_dealers?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_how_much_cable_will_i_need?Object

Alias for Main_product_presenter#display_how_much_cable_will_i_need?

Returns:

  • (Object)

    Main_product_presenter#display_how_much_cable_will_i_need?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_pro_contact_us_for_quote?Object

Alias for Main_product_presenter#display_pro_contact_us_for_quote?

Returns:

  • (Object)

    Main_product_presenter#display_pro_contact_us_for_quote?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_roof_gutter_deicing_quote?Object

Alias for Main_product_presenter#display_roof_gutter_deicing_quote?

Returns:

  • (Object)

    Main_product_presenter#display_roof_gutter_deicing_quote?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_what_size_panel_do_i_need?Object

Alias for Main_product_presenter#display_what_size_panel_do_i_need?

Returns:

  • (Object)

    Main_product_presenter#display_what_size_panel_do_i_need?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#display_where_to_buy?Object

Alias for Main_product_presenter#display_where_to_buy?

Returns:

  • (Object)

    Main_product_presenter#display_where_to_buy?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#documentsObject



334
335
336
# File 'app/presenters/www/product_line_presenter.rb', line 334

def documents
  retrieve_documents.all_documents
end

#documents_grouped_by_categoryObject



339
340
341
# File 'app/presenters/www/product_line_presenter.rb', line 339

def documents_grouped_by_category
  retrieve_documents.publications_grouped
end

#facetObject



525
526
527
# File 'app/presenters/www/product_line_presenter.rb', line 525

def facet
  potential_facets&.first
end

#facet_sort_keysObject



529
530
531
# File 'app/presenters/www/product_line_presenter.rb', line 529

def facet_sort_keys
  facet&.sort_keys
end

#facet_tokensObject



518
519
520
521
522
# File 'app/presenters/www/product_line_presenter.rb', line 518

def facet_tokens
  potential_facets = facets
  tokens = potential_facets.last.grouping_spec_tokens.compact_blank if potential_facets.present?
  tokens
end

#facetsObject



438
439
440
441
# File 'app/presenters/www/product_line_presenter.rb', line 438

def facets
  main_category = product_category
  product_line.facets.includes(:product_categories).where(product_categories: { id: main_category.id })
end

#faqsObject



514
515
516
# File 'app/presenters/www/product_line_presenter.rb', line 514

def faqs
  Item::ArticleRetriever.new(article_types: %w[faq], sales: true, add_vote_data: true).process(product_line:).articles
end

#floor_plansObject



327
328
329
330
331
# File 'app/presenters/www/product_line_presenter.rb', line 327

def floor_plans
  return [] unless main_item.product_category.is_heating_element?

  Item::FloorPlanRetriever.new(max_rooms: 1).process(product_line).all_rooms
end

#has_sales?Boolean

Returns:

  • (Boolean)


280
281
282
# File 'app/presenters/www/product_line_presenter.rb', line 280

def has_sales?
  product_line.slug_ltree.to_s.in?(%w[towel_warmer.classic.metropolitan])
end

#highest_priceObject



251
252
253
254
255
256
# File 'app/presenters/www/product_line_presenter.rb', line 251

def highest_price
  item = highest_price_catalog_item
  return nil unless item

  item.money_price.to_f.round(2)
end

#highest_price_catalog_itemObject



245
246
247
248
249
# File 'app/presenters/www/product_line_presenter.rb', line 245

def highest_price_catalog_item
  return nil if main_products.blank?

  main_products.max_by(&:money_effective_price)
end

#highest_price_formattedObject



258
259
260
261
262
# File 'app/presenters/www/product_line_presenter.rb', line 258

def highest_price_formatted
  return nil unless (price = highest_price)

  h.better_number_to_currency(price, unit: currency_symbol)
end

#image_tag(options) ⇒ Object



454
455
456
457
458
# File 'app/presenters/www/product_line_presenter.rb', line 454

def image_tag(options)
  return unless (img = item.primary_image)

  Www::ImagePresenter.new(img).image_tag(options)
end

#image_url(options) ⇒ Object



460
461
462
463
464
# File 'app/presenters/www/product_line_presenter.rb', line 460

def image_url(options)
  return unless primary_image

  primary_image.image_url(options)
end

#imagesObject



56
57
58
59
60
61
62
63
64
# File 'app/presenters/www/product_line_presenter.rb', line 56

def images
  # Use website image profiles from the main item (primary representative item)
  profile_images = main_item&.image_profiles&.website_image_profiles&.image_order&.includes(:image)&.map(&:image) || []

  # Fallback to product line's primary image if no profiles exist
  profile_images = [product_line.primary_image_inherited].compact if profile_images.empty?

  profile_images.uniq.first(25).map { |img| Www::ImagePresenter.new(img, h) }
end

#installation_kitsObject

Alias for Main_product_presenter#installation_kits

Returns:

  • (Object)

    Main_product_presenter#installation_kits

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#installation_methodsObject

Alias for Main_product_presenter#installation_methods

Returns:

  • (Object)

    Main_product_presenter#installation_methods

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#insulating_underlaymentsObject

Alias for Main_product_presenter#insulating_underlayments

Returns:

  • (Object)

    Main_product_presenter#insulating_underlayments

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#insulationsObject

Alias for Main_product_presenter#insulations

Returns:

  • (Object)

    Main_product_presenter#insulations

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#is_refurbished_item?Boolean

Returns:

  • (Boolean)


288
289
290
# File 'app/presenters/www/product_line_presenter.rb', line 288

def is_refurbished_item?
  false
end

#item_primary_product_line_slug_ltreeObject



284
285
286
# File 'app/presenters/www/product_line_presenter.rb', line 284

def item_primary_product_line_slug_ltree
  ProductLine.find_by(id: id)&.slug_ltree
end

#item_skuObject



179
180
181
182
183
# File 'app/presenters/www/product_line_presenter.rb', line 179

def item_sku
  return unless main_item

  main_item.sku || main_item.item_sku
end

#lowest_effective_price_catalog_itemObject



185
186
187
188
189
# File 'app/presenters/www/product_line_presenter.rb', line 185

def lowest_effective_price_catalog_item
  return nil if main_products.blank?

  main_products.min_by(&:money_effective_price)
end

#lowest_priceObject



204
205
206
207
208
209
# File 'app/presenters/www/product_line_presenter.rb', line 204

def lowest_price
  item = lowest_effective_price_catalog_item
  return nil unless item

  item.money_price.to_f.round(2)
end

#lowest_price_formattedObject



211
212
213
214
215
# File 'app/presenters/www/product_line_presenter.rb', line 211

def lowest_price_formatted
  return nil unless (price = lowest_price)

  h.better_number_to_currency(price, unit: currency_symbol)
end

#lowest_sales_priceObject



217
218
219
220
221
222
# File 'app/presenters/www/product_line_presenter.rb', line 217

def lowest_sales_price
  item = lowest_effective_price_catalog_item
  return nil unless item

  item.money_effective_price.to_f.round(2)
end

#lowest_sales_price_formattedObject



224
225
226
227
228
# File 'app/presenters/www/product_line_presenter.rb', line 224

def lowest_sales_price_formatted
  return nil unless (price = lowest_sales_price)

  h.better_number_to_currency(price, unit: currency_symbol)
end

#lowest_sales_price_percentage_off_displayObject



230
231
232
233
234
235
# File 'app/presenters/www/product_line_presenter.rb', line 230

def lowest_sales_price_percentage_off_display
  item = lowest_effective_price_catalog_item
  return nil unless item

  h.number_with_precision(item.sale_price_percentage_off, strip_insignificant_zeros: true, precision: 0)
end

#main_itemObject



133
134
135
136
137
# File 'app/presenters/www/product_line_presenter.rb', line 133

def main_item
  mi = main_products.where(product_stock_status: 'InStock').first
  mi ||= main_product
  mi&.item
end

#main_productObject



119
120
121
# File 'app/presenters/www/product_line_presenter.rb', line 119

def main_product
  main_products.where(product_stock_status: 'InStock').first || main_products.first
end

#main_product_presenterObject



129
130
131
# File 'app/presenters/www/product_line_presenter.rb', line 129

def main_product_presenter
  Www::ProductCatalogPresenter.new(main_product, h)
end

#main_productsObject



105
106
107
108
109
110
111
112
113
114
115
116
# File 'app/presenters/www/product_line_presenter.rb', line 105

def main_products
  products = available_products_scope
  products = products.belongs_to_product_line_id(product_line.id)
  # Use loaded association if available to avoid N+1, otherwise fall back to pluck
  category_ids = if product_line.product_categories.loaded?
                   product_line.product_categories.map(&:id)
                 else
                   product_line.product_category_ids
                 end&.compact
  products = products.belongs_to_product_category_id(category_ids) if category_ids.present?
  products.reorder(:price)
end

#main_products_countObject



90
91
92
# File 'app/presenters/www/product_line_presenter.rb', line 90

def main_products_count
  main_products.size
end

#main_products_presentersObject



608
609
610
# File 'app/presenters/www/product_line_presenter.rb', line 608

def main_products_presenters
  main_products.map { |p| Www::ProductCatalogPresenter.new(p, h) }
end

#membranesObject

Alias for Main_product_presenter#membranes

Returns:

  • (Object)

    Main_product_presenter#membranes

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#offer_countObject



241
242
243
# File 'app/presenters/www/product_line_presenter.rb', line 241

def offer_count
  main_products.size
end

#og_imageObject



44
45
46
# File 'app/presenters/www/product_line_presenter.rb', line 44

def og_image
  product_line&.primary_image&.image_url || DEFAULT_SOCIAL_ICON
end

#page_descriptionObject



40
41
42
# File 'app/presenters/www/product_line_presenter.rb', line 40

def page_description
  product_line&.effective_seo_description || product_line&.short_description
end

#page_titleObject



36
37
38
# File 'app/presenters/www/product_line_presenter.rb', line 36

def page_title
  product_line&.effective_seo_title || product_line&.public_short_name
end

#primary_imageObject



67
68
69
# File 'app/presenters/www/product_line_presenter.rb', line 67

def primary_image
  images.first
end

#product_categoryObject

Alias for Main_product#product_category

Returns:

  • (Object)

    Main_product#product_category

See Also:



14
# File 'app/presenters/www/product_line_presenter.rb', line 14

delegate :currency, :currency_symbol, :product_category, to: :main_product, allow_nil: true

#product_line_nameObject



200
201
202
# File 'app/presenters/www/product_line_presenter.rb', line 200

def product_line_name
  name
end

#product_type_pathObject



52
53
54
# File 'app/presenters/www/product_line_presenter.rb', line 52

def product_type_path
  product_line&.product_type(separator: '/')
end

#products_by_categoryObject



306
307
308
# File 'app/presenters/www/product_line_presenter.rb', line 306

def products_by_category
  main_products.group_by(&:product_category)
end

#promo_discount_percentageObject



588
589
590
591
592
593
594
595
596
597
# File 'app/presenters/www/product_line_presenter.rb', line 588

def promo_discount_percentage
  # Reuse memoized main_products instead of re-querying
  option_products = main_products_presenters

  promo_product = option_products.select(&:sale_price_in_effect?)
                                 .max_by { |p| p.discount_percentage(precision: 0).to_i }
  return unless promo_product

  promo_product.discount_percentage(precision: 0)
end

#purchase_urlObject



99
100
101
102
103
# File 'app/presenters/www/product_line_presenter.rb', line 99

def purchase_url
  h.catalog_link(product_line)
rescue ArgumentError
  nil
end

#quote_builder_urlObject



163
164
165
166
167
168
169
170
171
172
# File 'app/presenters/www/product_line_presenter.rb', line 163

def quote_builder_url
  return unless show_quote_builder_link?

  slt = product_line.slug_ltree.to_s
  if slt.start_with?('floor_heating')
    h.cms_link('floor-heating/quote-builder')
  elsif slt.start_with?('snow_melting')
    h.cms_link('snow-melting/quote-builder')
  end
end

#rating(page: 1, limit: 5) ⇒ Object



493
494
495
496
# File 'app/presenters/www/product_line_presenter.rb', line 493

def rating(page: 1, limit: 5)
  product_review_info = ::ReviewsIo.reviews_for_product_line(product_line.slug_ltree.to_s, page:, per_page: limit)
  Rating.new(apply_reviews_snapshot(product_review_info), nil)
end

#rating_snapshotObject



499
500
501
# File 'app/presenters/www/product_line_presenter.rb', line 499

def rating_snapshot
  load_rating_snapshot
end

#render_main_category_itemsObject



444
445
446
# File 'app/presenters/www/product_line_presenter.rb', line 444

def render_main_category_items
  h.render partial: 'main_category_items', locals: { products: main_products.map { |p| Www::ProductCatalogPresenter.new(p, h) } }
end

#render_section(path) ⇒ Object



448
449
450
451
452
# File 'app/presenters/www/product_line_presenter.rb', line 448

def render_section(path, **)
  h.render(partial: path.to_s, **)
rescue ActionView::MissingTemplate
  h.render(partial: 'default_section', **)
end

#restricted_to_trade?Object

Alias for Main_product_presenter#restricted_to_trade?

Returns:

  • (Object)

    Main_product_presenter#restricted_to_trade?

See Also:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/presenters/www/product_line_presenter.rb', line 15

delegate :display_how_much_cable_will_i_need?,
:display_what_size_panel_do_i_need?,
:display_roof_gutter_deicing_quote?,
:restricted_to_trade?,
:display_dealers?,
:display_where_to_buy?,
:display_pro_contact_us_for_quote?,
:display_best_product_tahoe?,
:installation_methods,
:membranes,
:installation_kits,
:insulations,
:controls,
:insulating_underlayments,
:control_electrical_rough_in_kits,
to: :main_product_presenter, allow_nil: true

#retrieve_documentsObject



320
321
322
323
324
# File 'app/presenters/www/product_line_presenter.rb', line 320

def retrieve_documents
  ProductLine::PublicationRetriever.new.process(product_line.slug_ltree.to_s,
                                                categories: %i[sales],
                                                product_category_ids: product_line.product_category_ids)
end

#review_summaryObject



584
585
586
# File 'app/presenters/www/product_line_presenter.rb', line 584

def review_summary
  ::ReviewsIo.stats_for_product_line(product_line.slug_ltree.to_s)
end

#reviews_io_average_ratingObject



504
505
506
507
# File 'app/presenters/www/product_line_presenter.rb', line 504

def reviews_io_average_rating
  snapshot = rating_snapshot
  snapshot[:source] == :none ? nil : snapshot[:average]
end

#reviews_io_review_countObject



509
510
511
512
# File 'app/presenters/www/product_line_presenter.rb', line 509

def reviews_io_review_count
  snapshot = rating_snapshot
  snapshot[:source] == :none ? nil : snapshot[:count]
end


580
581
582
# File 'app/presenters/www/product_line_presenter.rb', line 580

def reviews_link
  h.catalog_link(product_line, section: :reviews)
end

#sale_price_in_effect?Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
# File 'app/presenters/www/product_line_presenter.rb', line 191

def sale_price_in_effect?
  item = lowest_effective_price_catalog_item
  return false unless item

  item.sale_price_in_effect?
rescue StandardError
  false
end

#schema_dot_org_structureObject



533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'app/presenters/www/product_line_presenter.rb', line 533

def schema_dot_org_structure
  p = SchemaDotOrg::Product.new
  p.name = title
  p.category = product_type_path
  p.itemCondition = 'https://schema.org/NewCondition'
  p.image = image_url(size: '600x600>')
  p.description = Oembed::ProductProvider.sanitize_schema_text(page_description)
  p.brand = Brand.warmlyyours.schema_dot_org_structure

  o = SchemaDotOrg::AggregateOffer.new
  o.priceCurrency = currency
  o.priceValidUntil = (Date.current + 30.days).iso8601
  o.lowPrice = lowest_price
  o.highPrice = highest_price
  if (offers = variants&.map(&:schema_offer_struc)).present?
    o.offerCount = offers.size
    o.offers = offers
  end
  p.offers = o
  # if rating&.avg_stars.to_i > 3  #not sure i want to filter? has to match merchant/review feed
  if rating.review_count > 0
    p.aggregateRating = SchemaDotOrg::AggregateRating.new(
      ratingValue: rating.avg_stars,
      reviewCount: rating.review_count
    )
  end
  p.review = rating.product_reviews.map(&:schema_dot_org_structure).presence
  p
end

#sectionsObject



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'app/presenters/www/product_line_presenter.rb', line 343

def sections
  # Don't change this order unless you get clearance from Julia
  sections = %i[features description installation_plans related_products documents faq reviews]
  related_products_section = %i[installation_methods controls control_electrical_rough_in_kits upgrades accessories installation_kits insulating_underlayments]
  main_category = main_product&.product_category&.name
  main_category_sym = main_category.delete(' ').underscore.to_sym
  sections.delete(main_category_sym)
  s = {}
  # We keep track here of products that were already rendered so we don't re-render them in multiple sections.
  rendered_catalog_item_ids = []
  sections.each do |section|
    case section
    when :features
      features = product_line.features.presence || main_item.features.presence
      if features.present?
        s[section] = {
          section_name: 'Features',
          features:
        }
      end
    when :description
      s[section] = {
        section_name: section_name(section),
        description_html:
      }
    when :documents
      s[section] = {
        section_name: section_name(section),
        documents_grouped_by_category:
      }
    when :faq
      if faqs.any?
        s[section] = {
          nav_name: section_name(section),
          section_name: 'Frequently Asked Questions',
          faqs:,
          top_faqs: faqs.first(5),
          product_name: product_line.display_name
        }
      end
    when :reviews
      reviews = rating.product_reviews
      if reviews.present?
        s[section] = {
          section_name: section_name(section),
          top_reviews: top_rating.product_reviews,
          reviews:,
          average_rating: rating.avg_stars,
          review_count: rating.review_count,
          product_name: product_line.display_name
        }
      end
    when :installation_plans
      if floor_plans.present?
        s[section] = {
          section_name: section_name(section),
          floor_plans:
        }
      end
    when :related_products
      related_products_section.each do |related_product_section|
        section_products = send(related_product_section)
        next if section_products.blank?

        section_products = section_products.reject { |p| rendered_catalog_item_ids.include?(p.id) }
        next if section_products.blank?

        s[section] ||= {}
        s[section][related_product_section] = {
          section_name: section_name(related_product_section),
          section_intro: section_intro(related_product_section),
          products: section_products.map { |p| Www::ProductCatalogPresenter.new(p, h) }.sort_by(&:effective_price)
        }
        rendered_catalog_item_ids += section_products.map(&:id)
      end
    else
      section_products = send(section)
      next if section_products.blank?

      section_products = section_products.reject { |p| rendered_catalog_item_ids.include?(p.id) }
      if section_products.present?
        # Wrap our products in a presenter
        s[section] = {
          section_name: section_name(section),
          section_intro: section_intro(section),
          products: section_products.map { |p| Www::ProductCatalogPresenter.new(p, h) }.sort_by(&:effective_price)
        }
        rendered_catalog_item_ids += section_products.map(&:id)
      end
    end
  end
  s
end

#show_product_sample_request?Boolean

Returns:

  • (Boolean)


174
175
176
177
# File 'app/presenters/www/product_line_presenter.rb', line 174

def show_product_sample_request?
  slt = product_line.slug_ltree.to_s
  slt.start_with?('floor_heating.tempzone.flex_roll', 'floor_heating.tempzone.cable', 'floor_heating.environ.flex_roll', LtreePaths::PL_FLOOR_HEATING_UNDERLAYMENT_THERMALSHEET, LtreePaths::PL_FLOOR_HEATING_UNDERLAYMENT_CORK, LtreePaths::PL_FLOOR_HEATING_UNDERLAYMENT_PRODESO)
end

#show_quote_builder_link?Boolean

Returns:

  • (Boolean)


140
141
142
143
144
145
146
147
# File 'app/presenters/www/product_line_presenter.rb', line 140

def show_quote_builder_link?
  slt = product_line.slug_ltree.to_s
  return false if slt.start_with?('floor_heating.underlayment')
  # TODO: once quote builder is ready, we can remove this
  return false if slt.include?('ruler_cable')

  slt.start_with?('floor_heating', 'snow_melting')
end

#show_support_portal_link?Boolean

Returns:

  • (Boolean)


149
150
151
# File 'app/presenters/www/product_line_presenter.rb', line 149

def show_support_portal_link?
  support_portal_url.present?
end

#single_sku?Boolean

Returns:

  • (Boolean)


269
270
271
# File 'app/presenters/www/product_line_presenter.rb', line 269

def single_sku?
  variants_count == 1
end

#support_portal_urlObject



153
154
155
156
157
158
159
160
# File 'app/presenters/www/product_line_presenter.rb', line 153

def support_portal_url
  pl = main_item&.primary_product_line
  return unless pl

  h.catalog_link(pl, section: :support)
rescue ArgumentError
  nil
end

#thumbnail_image_slugObject

--- Additions to support card rendering without ProductCard ---



571
572
573
574
575
576
577
578
# File 'app/presenters/www/product_line_presenter.rb', line 571

def thumbnail_image_slug
  # Prefer the product line's primary image slug if available

  primary = product_line.respond_to?(:primary_image_inherited) ? product_line.primary_image_inherited : nil
  primary.try(:slug) || primary.try(:image)&.try(:slug)
rescue StandardError
  nil
end

#titleObject



48
49
50
# File 'app/presenters/www/product_line_presenter.rb', line 48

def title
  product_line.display_name
end

#to_json_ldObject



563
564
565
566
567
568
# File 'app/presenters/www/product_line_presenter.rb', line 563

def to_json_ld
  schema_dot_org_structure.to_s
rescue StandardError => e
  ErrorReporting.error e
  logger.error e
end

#top_rating(page: 1, limit: 10) ⇒ Object



314
315
316
317
# File 'app/presenters/www/product_line_presenter.rb', line 314

def top_rating(page: 1, limit: 10)
  product_review_info = ::ReviewsIo.reviews_for_product_line(product_line.slug_ltree.to_s, min_length: 10, page:, per_page: limit)
  Rating.new(product_review_info, nil)
end

#upgradesObject



292
293
294
295
296
297
# File 'app/presenters/www/product_line_presenter.rb', line 292

def upgrades
  item_ids = main_products.map(&:item).flat_map do |item|
    Item::Materials::CompatibleUpgrades.new.process(item:).items.map(&:id)
  end
  available_products_scope_sorted.where(item_id: item_ids)
end

#variants(include_self: false, facet_filters: {}) ⇒ Object



273
274
275
276
277
# File 'app/presenters/www/product_line_presenter.rb', line 273

def variants(include_self: false, facet_filters: {})
  return if main_products.blank?

  main_products.reorder(:price, :item_sku).map { |pc| Www::ProductCatalogPresenter.new(pc) }
end

#variants_countObject



264
265
266
# File 'app/presenters/www/product_line_presenter.rb', line 264

def variants_count
  variants(include_self: true)&.size || 0
end

#videosObject



477
478
479
# File 'app/presenters/www/product_line_presenter.rb', line 477

def videos
  ProductLine::VideoRetriever.new(tags: 'for-product-page').process(product_line).all_videos
end