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 =
12
ALLOWED_SORTS =
['', 'effective_price asc', 'effective_price desc', 'title asc'].freeze
DEFAULT_SORT =
'item_primary_product_line_slug_ltree asc, item_sku asc'
RETIRED_FINISH_SLUGS =

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.
Slugs that have been consolidated into composite pages and must 301.

%w[polished-gold brushed-gold].freeze

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, #append_token, #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::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, #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_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!, #authenticate_account_from_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, #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

#indexObject



40
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
78
79
80
# File 'app/controllers/www/towel_warmers_controller.rb', line 40

def index
  if (redirect_path = retired_slug_redirect)
    return redirect_to redirect_path, status: :moved_permanently
  end

  @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('/').reject(&: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



86
87
88
89
90
91
92
93
94
95
# File 'app/controllers/www/towel_warmers_controller.rb', line 86

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