Module: PageSectionsHelper

Defined in:
app/helpers/page_sections_helper.rb

Overview

View helpers for the standard sections shared across WWW landing pages —
FAQ, videos, showcases, documents, blog posts, reviews, plus the
single-component sections (benefits, calculator, other products,
smartplan, comparison).

Each helper owns the full repetitive pattern in one place: the
records.present? guard, the page_section chrome (canonical id and
border), and the component render. A call site collapses from a ~6-line
if … page_section … render block to a single line, and a change to a
section's wrapper style (e.g. the FAQ border:) happens here once
instead of on ~60 landing pages — preventing content-style drift.

Records default to the page's tag-scoped set (see PagesHelper); pass an
explicit collection to override. Any further keyword arguments are
forwarded to the underlying component; the rare page_section override
goes via section:.

Pass fragment_cache: true to wrap a section in a fragment cache. It is
off by default — these pages are served from Cloudflare's edge cache, so
fragment caching is only an occasional optimisation.

Instance Method Summary collapse

Instance Method Details

#benefits_section(section: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard "Why Install" benefits section (mint-green band).

All keyword arguments are forwarded to Www::BenefitsListComponent.

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call
    (e.g. id: 'benefits' for the controls/trade variant)

Returns:

  • (ActiveSupport::SafeBuffer)


228
229
230
231
232
# File 'app/helpers/page_sections_helper.rb', line 228

def benefits_section(section: {}, **)
  page_section(id: 'why-install', container: :none, border: :none, background: :mint_green, **section) do
    render Www::BenefitsListComponent.new(**)
  end
end

#calculator_section(section: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard floor-heating cost calculator section.

All keyword arguments are forwarded to Www::FloorHeatingCalculatorComponent.

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

Returns:

  • (ActiveSupport::SafeBuffer)


240
241
242
243
244
# File 'app/helpers/page_sections_helper.rb', line 240

def calculator_section(section: {}, **)
  page_section(id: 'floor-heating-calculator', **section) do
    render Www::FloorHeatingCalculatorComponent.new(**)
  end
end

#comparison_section(section: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard product comparison-table section.

All keyword arguments are forwarded to Www::ProductCompareTableComponent.

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

Returns:

  • (ActiveSupport::SafeBuffer)


276
277
278
279
280
# File 'app/helpers/page_sections_helper.rb', line 276

def comparison_section(section: {}, **)
  page_section(id: 'comparison', **section) do
    render Www::ProductCompareTableComponent.new(**)
  end
end

#documents_section(publications = page_publications, title: 'Documents', section: {}, fragment_cache: false) ⇒ ActiveSupport::SafeBuffer?

Renders the standard Documents section — publication PDFs. (Blog posts
render separately via #posts_section.)

Extra keyword arguments are forwarded to Www::CardGridComponent.

Parameters:

  • publications (Enumerable<Publication>) (defaults to: page_publications)

    defaults to the page's
    tag-scoped set via PagesHelper#page_publications

  • title (String) (defaults to: 'Documents')

    section heading

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

  • fragment_cache (Boolean) (defaults to: false)

    wrap the section in a fragment cache

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



106
107
108
109
110
111
112
113
114
# File 'app/helpers/page_sections_helper.rb', line 106

def documents_section(publications = page_publications, title: 'Documents', section: {}, fragment_cache: false, **)
  return if publications.blank?

  cached_section('documents', publications, enabled: fragment_cache) do
    page_section(id: 'documents', **section) do
      render Www::CardGridComponent.new(section_title: title, publications:, **)
    end
  end
end

#education_sections(videos: {}, documents: {}, posts: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard education sections — Videos, then Documents, then
Articles — in that fixed order as one unit, so the trio always renders
together and consistently ordered on every landing page (the same
bundling approach as #social_proof_sections).

Each section's options go in its own hash, forwarded to the underlying
helper. Pass a custom record collection via the records: key; omit it
to use that helper's tag-scoped default. A section with nothing to show
is skipped.

Parameters:

Returns:

  • (ActiveSupport::SafeBuffer)

    the sections, in order



210
211
212
213
214
215
216
217
218
219
# File 'app/helpers/page_sections_helper.rb', line 210

def education_sections(videos: {}, documents: {}, posts: {})
  safe_join(
    [[:videos_section, videos], [:documents_section, documents], [:posts_section, posts]].filter_map do |helper, opts|
      opts = opts.dup
      has_records = opts.key?(:records)
      records = opts.delete(:records)
      has_records ? send(helper, records, **opts) : send(helper, **opts)
    end
  )
end

#faq_section(article_faqs = page_article_faqs, title: 'Frequently Asked Questions', section: {}, fragment_cache: false) ⇒ ActiveSupport::SafeBuffer?

Renders the standard FAQ section — the last section on a landing page.

Extra keyword arguments are forwarded to Www::FaqListComponent
(e.g. filter_tags:, initial_limit:).

Parameters:

  • article_faqs (Array<Article>) (defaults to: page_article_faqs)

    FAQ records; defaults to the page's
    tag-scoped set via PagesHelper#page_article_faqs

  • title (String) (defaults to: 'Frequently Asked Questions')

    section heading

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

  • fragment_cache (Boolean) (defaults to: false)

    wrap the section in a fragment cache

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



44
45
46
47
48
49
50
51
52
# File 'app/helpers/page_sections_helper.rb', line 44

def faq_section(article_faqs = page_article_faqs, title: 'Frequently Asked Questions', section: {}, fragment_cache: false, **)
  return if article_faqs.blank?

  cached_section('faq', article_faqs, enabled: fragment_cache) do
    page_section(id: 'faq', border: :none, **section) do
      render Www::FaqListComponent.new(section_title: title, article_faqs:, **)
    end
  end
end

#other_products_section(section: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard "Other Products" category-cards section.

All keyword arguments are forwarded to Www::ProductCategoryCardsComponent.

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

Returns:

  • (ActiveSupport::SafeBuffer)


252
253
254
255
256
# File 'app/helpers/page_sections_helper.rb', line 252

def other_products_section(section: {}, **)
  page_section(id: 'other-products', **section) do
    render Www::ProductCategoryCardsComponent.new(**)
  end
end

#page_topic(value = nil) ⇒ String?

Renders the standard Articles section (blog posts).

Extra keyword arguments are forwarded to Www::CardGridComponent.

Setter/getter for the page's SEO topic — the short noun phrase a page
is "about". Used as the leading term in related-content section
headings (see #posts_section). When unset, formatted_breadcrumb
auto-derives one by reverse-joining the breadcrumb crumb names —
[Floor Heating, Bathroom]"Bathroom Floor Heating". Pages that
don't use breadcrumbs (or want a different phrase) can override:

<% page_topic 'Custom SEO Phrase' %>

Parameters:

  • value (String, nil) (defaults to: nil)

    when present, set the topic; otherwise read

Returns:

  • (String, nil)

    the current topic, or nil if none set / derivable



131
132
133
134
# File 'app/helpers/page_sections_helper.rb', line 131

def page_topic(value = nil)
  @page_topic = value if value
  @page_topic
end

#posts_section(posts = page_posts, title: nil, section: {}, fragment_cache: false) ⇒ ActiveSupport::SafeBuffer?

Returns nil — renders nothing — when empty.

Parameters:

  • posts (Enumerable<Post>) (defaults to: page_posts)

    posts; defaults to the page's tag-scoped
    set via PagesHelper#page_posts

  • title (String, nil) (defaults to: nil)

    section heading; when nil, derives a SEO-
    friendly heading from #page_topic (e.g. "Bathroom Floor Heating Posts"), falling back to a plain "Posts" when no topic is known

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

  • fragment_cache (Boolean) (defaults to: false)

    wrap the section in a fragment cache

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



144
145
146
147
148
149
150
151
152
153
154
# File 'app/helpers/page_sections_helper.rb', line 144

def posts_section(posts = page_posts, title: nil, section: {}, fragment_cache: false, **)
  return if posts.blank?

  title ||= page_topic.present? ? "#{page_topic} Posts" : 'Posts'

  cached_section('posts', posts, enabled: fragment_cache) do
    page_section(id: 'posts', **section) do
      render Www::CardGridComponent.new(section_title: title, posts:, **)
    end
  end
end

#reviews_section(section: {}) ⇒ ActiveSupport::SafeBuffer?

Renders the standard Customer Reviews section.

All keyword arguments are forwarded to Www::ReviewsIo::ReviewSliderComponent
(e.g. tags:, product_line:, section_title:, see_all_url:, min_rating:).
The section — wrapper included — is omitted entirely when the component has
nothing to show (no matching reviews and no founder lead card).

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



165
166
167
168
169
170
171
172
# File 'app/helpers/page_sections_helper.rb', line 165

def reviews_section(section: {}, **)
  component = Www::ReviewsIo::ReviewSliderComponent.new(**)
  return unless component.render?

  page_section(id: 'reviews', **section) do
    render component
  end
end

#showcases_section(showcases = page_showcases, title: 'Featured Projects', section: {}, fragment_cache: false) ⇒ ActiveSupport::SafeBuffer?

Renders the standard Showcases ("Featured Projects") section.

Extra keyword arguments are forwarded to Www::ShowcaseGridComponent
(e.g. description:, view_more_title:, view_more_href:).

Parameters:

  • showcases (Enumerable<Showcase>) (defaults to: page_showcases)

    showcase records; defaults to the
    page's tag-scoped set via PagesHelper#page_showcases

  • title (String) (defaults to: 'Featured Projects')

    section heading

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

  • fragment_cache (Boolean) (defaults to: false)

    wrap the section in a fragment cache

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



85
86
87
88
89
90
91
92
93
# File 'app/helpers/page_sections_helper.rb', line 85

def showcases_section(showcases = page_showcases, title: 'Featured Projects', section: {}, fragment_cache: false, **)
  return if showcases.blank?

  cached_section('showcases', showcases, enabled: fragment_cache) do
    page_section(id: 'showcases', **section) do
      render Www::ShowcaseGridComponent.new(section_title: title, showcases:, **)
    end
  end
end

#smartplan_section(section: {}) ⇒ ActiveSupport::SafeBuffer

Renders the standard "Request a SmartPlan" section.

All keyword arguments are forwarded to Www::RequestSmartplanSectionComponent.

Parameters:

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

Returns:

  • (ActiveSupport::SafeBuffer)


264
265
266
267
268
# File 'app/helpers/page_sections_helper.rb', line 264

def smartplan_section(section: {}, **)
  page_section(id: 'request-smartplan', container: :none, **section) do
    render Www::RequestSmartplanSectionComponent.new(**)
  end
end

#social_proof_sections(showcases = page_showcases, reviews: {}, **showcases_options) ⇒ ActiveSupport::SafeBuffer

Renders the Reviews section immediately followed by the Showcases
section — the canonical "social proof" pairing (see the home page).
Bundling them in one call guarantees the two render adjacent and in
this order on every landing page, so the pairing cannot drift apart.

Showcase records and showcases_section options are passed exactly as
to #showcases_section; Reviews options go in the reviews: hash and
are forwarded to #reviews_section. Either section is omitted when it
has nothing to show.

Parameters:

Returns:

  • (ActiveSupport::SafeBuffer)

    the two sections, in order



189
190
191
192
193
194
# File 'app/helpers/page_sections_helper.rb', line 189

def social_proof_sections(showcases = page_showcases, reviews: {}, **showcases_options)
  safe_join([
    reviews_section(**reviews),
    showcases_section(showcases, **showcases_options)
  ].compact)
end

#videos_section(videos = page_videos, title: 'Videos', section: {}, fragment_cache: false) ⇒ ActiveSupport::SafeBuffer?

Renders the standard Videos section.

Extra keyword arguments are forwarded to Www::VideoSectionComponent.

Parameters:

  • videos (Enumerable<Video>) (defaults to: page_videos)

    video records; defaults to the page's
    tag-scoped set via PagesHelper#page_videos

  • title (String) (defaults to: 'Videos')

    section heading

  • section (Hash) (defaults to: {})

    overrides merged into the page_section call

  • fragment_cache (Boolean) (defaults to: false)

    wrap the section in a fragment cache

Returns:

  • (ActiveSupport::SafeBuffer, nil)

    nil — renders nothing — when empty



64
65
66
67
68
69
70
71
72
# File 'app/helpers/page_sections_helper.rb', line 64

def videos_section(videos = page_videos, title: 'Videos', section: {}, fragment_cache: false, **)
  return if videos.blank?

  cached_section('videos', videos, enabled: fragment_cache) do
    page_section(id: 'videos', **section) do
      render Www::VideoSectionComponent.new(section_title: title, videos:, **)
    end
  end
end