Class: Www::ProductLinePresenter

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

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, #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, #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



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

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.



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

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

#available_products_scopeObject



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

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



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

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


474
475
476
477
478
# File 'app/presenters/www/product_line_presenter.rb', line 474

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


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

def breadcrumb_links_array
  h.catalog_breadcrumb_links(product_line)
end

#cache_keyObject



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

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

#can_be_purchased?Boolean

Returns:

  • (Boolean)


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

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


486
487
488
489
490
491
492
493
494
# File 'app/presenters/www/product_line_presenter.rb', line 486

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:



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

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:



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

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



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

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

#coupon_dataObject



605
606
607
608
609
610
611
612
# File 'app/presenters/www/product_line_presenter.rb', line 605

def coupon_data
  # Reuse memoized main_products instead of re-querying
  promo = main_products_presenters.detect(&: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:



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

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:



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

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

#description_htmlObject



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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



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

def documents
  retrieve_documents.all_documents
end

#documents_grouped_by_categoryObject



343
344
345
# File 'app/presenters/www/product_line_presenter.rb', line 343

def documents_grouped_by_category
  retrieve_documents.publications_grouped
end

#facetObject



531
532
533
# File 'app/presenters/www/product_line_presenter.rb', line 531

def facet
  potential_facets&.first
end

#facet_sort_keysObject



535
536
537
# File 'app/presenters/www/product_line_presenter.rb', line 535

def facet_sort_keys
  facet&.sort_keys
end

#facet_tokensObject



522
523
524
525
526
527
528
# File 'app/presenters/www/product_line_presenter.rb', line 522

def facet_tokens
  potential_facets = facets
  if potential_facets.present?
    tokens = potential_facets.last.grouping_spec_tokens.reject(&:blank?)
  end # if there are any facets for the product line the product is in...
  tokens
end

#facetsObject



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

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

#faqsObject



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

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

#floor_plansObject



331
332
333
334
335
# File 'app/presenters/www/product_line_presenter.rb', line 331

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)


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

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

#highest_priceObject



255
256
257
258
259
260
# File 'app/presenters/www/product_line_presenter.rb', line 255

def highest_price
  item = highest_price_catalog_item
  return nil unless item

  item.money_price.to_f.round(2)
end

#highest_price_catalog_itemObject



249
250
251
252
253
# File 'app/presenters/www/product_line_presenter.rb', line 249

def highest_price_catalog_item
  return nil if main_products.blank?

  main_products.max_by { |p| p.money_effective_price }
end

#highest_price_formattedObject



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

def highest_price_formatted
  return nil unless (price = highest_price)

  h.better_number_to_currency(price, unit: currency_symbol)
end

#image_tag(options) ⇒ Object



458
459
460
461
462
# File 'app/presenters/www/product_line_presenter.rb', line 458

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

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

#image_url(options) ⇒ Object



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

def image_url(options)
  return unless primary_image

  primary_image.image_url(options)
end

#imagesObject



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

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:



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

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:



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

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:



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

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:



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

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)


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

def is_refurbished_item?
  false
end

#item_primary_product_line_slug_ltreeObject



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

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

#item_skuObject



183
184
185
186
187
# File 'app/presenters/www/product_line_presenter.rb', line 183

def item_sku
  return unless main_item

  main_item.sku || main_item.item_sku
end

#lowest_effective_price_catalog_itemObject



189
190
191
192
193
# File 'app/presenters/www/product_line_presenter.rb', line 189

def lowest_effective_price_catalog_item
  return nil if main_products.blank?

  main_products.min_by { |p| p.money_effective_price }
end

#lowest_priceObject



208
209
210
211
212
213
# File 'app/presenters/www/product_line_presenter.rb', line 208

def lowest_price
  item = lowest_effective_price_catalog_item
  return nil unless item

  item.money_price.to_f.round(2)
end

#lowest_price_formattedObject



215
216
217
218
219
# File 'app/presenters/www/product_line_presenter.rb', line 215

def lowest_price_formatted
  return nil unless (price = lowest_price)

  h.better_number_to_currency(price, unit: currency_symbol)
end

#lowest_sales_priceObject



221
222
223
224
225
226
# File 'app/presenters/www/product_line_presenter.rb', line 221

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



228
229
230
231
232
# File 'app/presenters/www/product_line_presenter.rb', line 228

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



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

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



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

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

#main_productObject



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

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

#main_product_presenterObject



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

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

#main_productsObject



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

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



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

def main_products_count
  main_products.size
end

#main_products_presentersObject



614
615
616
# File 'app/presenters/www/product_line_presenter.rb', line 614

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:



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

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



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

def offer_count
  main_products.size
end

#og_imageObject



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

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

#page_descriptionObject



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

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

#page_titleObject



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

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

#primary_imageObject



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

def primary_image
  images.first
end

#product_categoryObject

Alias for Main_product#product_category

Returns:

  • (Object)

    Main_product#product_category

See Also:



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

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

#product_line_nameObject



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

def product_line_name
  name
end

#product_type_pathObject



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

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

#products_by_categoryObject



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

def products_by_category
  main_products.group_by(&:product_category)
end

#promo_discount_percentageObject



594
595
596
597
598
599
600
601
602
603
# File 'app/presenters/www/product_line_presenter.rb', line 594

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



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

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

#quote_builder_urlObject



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

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



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

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



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

def rating_snapshot
  load_rating_snapshot
end

#render_main_category_itemsObject



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

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



452
453
454
455
456
# File 'app/presenters/www/product_line_presenter.rb', line 452

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:



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

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



324
325
326
327
328
# File 'app/presenters/www/product_line_presenter.rb', line 324

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



590
591
592
# File 'app/presenters/www/product_line_presenter.rb', line 590

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

#reviews_io_average_ratingObject



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

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

#reviews_io_review_countObject



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

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


586
587
588
# File 'app/presenters/www/product_line_presenter.rb', line 586

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

#sale_price_in_effect?Boolean

Returns:

  • (Boolean)


195
196
197
198
199
200
201
202
# File 'app/presenters/www/product_line_presenter.rb', line 195

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



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'app/presenters/www/product_line_presenter.rb', line 539

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 = 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



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
436
437
438
439
# File 'app/presenters/www/product_line_presenter.rb', line 347

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 unless section_products.present?

        section_products = section_products.reject { |p| rendered_catalog_item_ids.include?(p.id) }
        next unless section_products.present?

        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 unless section_products.present?

      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)


173
174
175
176
177
178
179
180
181
# File 'app/presenters/www/product_line_presenter.rb', line 173

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

#show_quote_builder_link?Boolean

Returns:

  • (Boolean)


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

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') || slt.start_with?('snow_melting')
end

#show_support_portal_link?Boolean

Returns:

  • (Boolean)


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

def show_support_portal_link?
  support_portal_url.present?
end

#single_sku?Boolean

Returns:

  • (Boolean)


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

def single_sku?
  variants_count == 1
end

#support_portal_urlObject



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

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



577
578
579
580
581
582
583
584
# File 'app/presenters/www/product_line_presenter.rb', line 577

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



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

def title
  product_line.display_name
end

#to_json_ldObject



569
570
571
572
573
574
# File 'app/presenters/www/product_line_presenter.rb', line 569

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



318
319
320
321
# File 'app/presenters/www/product_line_presenter.rb', line 318

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



296
297
298
299
300
301
# File 'app/presenters/www/product_line_presenter.rb', line 296

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



277
278
279
280
281
# File 'app/presenters/www/product_line_presenter.rb', line 277

def variants(include_self: false, facet_filters: {})
  return unless main_products.present?

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

#variants_countObject



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

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

#videosObject



481
482
483
# File 'app/presenters/www/product_line_presenter.rb', line 481

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