0.0
No release in over 3 years
rails_consent provides a Rails Engine, customizable templates, generators, and a minimal JavaScript runtime for managing consent preferences in Rails applications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.1, < 9.0
 Project Readme

Rails Consent

RubyGems version

Documentation · See the demo · Changelog · RubyGems · GitHub

Rails Consent

rails_consent is a Rails engine for cookie consent in Rails applications, built around locale-backed categories, a built-in signed cookie flow, small helper APIs, and a minimal JavaScript runtime.

Features

  • Cookie consent banner and preferences modal
  • Built-in signed cookie persistence with optional :db storage hooks
  • Locale-backed category definitions and interface copy
  • Helpers for views, controllers, and layouts
  • Minimal browser runtime with lifecycle events and a small client-side API
  • Overrideable templates without copying runtime assets
  • Importmap and Turbo-friendly integration

Installation

Add the gem to your Rails app:

gem "rails_consent"

Then install it:

bundle install
bin/rails generate rails_consent:install

The installer creates:

  • config/initializers/rails_consent.rb
  • config/locales/rails_consent.en.yml

In the default :cookie mode, that generated setup is enough to render the UI and persist consent through a signed Rails cookie.

Define Categories and Copy

Rails Consent keeps banner copy, modal copy, and category definitions in one locale file:

en:
  rails_consent:
    banner:
      eyebrow: Privacy settings
      title: Review cookie choices
      description: Necessary cookies stay enabled. Optional categories can be updated at any time.
      accept_all: Allow all
      reject_optional: Reject optional
      manage_preferences: Privacy preferences
    modal:
      eyebrow: Cookie preferences
      title: Review cookie categories
      save_preferences: Save settings
    categories:
      necessary:
        label: Necessary
        required: true
        description: Essential cookies required for site functionality
      analytics:
        label: Analytics
        required: false
        description: Used to understand visitor behavior
      marketing:
        label: Marketing
        required: false
        description: Used for advertising and remarketing

Display order follows the locale file order.

Configure Rails Consent

The initializer defines where consent lives, how the banner behaves by default, and how category checks should resolve:

RailsConsent.configure do |config|
  config.storage = :cookie
  config.banner_position = :bottom
  config.prompt_dismissible = true
  config.cookie_name = "rails_consent_preferences"
  config.cookie_expiration_days = 180
  # config.cookie_same_site = :lax
  # config.cookie_http_only = true
  # config.cookie_secure = ->(request) { request.ssl? || Rails.env.production? }
  config.consent_resolver = RailsConsent::DefaultConsentResolver.new
end

consent_resolver is required. Start with RailsConsent::DefaultConsentResolver.new when each optional category maps directly to a boolean consent decision, then replace it when your application needs additional rules.

Render the Consent UI

Load the gem assets in your layout and keep the banner mounted near the bottom of the page:

<head>
  <%= rails_consent_assets %>
</head>

<body>
  <%= yield %>
  <%= rails_consent_banner %>
</body>

rails_consent_assets loads only the gem's core runtime assets: rails_consent.js and the minimal rails_consent/application.css stylesheet used for runtime visibility utilities.

If one page needs different behavior, override it locally:

<%= rails_consent_banner position: :top, dismissible: false %>

Reopen Preferences Later

Use the shared helper anywhere you want to reopen the preferences modal:

<%= rails_consent_preferences_button "Privacy preferences", class: "footer-button" %>

That helper is a good fit for footers, privacy pages, and account settings screens.

Gate Optional Behavior

Use consent_given? anywhere you need to align server-rendered behavior with the current consent state:

<% if consent_given?(:analytics) %>
  <%= render "analytics_script" %>
<% end %>

The public helper surface is:

rails_consent_assets
rails_consent_banner
rails_consent_preferences_button
consent_given?(:analytics)
consent_preferences
consent_recorded?
reset_consent!

Client-side API

Rails Consent also exposes a small browser API through window.RailsConsent:

window.RailsConsent.preferences()
window.RailsConsent.consentGiven("analytics")
window.RailsConsent.consentRecorded()
window.RailsConsent.sessionId()
window.RailsConsent.openPreferences()

Lifecycle events are dispatched on document so host apps can react without modifying the gem runtime:

  • rails-consent:banner-shown
  • rails-consent:preferences-opened
  • rails-consent:preferences-saved
  • rails-consent:preferences-updated
  • rails-consent:accepted-all
  • rails-consent:rejected-optional
  • rails-consent:dismissed

Storage Modes

Signed Cookie Storage

config.storage = :cookie uses the built-in persistence route and a signed cookie payload keyed by category name. This is the default integration path.

If you want to wrap the built-in cookie flow without replacing it, provide cookie_reader, cookie_writer, or cookie_destroyer hooks around RailsConsent::CookieStore.

Database-backed Storage

When consent should live alongside a user or account record, switch only the storage hooks:

RailsConsent.configure do |config|
  config.storage = :db
  config.banner_position = :bottom
  config.prompt_dismissible = true
  config.consent_resolver = RailsConsent::DefaultConsentResolver.new

  config.preferences_provider = ->(context:, **) do
    context.current_user&.consent_preferences || {}
  end

  config.preferences_writer = ->(preferences:, context:, **) do
    user = context.current_user
    raise "current_user is required for db-backed consent" unless user

    user.update!(consent_preferences: preferences)
    user.consent_preferences
  end

  config.preferences_destroyer = ->(context:, **) do
    context.current_user&.update!(consent_preferences: {})
  end
end

Most applications pair that initializer with a JSON or JSONB column such as users.consent_preferences.

Customize Presentation

Override any shipped partial from your host app by creating:

app/views/rails_consent/_banner.html.erb
app/views/rails_consent/_preferences_modal.html.erb
app/views/rails_consent/_category_toggle.html.erb
app/views/rails_consent/_cookie_table.html.erb

Keep wording changes in locale YAML when the markup stays the same. Reach for template overrides only when the structure itself needs to change.

Troubleshooting

  • If the banner never appears, confirm rails_consent_banner is mounted in your layout and consent has not already been recorded.
  • If the UI opens without styles or behavior, confirm rails_consent_assets is loaded in <head>.
  • If boot fails with a configuration error, compare your initializer to the generated file and restore any required lines that were removed.
  • If a custom category never enables, review consent_resolver after changing config/locales/rails_consent.en.yml.
  • If :db mode does not persist updates, confirm preferences_provider, preferences_writer, and preferences_destroyer are all configured and return hashes keyed by category name.
  • If client-side integrations cannot read consent, keep rails_consent_banner mounted on the page so window.RailsConsent has a rendered boundary to read from.

Documentation

Full guides, live demos, and integration examples are available at:

Contributing

See CONTRIBUTING.md for development and contribution guidelines.

License

Released under the MIT license. See LICENSE.