Class: Www::TowelWarmersController

Inherits:
BasePortalController show all
Includes:
TowelWarmerFilterSlugs
Defined in:
app/controllers/www/towel_warmers_controller.rb

Overview

Www::TowelWarmersController

Handles the towel warmer product grid with server-side Ransack filtering.
Supports SEO-friendly URL paths for filters:
/towel-warmer -> All products
/towel-warmer/brushed-gold -> Filter by finish
/towel-warmer/brushed-gold/round-bars -> Filter by finish + style
/towel-warmer/brushed-gold/round-bars/hardwired -> + connection

Constant Summary collapse

PRODUCTS_PER_PAGE =

Products per page.

12
ALLOWED_SORTS =

Allowed sorts.

['', 'effective_price asc', 'effective_price desc', 'title asc'].freeze
DEFAULT_SORT =

Default sort.

'item_primary_product_line_slug_ltree asc, item_sku asc'

Constants included from TowelWarmerFilterSlugs

TowelWarmerFilterSlugs::COMPOSITE_FINISH_SLUGS, TowelWarmerFilterSlugs::CONNECTION_LABELS, TowelWarmerFilterSlugs::CONNECTION_SLUGS, TowelWarmerFilterSlugs::FINISH_SLUGS, TowelWarmerFilterSlugs::MOUNTING_LABELS, TowelWarmerFilterSlugs::MOUNTING_SLUGS, TowelWarmerFilterSlugs::RANSACK_TO_SLUG, TowelWarmerFilterSlugs::SIZE_LABELS, TowelWarmerFilterSlugs::SIZE_SLUGS, TowelWarmerFilterSlugs::SLUG_TO_RANSACK, TowelWarmerFilterSlugs::STYLE_LABELS, TowelWarmerFilterSlugs::STYLE_SLUGS

Constants included from Controllers::MasqueradeGuarded

Controllers::MasqueradeGuarded::DEFAULT_BLOCK_MESSAGE

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

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

Instance Method Summary collapse

Methods included from TowelWarmerFilterSlugs

available_filter_counts, build_filter_path, filter_options_with_slugs, parse_filter_segments, size_filter_counts

Methods inherited from BasePortalController

#current_ability, #portal_party, #set_catalog, #set_webpack

Methods included from Controllers::MasqueradeGuarded

block_while_masquerading, #masquerade_blocks?

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #stamp_impersonation_context, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

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

#indexObject

GET /towel-warmer
GET /towel-warmer/:filters (e.g., /towel-warmer/brushed-gold/round-bars)

Main index action - displays all towel warmer products with optional filtering.
Filter segments from the URL path are parsed into Ransack parameters.
Products are paginated with Pagy; subsequent pages load via Turbo Stream.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/controllers/www/towel_warmers_controller.rb', line 41

def index
  @q = base_scope.ransack(combined_filter_params)
  @q.sorts = default_sort if @q.sorts.empty?

  @products = @q.result
  all_skus = @products.pluck(:item_sku).uniq
  @skus = current_sort.blank? ? sort_with_top_sellers_first(all_skus) : all_skus

  # 301-redirect multi-filter pages that yield zero products to their parent.
  # e.g. /towel-warmer/brushed-gold/freestanding (no products) → /towel-warmer/brushed-gold
  if @skus.empty? && active_filters.size > 1
    segments = params[:filters].to_s.split('/').compact_blank
    parent_segments = segments[0..-2]
    redirect_path = parent_segments.any? ? "/#{I18n.locale}/towel-warmer/#{parent_segments.join('/')}" : www_towel_warmers_path
    return redirect_to redirect_path, status: :moved_permanently
  end

  @total_product_count = @skus.size
  @pagy, @page_skus = pagy(:offset, @skus, limit: PRODUCTS_PER_PAGE)
  @page_skus_with_badges = build_connection_badges(@page_skus)

  # Track if filters are active - used to skip expensive content queries in view
  @filters_active = active_filters.present?

  respond_to do |format|
    format.html do
      set_cloudflare_cache(time_in_secs: 4.hours.to_i, tags: %w[towel-warmer product])
      fresh_when(etag: [@products, combined_filter_params, current_sort, I18n.locale], last_modified: @products.maximum(:updated_at), public: true)
    end
    format.turbo_stream do
      return unless stale?(etag: [@products, combined_filter_params, current_sort, I18n.locale, @pagy.page],
                           last_modified: @products.maximum(:updated_at), public: false)

      render partial: 'infinite_scroll'
    end
  end
end

#sort_with_top_sellers_first(skus) ⇒ Object

Sort SKUs to put top sellers first while preserving the rest of the order
Uses a single SQL query to check for top-seller tags



83
84
85
86
87
88
89
90
91
92
# File 'app/controllers/www/towel_warmers_controller.rb', line 83

def sort_with_top_sellers_first(skus)
  return skus if skus.empty?

  # Single query to get all SKUs that have the top-seller tag
  top_seller_skus = Item.tags_include('top-seller').where(sku: skus).pluck(:sku).to_set

  # Partition into top sellers and others, preserving original order within each group
  top_sellers, others = skus.partition { |sku| top_seller_skus.include?(sku) }
  top_sellers + others
end