Class: ApplicationController
- Inherits:
-
ActionController::Base
- Object
- ActionController::Base
- ApplicationController
- Includes:
- ActionView::Helpers::NumberHelper, ActionView::Helpers::TagHelper, Controllers::AcceleratedFileSending, Controllers::AnalyticsEvents, Controllers::AppsignalTagging, Controllers::Authenticable, Controllers::CloudflareCaching, Controllers::DeviceDetection, Controllers::ErrorRendering, Controllers::Localizable, Controllers::Paginateable, Controllers::PaperTrailContext, Controllers::ReturnPathHandling, Controllers::SetCurrentRequestDetails, Controllers::SubdomainDetection, Controllers::TrackingDetection, Controllers::TurboSafeRedirect, Controllers::TurboStreamFlashable, Controllers::TurnstileVerification, Controllers::Webpackable, Memery, UrlsHelper
- Defined in:
- app/controllers/application_controller.rb
Overview
Controller: application.
Direct Known Subclasses
Auth::AuthenticationsController, BasePortalController, CreditApplicationsController, Crm::MarketplaceShippingOptionsController, CrmController, DownloadsController, EmailEventsController, GlobalsController, LocatorRecordsController, NavbarAccountController, NavbarContactController, OnlineOfflineUsersController, PostCommentsController, PostsController, PresetJobsControllerBase, Privacy::DeletionStatusController, RedactorDragonfly::FilesController, RedactorDragonfly::ImagesController, VideoMediaController, Www::ErrorPreviewController, Www::LiveEventsController, Www::LlmsController, Www::PaymentsController, Www::PublicationsController, Www::QuoteBuilderController, Www::RoomPlansController, Www::SearchController, Www::SitemapController, Www::SmsRedirectsController, Www::SupportArticlesController, Www::SupportPortalsController, Www::UploadsController
Constant Summary
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
Instance Method Summary collapse
-
#account_impersonated? ⇒ Boolean
Pretender does not ship a boolean helper, but it does set
session[:impersonated_account_id]whenever an impersonation is active (see vendor/bundle/.../pretender.rb). -
#add_to_flash(message, type = :error, now: false) ⇒ void
protected
Append a message to flash[type], deduping against what's already there.
-
#after_sign_in_path_for(resource) ⇒ String
Default post-authentication landing for storefront flows.
-
#bypass_forgery_protection? ⇒ Boolean
protected
True if CSRF protection should be skipped for this request.
-
#chat_enabled? ⇒ Boolean
protected
True if the live-chat widget should render.
-
#cloudflare_cleared? ⇒ Boolean
protected
True if Cloudflare has already cleared this browser (has a
cf_clearancecookie). -
#default_catalog ⇒ Catalog
protected
The current user's catalog, or the locale's default.
-
#default_url_options(options = {}) ⇒ Hash
protected
Default URL options applied app-wide.
-
#enable_turbo_frames ⇒ Object
Enable Turbo Frames and Streams for specific sections.
-
#find_publication(sku, override_locale_store = nil) ⇒ Publication?
protected
The item's publication in the resolved store.
-
#fix_invalid_accept_header ⇒ Object
protected
Clean possibly misconfigured proxies or old browsers.
-
#init_js_utils ⇒ void
protected
Initializes the props hash React components read via
server-to-client-data. -
#is_globals_call? ⇒ Boolean
protected
True if this request is for the
/globalsendpoint. -
#layout_by_resource ⇒ String, false
protected
The layout name to render, or false to skip the layout.
-
#locale_store ⇒ Store
protected
The store for the current I18n locale.
-
#redirect_to(options = {}, response_options = {}) ⇒ void
Hotwire: default redirects after mutating verbs to 303 See Other unless an explicit
status:is passed (matches Turbo / Rails 7 guidance for POST/PUT/PATCH/DELETE flows). -
#require_employee_for_crm ⇒ Object
protected
Gate: every request on the CRM subdomain must be from an authenticated employee.
- #set_base_host ⇒ void protected
-
#set_real_ip ⇒ void
protected
Fake our location in development when working locally, this is what request geocoder uses.
-
#set_report_errors_for ⇒ void
protected
Stub, override in your own controllers, put the models that are updated in the array, note use instance variable @ not symbols.
-
#should_render_layout? ⇒ Boolean
protected
Determines if a layout should be rendered based on the request type.
-
#skip_layout_for_turbo_frame? ⇒ Boolean
protected
In-pane Turbo Frame fetches skip the layout; breakouts (and plain navigation) keep it.
-
#stamp_impersonation_context ⇒ void
protected
Records the true (pre-impersonation) account id so downstream logging/ AR-versioning can attribute changes to the real actor while masquerading.
-
#tab_frame_breakout_request? ⇒ Boolean
protected
A Turbo Frame fetch from inside a
tab-content-*pane whose response will render under a DIFFERENT tab frame id — i.e. -
#warmlyyours_canada_ip?(ip = nil) ⇒ Boolean
protected
True if the IP belongs to WarmlyYours' Canadian office.
-
#warmlyyours_ip?(ip = nil) ⇒ Boolean
protected
True if the IP belongs to WarmlyYours.
-
#y(value) ⇒ String
protected
Renders a loosely-typed value as "Yes"/"No" for view display.
Methods included from Controllers::ReturnPathHandling
#check_for_return_path, #redirect_to_return_path_or_default
Methods included from Controllers::AnalyticsEvents
#consume_queued_analytics_events, #registration_lead_type, #track_event
Methods included from Controllers::DeviceDetection
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, #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 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?
Instance Method Details
#account_impersonated? ⇒ Boolean
Pretender does not ship a boolean helper, but it does set
session[:impersonated_account_id] whenever an impersonation is active
(see vendor/bundle/.../pretender.rb). We define one here as the canonical
check used everywhere in the app (views, MasqueradeGuarded, AnalyticsEvents,
AppsignalTagging, etc.).
18 19 20 |
# File 'app/controllers/application_controller.rb', line 18 def account_impersonated? request.session[:impersonated_account_id].present? end |
#add_to_flash(message, type = :error, now: false) ⇒ void (protected)
This method returns an undefined value.
Append a message to flash[type], deduping against what's already there.
Pass now: true for one-shot messages set on a render (rather than a
redirect) — they'll show on this request only, the same way flash.now
works. Without it, the message persists into the next request, which
leaks onto the back/forward target after a Turbo Drive cache miss.
180 181 182 183 184 185 186 |
# File 'app/controllers/application_controller.rb', line 180 def add_to_flash(, type = :error, now: false) return if .blank? || (flash[type].present? && flash[type].to_s.downcase.include?(.to_s.downcase)) bucket = now ? flash.now : flash bucket[type] ||= +'' bucket[type] += end |
#after_sign_in_path_for(resource) ⇒ String
Default post-authentication landing for storefront flows. Every storefront
auth controller (sessions, registrations, passwords, invitations, omniauth)
already redirects to @devise_return_path || my_account_path on success;
this override applies the same rule for the implicit call sites Devise
makes directly — most importantly require_no_authentication, which lands
an already-signed-in user back here when they click a password-reset
email link. Without it, Devise's gem default root_path sent them to the
homepage with no signal. CRM falls through to super because the
employee subdomain has no my_account route.
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'app/controllers/application_controller.rb', line 93 def after_sign_in_path_for(resource) # A stale / legacy-format session can hand us the raw serialized Warden value # (`[[id], salt]`) instead of an Account. Calling `super` then explodes deep in # `Devise::Mapping.find_scope!` ("Could not find a valid mapping for [[id], …]"), # 500ing the first sign-in after a session-format change (it self-heals on the # next request once the fresh session is set). Fall back to a safe path instead. return @devise_return_path.presence || root_path unless resource.is_a?(Account) return super if is_crm_request? @devise_return_path.presence || my_account_path end |
#bypass_forgery_protection? ⇒ Boolean (protected)
Returns true if CSRF protection should be skipped for this request.
123 124 125 126 127 128 129 |
# File 'app/controllers/application_controller.rb', line 123 def bypass_forgery_protection? return true if json_request? # Because the user might be presented with a captcha, this can break the csrf check, so this is a workaround. return true if cloudflare_cleared? false end |
#chat_enabled? ⇒ Boolean (protected)
Returns true if the live-chat widget should render.
144 145 146 |
# File 'app/controllers/application_controller.rb', line 144 def chat_enabled? Rails.env.production? || ENV['ENABLE_LIVECHAT'].to_b end |
#cloudflare_cleared? ⇒ Boolean (protected)
Returns true if Cloudflare has already cleared this browser (has a cf_clearance cookie).
117 118 119 120 |
# File 'app/controllers/application_controller.rb', line 117 def cloudflare_cleared? # See https://developers.cloudflare.com/fundamentals/reference/policies-compliances/cloudflare-cookies/ request.['cf_clearance'].present? end |
#default_catalog ⇒ Catalog (protected)
Returns the current user's catalog, or the locale's default.
165 166 167 |
# File 'app/controllers/application_controller.rb', line 165 def default_catalog current_user.try(:catalog) || Catalog.locale_to_catalog(I18n.locale) end |
#default_url_options(options = {}) ⇒ Hash (protected)
Default URL options applied app-wide.
209 210 211 |
# File 'app/controllers/application_controller.rb', line 209 def ( = {}) UrlHelper.() end |
#enable_turbo_frames ⇒ Object
Enable Turbo Frames and Streams for specific sections
77 78 79 |
# File 'app/controllers/application_controller.rb', line 77 def enable_turbo_frames @turbo_frames_enabled = true end |
#find_publication(sku, override_locale_store = nil) ⇒ Publication? (protected)
Returns the item's publication in the resolved store.
159 160 161 162 |
# File 'app/controllers/application_controller.rb', line 159 def find_publication(sku, override_locale_store = nil) store = override_locale_store || locale_store Item.find_publication(sku, store) end |
#fix_invalid_accept_header ⇒ Object (protected)
Clean possibly misconfigured proxies or old browsers
297 298 299 300 301 |
# File 'app/controllers/application_controller.rb', line 297 def fix_invalid_accept_header return unless is_crm_request? || is_www_request? request.headers['Accept'] = '*/*' if request.headers['Accept'] == '*' end |
#init_js_utils ⇒ void (protected)
This method returns an undefined value.
Initializes the props hash React components read via server-to-client-data.
139 140 141 |
# File 'app/controllers/application_controller.rb', line 139 def init_js_utils @react_props = {} end |
#is_globals_call? ⇒ Boolean (protected)
Returns true if this request is for the /globals endpoint.
132 133 134 |
# File 'app/controllers/application_controller.rb', line 132 def is_globals_call? request&.fullpath.to_s.start_with?('/globals') end |
#layout_by_resource ⇒ String, false (protected)
Returns the layout name to render, or false to skip the layout.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'app/controllers/application_controller.rb', line 228 def layout_by_resource # Turbo Frame requests only consume the matching <turbo-frame> from the # response; the surrounding layout chrome (navbar, sidebar, head/asset # tags, modal containers) is rendered then immediately discarded by the # browser. Skip it server-side to save render time and bandwidth on every # frame fetch, app-wide. # # Exception: tab-frame breakouts. A navigation inside a tab pane that # lands on a page whose own tab frame id differs (an order link inside a # warehouse tab, an item link inside another item's kit tab) deliberately # misses the requesting frame so the client's turbo:frame-missing handler # promotes the response to a full Drive visit via `visit(response)`. That # promotion renders the fetched body as the whole page — without the # layout the user lands on a bare, chrome-less fragment. return false if skip_layout_for_turbo_frame? if devise_controller? if is_crm_request? @disable_sign_in = true 'crm/sessions' else 'www/cms_page' end elsif is_crm_request? 'crm/crm' else 'www/cms_page' end end |
#locale_store ⇒ Store (protected)
Returns the store for the current I18n locale.
214 215 216 |
# File 'app/controllers/application_controller.rb', line 214 def locale_store Store.locale_to_catalog(I18n.locale) end |
#redirect_to(options = {}, response_options = {}) ⇒ void
explicitly public because this override sits below the protected keyword
This method returns an undefined value.
Hotwire: default redirects after mutating verbs to 303 See Other unless an explicit
status: is passed (matches Turbo / Rails 7 guidance for POST/PUT/PATCH/DELETE flows).
above. Without this, the inherited public redirect_to becomes protected on the
whole app, and any gem that calls controller.redirect_to(...) externally (e.g. the
responders gem used by Devise's respond_with) raises NoMethodError.
364 365 366 367 368 369 370 |
# File 'app/controllers/application_controller.rb', line 364 public def redirect_to( = {}, = {}) = {} unless .is_a?(Hash) unless .key?(:status) || (.is_a?(Hash) && .key?(:status)) = { status: :see_other }.merge() if %w[POST PUT PATCH DELETE].include?(request.request_method) end super end |
#require_employee_for_crm ⇒ Object (protected)
Gate: every request on the CRM subdomain must be from an authenticated employee.
Controllers with legitimate open access (webhooks, OAuth, token downloads) must
explicitly declare: skip_before_action :require_employee_for_crm
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'app/controllers/application_controller.rb', line 261 def require_employee_for_crm return unless is_crm_request? return if devise_controller? # `current_account` is wrapped by Pretender, which can return a non-Account # value when the session is partially corrupt (e.g. `:impersonated_account_id` # set with no `true_account`). `&.is_employee?` only short-circuits on nil, # so an Array/Hash receiver crashes the request with a 500 instead of # redirecting to sign-in. Coerce anything that isn't an Account to nil. account = current_account if current_account.is_a?(Account) return if account&.is_employee? if account flash[:error] = "Access to the CRM is restricted to employees." redirect_to cms_link('/my_account') else flash[:info] = t('controllers.authenticable.sign_in_to_proceed') redirect_to new_account_session_path(devise_return_path: request.fullpath) end end |
#set_base_host ⇒ void (protected)
This method returns an undefined value.
284 285 286 |
# File 'app/controllers/application_controller.rb', line 284 def set_base_host @base_url = "#{request.scheme}://#{request.host_with_port}" end |
#set_real_ip ⇒ void (protected)
This method returns an undefined value.
Fake our location in development when working locally, this is what
request geocoder uses.
292 293 294 |
# File 'app/controllers/application_controller.rb', line 292 def set_real_ip request.env['HTTP_X_REAL_IP'] = NetworkConstants::REAL_FAKE_IP if Rails.env.development? end |
#set_report_errors_for ⇒ void (protected)
This method returns an undefined value.
Stub, override in your own controllers, put the models that are updated
in the array, note use instance variable @ not symbols.
222 223 224 225 |
# File 'app/controllers/application_controller.rb', line 222 def set_report_errors_for @report_errors_for = [] logger.debug "set_report_errors_for is set to #{@report_errors_for.join(', ')}" if @report_errors_for.present? end |
#should_render_layout? ⇒ Boolean (protected)
Determines if a layout should be rendered based on the request type.
Returns false (no layout) for:
- XHR requests (legacy AJAX approach)
- Turbo Frame fetches that target this controller's content (e.g.
Turbo-Frame: tab-content-<controller_name>or any non-tab frame
like pagination scopes)
Returns true (full layout) for:
- Plain navigation
- Tab-frame breakouts (see #tab_frame_breakout_request?): clicking an
order link from inside the warehouse tab, or an item link from inside
another item's kit tab. These triggerturbo:frame-missing
client-side, and the global handler'svisit(response)fallback needs
a complete HTML page to render. A layoutless response leaves the user
staring at just the body content (no chrome, no nav, no breadcrumb).
319 320 321 322 323 324 |
# File 'app/controllers/application_controller.rb', line 319 def should_render_layout? return false if request.xhr? return false if request.format.turbo_stream? !skip_layout_for_turbo_frame? end |
#skip_layout_for_turbo_frame? ⇒ Boolean (protected)
In-pane Turbo Frame fetches skip the layout; breakouts (and plain
navigation) keep it. Single source of truth shared by
layout_by_resource and should_render_layout? so the app-wide layout
decision and the explicit render layout: call sites can't drift.
330 331 332 |
# File 'app/controllers/application_controller.rb', line 330 def skip_layout_for_turbo_frame? turbo_frame_request? && !tab_frame_breakout_request? end |
#stamp_impersonation_context ⇒ void (protected)
This method returns an undefined value.
Records the true (pre-impersonation) account id so downstream logging/
AR-versioning can attribute changes to the real actor while masquerading.
112 113 114 |
# File 'app/controllers/application_controller.rb', line 112 def stamp_impersonation_context CurrentScope.true_account_id = account_impersonated? ? true_account&.id : nil end |
#tab_frame_breakout_request? ⇒ Boolean (protected)
A Turbo Frame fetch from inside a tab-content-* pane whose response
will render under a DIFFERENT tab frame id — i.e. an in-frame navigation
that must break out to a full-page visit instead of slotting into the
pane. Tab frame ids are instance-scoped (tab-content-items-13140, see
ApplicationHelper#tab_frame_id), so this covers both cross-controller
clicks (order link inside a warehouse tab) and same-controller record
navigation (item link inside another item's kit-contents tab). The
mismatched response fires turbo:frame-missing client-side; the global
handler upgrades it to a Drive visit of the already-fetched page — which
is why layout_by_resource keeps the full layout on these responses.
344 345 346 347 348 349 |
# File 'app/controllers/application_controller.rb', line 344 def tab_frame_breakout_request? header = request.headers['Turbo-Frame'].to_s return false unless header.start_with?('tab-content-') helpers.tab_frame_id != header end |
#warmlyyours_canada_ip?(ip = nil) ⇒ Boolean (protected)
Returns true if the IP belongs to WarmlyYours' Canadian office.
199 200 201 202 |
# File 'app/controllers/application_controller.rb', line 199 def warmlyyours_canada_ip?(ip = nil) ip ||= request.remote_ip IpDetector.warmlyyours_canada_ip?(ip) end |
#warmlyyours_ip?(ip = nil) ⇒ Boolean (protected)
Returns true if the IP belongs to WarmlyYours.
192 193 194 195 |
# File 'app/controllers/application_controller.rb', line 192 def warmlyyours_ip?(ip = nil) ip ||= request.remote_ip IpDetector.warmlyyours_ip?(ip) end |
#y(value) ⇒ String (protected)
Renders a loosely-typed value as "Yes"/"No" for view display.
152 153 154 |
# File 'app/controllers/application_controller.rb', line 152 def y(value) value.to_b ? 'Yes' : 'No' end |