Module: FloorHeatingCompatibility
- Defined in:
- app/lib/floor_heating_compatibility.rb
Overview
Floor Heating Compatibility - Hybrid Database/Presentation Layer
This module provides floor type / heating system compatibility data by:
- Querying HeatingElementProductLineOption for actual compatibility (source of truth)
- Layering presentation config on top (icons, labels, footnotes, column headers)
Used by Www::FloorCompatibilityTableComponent to render comparison tables
on /floor-heating, /floor-heating/heating-cable, and /floor-heating/heated-floor-mat.
DATABASE SYNC:
Compatibility is derived from HeatingElementProductLineOption records where:
- environment = 'Indoor'
- is_public = true
If a record exists for (floor_type_id, product_line_id), they're compatible.
PRESENTATION CONFIG:
Display-specific data (icons, column headers, footnotes) is maintained here
since these are presentation-layer concerns not stored in the database.
Constant Summary collapse
- HEATING_SYSTEMS =
Heating system presentation config
:product_line_slug maps to product_lines.slug_ltree in database
:category is :cable or :mat (for filtering on subpages)
:column_header is the HTML-safe column header with optional subtitle
:variant distinguishes installation methods for the same product line
Order: Mats first (TempZone, Environ, Slab Heat Mat), then Cables (TempZone Grip, Prodeso, Slab Heat Cable) [ { key: :tempzone_mat, name: 'TempZone Mat', product_line_slug: LtreePaths::PL_FLOOR_HEATING_TEMPZONE_FLEX_ROLL, category: :mat, wattage: '15W/sq ft', column_header: 'TempZone™ Mats<br><small class="fw-normal text-muted">(Flex Roll & Easy Mats) 15W/sq ft</small>' }, { key: :environ_mat, name: 'Environ Mat', product_line_slug: LtreePaths::PL_FLOOR_HEATING_ENVIRON_FLEX_ROLL, category: :mat, wattage: '12W/sq ft', column_header: 'Environ™ Mats<br><small class="fw-normal text-muted">12W/sq ft</small>' }, { key: :tempzone_cable_grip, name: 'TempZone Cable with Grip Strips', product_line_slug: LtreePaths::PL_FLOOR_HEATING_TEMPZONE_CABLE, category: :cable, variant: :grip_strips, wattage: '9-15W/sq ft', column_header: 'TempZone™ Cable<br><small class="fw-normal text-muted">with Grip Strips</small>' }, { key: :tempzone_cable_prodeso, name: 'TempZone Cable + Prodeso', product_line_slug: LtreePaths::PL_FLOOR_HEATING_UNDERLAYMENT_PRODESO, category: :cable, variant: :prodeso, wattage: '9-15W/sq ft', column_header: 'TempZone™ Cable<br><small class="fw-normal text-muted">+ Prodeso Membrane</small>' }, { key: :slab_heat_mat, name: 'Slab Heat Mat', product_line_slug: LtreePaths::PL_FLOOR_HEATING_SLAB_HEAT_MAT, category: :mat, wattage: '20W/sq ft', column_header: 'Slab Heat Mat<br><small class="fw-normal text-muted">20W/sq ft</small>' }, { key: :slab_heat_cable, name: 'Slab Heat Cable', product_line_slug: LtreePaths::PL_FLOOR_HEATING_SLAB_HEAT_CABLE, category: :cable, wattage: '15-20W/sq ft', column_header: 'Slab Heat Cable<br><small class="fw-normal text-muted">15-20W/sq ft</small>' } ].freeze
- FLOOR_TYPES =
Floor type presentation config
Maps database floor types to display groupings with icons and URLs
:db_seo_keys lists all floor_types.seo_key values that belong to this group [ { key: :tile, name: 'Tile, Marble, Stone', icon: 'grid-2', url: '/floor-heating/tile-marble-or-stone', db_seo_keys: %w[tile-marble-or-stone stone] }, { key: :hardwood, name: 'Hardwood', icon: 'tree', url: '/floor-heating/nailed-hardwood', db_seo_keys: %w[wood-nailed wood-glued] }, { key: :engineered, name: 'Engineered Wood', icon: 'layer-group', url: '/floor-heating/engineered-wood', db_seo_keys: %w[engineered-nailed engineered-glued engineered-floating] }, { key: :bamboo, name: 'Bamboo', icon: 'leaf', url: '/floor-heating/bamboo', db_seo_keys: %w[bamboo-nailed bamboo-glued bamboo-floating] }, { key: :laminate, name: 'Laminate', icon: 'th', url: '/floor-heating/laminate', db_seo_keys: %w[laminate-click-together-floating laminate-glued-together-floating laminate-nailed] }, { key: :lvt_vinyl, name: 'LVT / Vinyl', icon: 'table-columns', url: '/floor-heating/luxury-vinyl-tiles', db_seo_keys: %w[resilients-vinyl-and-luxury-vinyl-tile-lvt lvt-floating] }, { key: :carpet, name: 'Carpet', icon: 'rug', url: '/floor-heating/carpet', db_seo_keys: %w[carpet carpet-glued] }, { key: :cork, name: 'Cork', icon: 'circle', url: nil, # No dedicated cork page db_seo_keys: %w[cork] }, { key: :concrete, name: 'Concrete Slab', icon: 'cubes-stacked', url: '/floor-heating/concrete', db_seo_keys: %w[unfinished-stamped-or-colored-concrete] } ].freeze
- INSTALLATION_LABELS =
Installation method presentation labels
{ nailed: 'Nailed', glued: 'Glued', floating: 'Floating', thinset: 'Thinset', stretch_in: 'Stretch-In', in_slab: 'In-slab' }.freeze
- SEO_KEY_TO_INSTALLATION =
Maps floor_type seo_key to installation method symbol
This extracts the installation method from the database floor type name { 'tile-marble-or-stone' => :thinset, 'stone' => :thinset, 'wood-nailed' => :nailed, 'wood-glued' => :glued, 'engineered-nailed' => :nailed, 'engineered-glued' => :glued, 'engineered-floating' => :floating, 'bamboo-nailed' => :nailed, 'bamboo-glued' => :glued, 'bamboo-floating' => :floating, 'laminate-click-together-floating' => :floating, 'laminate-glued-together-floating' => :floating, 'laminate-nailed' => :nailed, 'resilients-vinyl-and-luxury-vinyl-tile-lvt' => :glued, 'lvt-floating' => :floating, 'carpet' => :stretch_in, 'carpet-glued' => :glued, 'cork' => :floating, 'unfinished-stamped-or-colored-concrete' => :in_slab }.freeze
- FOOTNOTES_ORDER =
Footnote definitions for special compatibility notes
These provide additional context beyond simple yes/no compatibility
Note: Markers are assigned dynamically by the component based on which footnotes are used
Order here determines the display order when multiple footnotes are shown %i[slab_any_floor prodeso_tile_only floating_environ environ_flooring_check floating_over_slc mat_sleepers mat_with_slc carpet_us_only].freeze
- FOOTNOTES =
{ slab_any_floor: { text: 'Slab Heat systems are embedded in the concrete slab; any finished flooring can be installed over the cured slab.' }, prodeso_tile_only: { text: 'Prodeso membrane is designed for tile/stone with thinset. Wood/vinyl/carpet floors use Grip Strips instead.' }, floating_environ: { text: 'Floating floors and stretch-in carpet require Environ™ mats, or use Slab Heat Cable for in-slab heating.' }, environ_flooring_check: { text: 'Consult your flooring manufacturer to ensure the flooring can be installed directly over a heating system. If embedding is required, you must use a TempZone or Slab Heat system.' }, floating_over_slc: { text: 'Floating floor can be installed over an embedded TempZone system in 3/8–½" self-leveling cement (SLC), fully cured.' }, mat_sleepers: { text: 'Nailed floors: Install wood sleepers across subfloor, lay mat between sleepers, cover with self-leveling cement, then nail flooring into sleepers only.' }, mat_with_slc: { text: 'Glued floors: Embed mat in 3/8–½" self-leveling cement, allow to cure, then install flooring with adhesive.' }, carpet_us_only: { text: 'Carpet floor heating is available in the U.S. only.' } }.freeze
- COMPATIBILITY_NOTES =
Special compatibility overrides and notes
Format: { [floor_key, installation, system_key] => { status:, footnote:, note: } }
Use this for cases where database compatibility needs presentation enhancementKey distinction:
- TempZone Cable with Grip Strips: Works with any floor where cable goes in thinset/SLC
- TempZone Cable + Prodeso: ONLY for tile/stone (Prodeso membrane is tile-specific)
{ # === TempZone Cable with Grip Strips === # Works with tile and adhered floors (cable in thinset/SLC) %i[tile thinset tempzone_cable_grip] => { status: :yes }, %i[hardwood nailed tempzone_cable_grip] => { status: :yes }, %i[hardwood glued tempzone_cable_grip] => { status: :yes }, %i[engineered nailed tempzone_cable_grip] => { status: :yes }, %i[engineered glued tempzone_cable_grip] => { status: :yes }, %i[bamboo nailed tempzone_cable_grip] => { status: :yes }, %i[bamboo glued tempzone_cable_grip] => { status: :yes }, %i[laminate nailed tempzone_cable_grip] => { status: :yes }, %i[lvt_vinyl glued tempzone_cable_grip] => { status: :yes }, %i[lvt_vinyl floating tempzone_cable_grip] => { status: :yes, footnote: :floating_over_slc }, %i[carpet glued tempzone_cable_grip] => { status: :yes, us_only: true }, # Compatible with floating floors when embedded in SLC %i[engineered floating tempzone_cable_grip] => { status: :yes, footnote: :floating_over_slc }, %i[bamboo floating tempzone_cable_grip] => { status: :yes, footnote: :floating_over_slc }, %i[laminate floating tempzone_cable_grip] => { status: :yes, footnote: :floating_over_slc }, %i[carpet stretch_in tempzone_cable_grip] => { status: :no, footnote: :floating_environ, us_only: true }, %i[cork floating tempzone_cable_grip] => { status: :yes, footnote: :floating_over_slc }, %i[concrete in_slab tempzone_cable_grip] => { status: :no }, # === TempZone Cable + Prodeso Membrane === # ONLY works with tile/stone - Prodeso is a tile uncoupling membrane %i[tile thinset tempzone_cable_prodeso] => { status: :yes, note: 'Recommended' }, %i[hardwood nailed tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[hardwood glued tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[engineered nailed tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[engineered glued tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[engineered floating tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[bamboo nailed tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[bamboo glued tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[bamboo floating tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[laminate floating tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[laminate nailed tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[lvt_vinyl glued tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[lvt_vinyl floating tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[carpet glued tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only, us_only: true }, %i[carpet stretch_in tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only, us_only: true }, %i[cork floating tempzone_cable_prodeso] => { status: :no, footnote: :prodeso_tile_only }, %i[concrete in_slab tempzone_cable_prodeso] => { status: :no }, # === Slab Heat Cable === # Works with everything (goes under the slab) - show as warning since it's indirect %i[tile thinset slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[hardwood nailed slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[hardwood glued slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[engineered nailed slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[engineered glued slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[engineered floating slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo nailed slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo glued slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo floating slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[laminate floating slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[laminate nailed slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[lvt_vinyl glued slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[lvt_vinyl floating slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[carpet glued slab_heat_cable] => { status: :warning, footnote: :slab_any_floor, us_only: true }, %i[carpet stretch_in slab_heat_cable] => { status: :warning, footnote: :slab_any_floor, us_only: true }, %i[cork floating slab_heat_cable] => { status: :warning, footnote: :slab_any_floor }, %i[concrete in_slab slab_heat_cable] => { status: :yes, note: 'Primary' }, # === TempZone Mats === # Works with adhered floors (tile direct, others with SLC) %i[tile thinset tempzone_mat] => { status: :yes, note: 'Direct' }, %i[hardwood nailed tempzone_mat] => { status: :yes, footnote: :mat_sleepers, note: 'with sleepers' }, %i[hardwood glued tempzone_mat] => { status: :yes, footnote: :mat_with_slc, note: 'with SLC' }, %i[engineered nailed tempzone_mat] => { status: :yes, footnote: :mat_sleepers, note: 'with sleepers' }, %i[engineered glued tempzone_mat] => { status: :yes, footnote: :mat_with_slc, note: 'with SLC' }, %i[engineered floating tempzone_mat] => { status: :yes, footnote: :floating_over_slc }, %i[bamboo nailed tempzone_mat] => { status: :yes, footnote: :mat_sleepers, note: 'with sleepers' }, %i[bamboo glued tempzone_mat] => { status: :yes, footnote: :mat_with_slc, note: 'with SLC' }, %i[bamboo floating tempzone_mat] => { status: :yes, footnote: :floating_over_slc }, %i[laminate nailed tempzone_mat] => { status: :yes, footnote: :mat_sleepers, note: 'with sleepers' }, %i[laminate floating tempzone_mat] => { status: :yes, footnote: :floating_over_slc }, %i[lvt_vinyl glued tempzone_mat] => { status: :yes, footnote: :mat_with_slc, note: 'with SLC' }, %i[lvt_vinyl floating tempzone_mat] => { status: :yes, footnote: :floating_over_slc }, %i[carpet glued tempzone_mat] => { status: :yes, footnote: :mat_with_slc, note: 'with SLC', us_only: true }, %i[carpet stretch_in tempzone_mat] => { status: :no, us_only: true }, %i[cork floating tempzone_mat] => { status: :yes, footnote: :floating_over_slc }, %i[concrete in_slab tempzone_mat] => { status: :no }, # === Environ Mats === # Works with floating floors and stretch-in carpet (direct installation) # Note: Floating floor entries include footnote to check with flooring manufacturer %i[tile thinset environ_mat] => { status: :no }, %i[hardwood nailed environ_mat] => { status: :no }, %i[hardwood glued environ_mat] => { status: :no }, %i[engineered nailed environ_mat] => { status: :no }, %i[engineered glued environ_mat] => { status: :no }, %i[engineered floating environ_mat] => { status: :yes, footnote: :environ_flooring_check }, %i[bamboo nailed environ_mat] => { status: :no }, %i[bamboo glued environ_mat] => { status: :no }, %i[bamboo floating environ_mat] => { status: :yes, footnote: :environ_flooring_check }, %i[laminate nailed environ_mat] => { status: :no }, %i[laminate floating environ_mat] => { status: :yes, footnote: :environ_flooring_check }, %i[lvt_vinyl glued environ_mat] => { status: :no }, %i[lvt_vinyl floating environ_mat] => { status: :warning, footnote: :environ_flooring_check }, %i[carpet glued environ_mat] => { status: :no, us_only: true }, %i[carpet stretch_in environ_mat] => { status: :yes, note: 'Direct', us_only: true }, %i[cork floating environ_mat] => { status: :yes, footnote: :environ_flooring_check }, %i[concrete in_slab environ_mat] => { status: :no }, # === Slab Heat Mat === # Works with everything (goes in the concrete slab) - show as warning since it's indirect %i[tile thinset slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[hardwood nailed slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[hardwood glued slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[engineered nailed slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[engineered glued slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[engineered floating slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo nailed slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo glued slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[bamboo floating slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[laminate floating slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[laminate nailed slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[lvt_vinyl glued slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[lvt_vinyl floating slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[carpet glued slab_heat_mat] => { status: :warning, footnote: :slab_any_floor, us_only: true }, %i[carpet stretch_in slab_heat_mat] => { status: :warning, footnote: :slab_any_floor, us_only: true }, %i[cork floating slab_heat_mat] => { status: :warning, footnote: :slab_any_floor }, %i[concrete in_slab slab_heat_mat] => { status: :yes, note: 'Primary' } }.freeze
Class Method Summary collapse
-
.clear_cache! ⇒ Object
Cache clearing for development/testing.
-
.compatibility_for(floor_key, installation, system_key) ⇒ Object
Check compatibility from database with presentation overlay Returns: [status, footnote_key, note, us_only].
-
.database_compatible?(floor_key, installation, system_key) ⇒ Boolean
Query HeatingElementProductLineOption to check if compatible.
-
.floor_types ⇒ Object
Returns floor types with their installation methods derived from database.
-
.footnotes_for_systems(system_keys) ⇒ Object
Returns footnotes relevant to the displayed systems.
-
.heating_systems(category: nil) ⇒ Object
Returns heating systems, optionally filtered by category.
-
.installation_label(key) ⇒ Object
Returns human-readable installation method label.
Class Method Details
.clear_cache! ⇒ Object
Cache clearing for development/testing
442 443 444 |
# File 'app/lib/floor_heating_compatibility.rb', line 442 def clear_cache! @compatibility_cache = nil end |
.compatibility_for(floor_key, installation, system_key) ⇒ Object
Check compatibility from database with presentation overlay
Returns: [status, footnote_key, note, us_only]
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'app/lib/floor_heating_compatibility.rb', line 370 def compatibility_for(floor_key, installation, system_key) # First check if there's a presentation override override = COMPATIBILITY_NOTES[[floor_key, installation, system_key]] if override return [ override[:status], override[:footnote], override[:note], override[:us_only] ] end # Query database for actual compatibility compatible = database_compatible?(floor_key, installation, system_key) if compatible [:yes, nil, nil, nil] else [:no, nil, nil, nil] end end |
.database_compatible?(floor_key, installation, system_key) ⇒ Boolean
Query HeatingElementProductLineOption to check if compatible
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 |
# File 'app/lib/floor_heating_compatibility.rb', line 393 def database_compatible?(floor_key, installation, system_key) # Get the floor type's db_seo_keys for this installation floor_config = FLOOR_TYPES.find { |f| f[:key] == floor_key } return false unless floor_config # Find matching seo_keys for this installation method matching_seo_keys = floor_config[:db_seo_keys].select do |seo_key| SEO_KEY_TO_INSTALLATION[seo_key] == installation end return false if matching_seo_keys.empty? # Get the product line slug for this heating system system_config = HEATING_SYSTEMS.find { |s| s[:key] == system_key } return false unless system_config product_line_slug = system_config[:product_line_slug] # Check if any matching floor type is compatible with this product line HeatingElementProductLineOption .joins(:floor_type, :product_line) .where( environment: 'Indoor', is_public: true ) .where(floor_types: { seo_key: matching_seo_keys }) .where(product_lines: { slug_ltree: product_line_slug }) .exists? end |
.floor_types ⇒ Object
Returns floor types with their installation methods derived from database
358 359 360 361 362 363 364 365 366 |
# File 'app/lib/floor_heating_compatibility.rb', line 358 def floor_types FLOOR_TYPES.map do |ft| installations = ft[:db_seo_keys].filter_map do |seo_key| SEO_KEY_TO_INSTALLATION[seo_key] end.uniq ft.merge(installations: installations) end end |
.footnotes_for_systems(system_keys) ⇒ Object
Returns footnotes relevant to the displayed systems
423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'app/lib/floor_heating_compatibility.rb', line 423 def footnotes_for_systems(system_keys) used_footnote_keys = Set.new # Check all compatibility notes for used footnotes COMPATIBILITY_NOTES.each do |(floor_key, installation, sys_key), config| next unless system_keys.include?(sys_key) && config[:footnote] used_footnote_keys << config[:footnote] end FOOTNOTES.slice(*used_footnote_keys.to_a) end |
.heating_systems(category: nil) ⇒ Object
Returns heating systems, optionally filtered by category
351 352 353 354 355 |
# File 'app/lib/floor_heating_compatibility.rb', line 351 def heating_systems(category: nil) systems = HEATING_SYSTEMS systems = systems.select { |s| s[:category] == category } if category systems end |
.installation_label(key) ⇒ Object
Returns human-readable installation method label
437 438 439 |
# File 'app/lib/floor_heating_compatibility.rb', line 437 def installation_label(key) INSTALLATION_LABELS[key] || key.to_s.titleize end |