Class: ShowcasePresenter
- Inherits:
-
BasePresenter
- Object
- SimpleDelegator
- BasePresenter
- ShowcasePresenter
- Includes:
- Memery
- Defined in:
- app/presenters/showcase_presenter.rb
Overview
Presenter: showcase presenter.
Instance Attribute Summary
Attributes inherited from BasePresenter
#current_account, #options, #url_helper
Instance Method Summary collapse
-
#quick_links_hash ⇒ Array<Hash>
Quick links for the showcase sidebar, derived from breadcrumbs, product-line slugs, flooring surfaces, and enabled CTA buttons.
-
#schema_dot_org_structure ⇒ SchemaDotOrg::Article
JSON-LD for the project itself.
Methods inherited from BasePresenter
#can?, #capture, #concat, #content_tag, #fa_icon, #h, #initialize, #link_to, #number_to_currency, #present, presents, #r, #safe_present, #simple_format, #u
Constructor Details
This class inherits a constructor from BasePresenter
Instance Method Details
#quick_links_hash ⇒ Array<Hash>
Quick links for the showcase sidebar, derived from breadcrumbs,
product-line slugs, flooring surfaces, and enabled CTA buttons.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 81 82 83 84 85 86 87 88 |
# File 'app/presenters/showcase_presenter.rb', line 12 def quick_links_hash ql_array = (showcase. || []).dup urls = ql_array.map { |h| h[:url].to_s } if urls.empty? product_line_ids = Array(showcase.product_line_ids).compact item_ids = Array(showcase.item_ids).compact product_line_ids |= Item.joins(:product_lines).where(items: { id: item_ids }).pluck('product_lines.id') if item_ids.any? slugs = ProductLine.where(id: product_line_ids).pluck(:slug_ltree).filter_map(&:to_s) urls << h.cms_link('/floor-heating') if slugs.any? { |s| s.include?('floor_heating') } urls << h.cms_link('/snow-melting') if slugs.any? { |s| s.include?('snow_melting') } urls << h.cms_link('/roof-and-gutter-deicing') if slugs.any? { |s| s.include?('roof_and_gutter_deicing') } urls << h.cms_link('/pipe-freeze-protection') if slugs.any? { |s| s.include?('pipe_freeze') } urls << h.cms_link('/towel-warmer') if slugs.any? { |s| s.include?('towel_warmer') } urls << h.cms_link('/infrared-heating-panels') if slugs.any? { |s| s.include?('infrared_heating_panels') } urls << h.cms_link('/led-mirror') if slugs.any? { |s| s.include?('led_mirror') } urls << h.cms_link('/mirror-defogger') if slugs.any? { |s| s.include?('mirror_defogger') } end if urls.any? { |u| u.match?(%r{/(floor-heating|indoor-heating)}) } ql_array << { name: 'Floor Heating', url: h.cms_link('/floor-heating'), icon: 'flame' } ql_array << { name: 'Instant Quote', url: h.cms_link('/floor-heating/quote-builder'), icon: 'bolt' } ql_array << { name: 'Running Costs', url: h.cms_link('/floor-heating/cost-calculator'), icon: 'calculator' } end if urls.any? { |u| u.match?(%r{/(snow-melting|outdoor-heating)}) } ql_array << { name: 'Snow Melting', url: h.cms_link('/snow-melting'), icon: 'snowflake' } ql_array << { name: 'Instant Quote', url: h.cms_link('/snow-melting/quote-builder'), icon: 'bolt' } ql_array << { name: 'Running Costs', url: h.cms_link('/snow-melting/cost-calculator'), icon: 'calculator' } end # Optional: add base category links for other detected contexts ql_array << { name: 'Towel Warmers', url: h.cms_link('/towel-warmer'), icon: 'bath' } if urls.any? { |u| u.include?('/towel-warmer') } ql_array << { name: 'Infrared Heating Panels', url: h.cms_link('/infrared-heating-panels'), icon: 'sun' } if urls.any? { |u| u.include?('/infrared-heating-panels') } ql_array << { name: 'Roof & Gutter Deicing', url: h.cms_link('/roof-and-gutter-deicing'), icon: 'icicles' } if urls.any? { |u| u.include?('/roof-and-gutter-deicing') } ql_array << { name: 'Pipe Freeze Protection', url: h.cms_link('/pipe-freeze-protection'), icon: 'icicles' } if urls.any? { |u| u.include?('/pipe-freeze-protection') } # Floor Plan Gallery link - only for floor heating and snow melting showcases ql_array << { name: 'Floor Plan Gallery', url: h.cms_link('/floor-plans'), icon: 'th' } if urls.any? { |u| u.match?(%r{/(floor-heating|indoor-heating|snow-melting|outdoor-heating)}) } # Flooring/Surface driven links if showcase.respond_to?(:flooring_surface_types) && showcase.flooring_surface_types.present? showcase.flooring_surface_types.each do |name| next if name.blank? if (path = ShowcaseTaxonomies.flooring_surface_link_for(name)).present? ql_array << { name: name, url: h.cms_link(path), icon: 'chevron-right' } end end end # Merge enabled CTA buttons into quick links = { 'button_design_room' => { name: 'Design Your Own Heated Space', url: h.cms_link('room_plans/new'), icon: 'paint-brush' }, 'button_floor_heating' => { name: 'Floor Heating', url: h.cms_link('/floor-heating'), icon: 'flame' }, 'button_floor_heating_quote' => { name: 'Floor Heating Quote', url: h.cms_link('/floor-heating/quote-builder'), icon: 'bolt' }, 'button_snow_melting' => { name: 'Snow Melting', url: h.cms_link('/snow-melting'), icon: 'snowflake' }, 'button_snow_melting_quote' => { name: 'Snow Melting Quote', url: h.cms_link('/snow-melting/quote-builder'), icon: 'bolt' }, 'button_roof_gutter_deicing' => { name: 'Roof & Gutter Deicing', url: h.cms_link('/roof-and-gutter-deicing'), icon: 'icicles' }, 'button_pipe_freeze_protection' => { name: 'Pipe Freeze Protection', url: h.cms_link('/pipe-freeze-protection'), icon: 'icicles' }, 'button_customize_floor_plan' => { name: 'Customize Floor Plan', url: h.cms_link('/floor-plans'), icon: 'th' } } existing_urls = ql_array.map { |link| link[:url].to_s } Array(showcase.).each do |btn| link = [btn] next unless link next if existing_urls.include?(link[:url].to_s) ql_array << link existing_urls << link[:url].to_s end ql_array.uniq { |h| h[:url] }.sort_by { |h| h[:name].to_s } end |
#schema_dot_org_structure ⇒ SchemaDotOrg::Article
JSON-LD for the project itself. The page already emits FAQPage and
BreadcrumbList, so the questions were machine-readable while the specs —
the part the AEO prompt calls the "agentic data table" — were only prose in
an HTML table. An AI agent could quote our FAQ answer but not the wattage.
Modeled as an Article about a SchemaDotOrg::Place: the specs live in the
Place's additionalProperty, which is where schema.org puts a spec table.
The pairs deliberately mirror the visible chip bar in
shared/_showcase.html.erb one for one — structured data that claims more
than the page shows is exactly what Google penalizes.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/presenters/showcase_presenter.rb', line 103 def schema_dot_org_structure article = SchemaDotOrg::Article.new( headline: showcase.name, name: showcase.name, description: showcase.seo_description.presence || showcase.short_description, url: canonical_url, mainEntityOfPage: canonical_url, inLanguage: I18n.locale.to_s.tr('_', '-'), datePublished: showcase.created_at, dateModified: showcase.updated_at, image: image_schemas, publisher: publisher_schema, about: project_place ) article.id_iri = canonical_url article end |