Module: Consently::TagsHelper

Defined in:
app/helpers/consently/tags_helper.rb

Overview

The three helpers a host application calls: the tags in , the noscript fallbacks right after , and the banner anywhere on the page.

Instance Method Summary collapse

Instance Method Details

#consently_banner(policy_url: nil) ⇒ Object

The banner, the preferences panel, and the JavaScript that releases the blocked tags. Render it once per page, ideally at the end of the body.

The container stays in the DOM after a choice is made so that consently_preferences_link has something to reopen.



43
44
45
46
47
48
49
50
# File 'app/helpers/consently/tags_helper.rb', line 43

def consently_banner(policy_url: nil)
  return "".html_safe unless consently_enabled? && Consently.consent_required?(request)

  render "consently/banner",
    consent: consently_consent,
    policy_url: policy_url || consently_policy_url,
    categories: Consently.config.optional_categories
end


65
66
67
68
69
70
71
72
73
74
75
76
# File 'app/helpers/consently/tags_helper.rb', line 65

def consently_consent
  @consently_consent ||= if Consently.consent_required?(request)
    Consent.from_cookie(
      cookies[Consently.config.cookie_name],
      version: Consently.config.consent_version,
      max_age: Consently.config.consent_max_age
    )
  else
    # Nobody to ask, so nothing is held back.
    Consent.new(categories: Consently.config.categories, version: Consently.config.consent_version)
  end
end

#consently_data_layer_push(event, category: :analytics, **payload) ⇒ Object

Push an event onto the dataLayer from a view, respecting consent: with no analytics consent the event is simply not emitted.

<%= consently_data_layer_push("newsletter_signup", source: "footer") %>


82
83
84
85
86
87
# File 'app/helpers/consently/tags_helper.rb', line 82

def consently_data_layer_push(event, category: :analytics, **payload)
  return "".html_safe unless consently_enabled? && consently_consent.granted?(category)

  payload = payload.merge(event: event)
  consently_inline_script "window.dataLayer = window.dataLayer || []; window.dataLayer.push(#{payload.to_json});"
end

#consently_ecommerce(event, items: [], category: :analytics, **params) ⇒ Object

A GA4 ecommerce event in the shape Google expects, from whatever your models happen to look like:

<%= consently_ecommerce("purchase", items: @order.line_items,
    value: @order.total, currency: "EUR", transaction_id: @order.number) %>

Items may be hashes already in GA4 shape, or any object answering to a few obvious names (id/sku, name, price, quantity, category, brand, variant) - a LineItem or a Product usually does.

The previous ecommerce object is cleared first, as Google asks, so two events on one page cannot bleed into each other.



101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/helpers/consently/tags_helper.rb', line 101

def consently_ecommerce(event, items: [], category: :analytics, **params)
  return "".html_safe unless consently_enabled? && consently_consent.granted?(category)

  ecommerce = params.merge(items: Array(items).map { |item| consently_ecommerce_item(item) })
  payload = { event: event, ecommerce: ecommerce.compact }

  consently_inline_script <<~JS.strip
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ ecommerce: null });
    window.dataLayer.push(#{payload.to_json});
  JS
end

#consently_embed(kind, identifier, category: :marketing, title: nil, ratio: "16 / 9", **iframe_options) ⇒ Object

An embedded video or map that waits for consent. Blocking scripts is only half the job: a YouTube iframe sets cookies all by itself.

<%= consently_embed :youtube, "dQw4w9WgXcQ" %>
<%= consently_embed :vimeo, "76979871", category: :analytics %>
<%= consently_embed :google_maps, "Bahnhofstrasse 12, Berlin" %>
<%= consently_embed :custom, "https://example.com/widget", title: "Widget" %>

Until the category is granted the visitor sees a placeholder with a button that opens the preferences panel; the iframe appears the moment they agree, without a reload.



134
135
136
137
138
139
140
141
142
143
144
145
# File 'app/helpers/consently/tags_helper.rb', line 134

def consently_embed(kind, identifier, category: :marketing, title: nil, ratio: "16 / 9", **iframe_options)
  src = consently_embed_src(kind, identifier)
  return "".html_safe if src.blank?

  render "consently/embed",
    src: src,
    title: title || t("consently.embed.title_#{kind}", default: t("consently.embed.title_default")),
    category: category.to_sym,
    ratio: ratio,
    granted: consently_consent.granted?(category),
    iframe_options: iframe_options
end

#consently_log_urlObject

Where the banner POSTs the decision, when consent logging is on and the engine is mounted. Nil otherwise, and the banner skips the request.



156
157
158
159
160
161
162
# File 'app/helpers/consently/tags_helper.rb', line 156

def consently_log_url
  return nil unless Consently.config.log_consents

  consently.consents_path
rescue NoMethodError, NameError
  nil
end

#consently_noscript_tagsObject

Goes directly after - Google Tag Manager and Meta both still ship a noscript fallback. Only rendered for categories already granted: there is no way to hold an iframe back and release it later.



25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/helpers/consently/tags_helper.rb', line 25

def consently_noscript_tags
  return "".html_safe unless consently_enabled?

  fallbacks = Consently.tags_for(request).filter_map do |provider|
    next unless consently_consent.granted?(provider.category)

    provider.noscript&.html_safe
  end
  return "".html_safe if fallbacks.empty?

  (:noscript, safe_join(fallbacks, "\n"))
end

#consently_policyObject

A complete cookie policy for the tags this request would load: every category, every vendor, every cookie it sets and for how long, plus whether the visitor has agreed to it right now.

Drop it into your own policy page under your own heading and legal text.



119
120
121
# File 'app/helpers/consently/tags_helper.rb', line 119

def consently_policy
  render "consently/policy", tags: Consently.tags_for(request), consent: consently_consent
end

A "Cookie settings" link for the footer. Reopens the panel.

Marked rather than wired: the link lives outside the banner element, so a data-action on it would never bind. The controller watches the whole document for a click on anything carrying this attribute, which also means your own markup can reopen the panel just by wearing it.



58
59
60
61
62
63
# File 'app/helpers/consently/tags_helper.rb', line 58

def consently_preferences_link(name = nil, **options, &block)
  name ||= t("consently.preferences_link")
  options[:data] = { consently_open: true }.merge(options[:data] || {})

  link_to(name, "#consently", options, &block)
end

#consently_stylesheet_tagObject

The banner brings its own plain CSS - no framework, no build step. The look is driven by custom properties, so overriding a few variables is usually enough; rails g consently:views is there for the rest.



150
151
152
# File 'app/helpers/consently/tags_helper.rb', line 150

def consently_stylesheet_tag
  stylesheet_link_tag "consently", media: "all"
end

#consently_tagsObject

Every tag configured for this request. Tags whose category the visitor has not agreed to are rendered inert (type="text/plain") and the banner turns them into real scripts the moment consent is given - so a visitor who accepts does not have to reload to be counted.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'app/helpers/consently/tags_helper.rb', line 9

def consently_tags
  return "".html_safe unless consently_enabled?

  parts = []
  parts << consently_stylesheet_tag if Consently.config.stylesheet
  parts << consently_consent_mode_tag if Consently.config.google_consent_mode
  Consently.tags_for(request).each do |provider|
    provider.scripts.each { |script| parts << consently_script_tag(script, provider, consently_release?(provider)) }
  end

  safe_join(parts, "\n")
end