The project is in a healthy, maintained state
Adds the Privatus Analytics tracking script to your Rails views with a helper or an optional Rack middleware. The script sets no cookies, so analytics does not need a consent banner by itself.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 6.1, < 9
>= 6.1, < 9
 Project Readme

Privatus Analytics for Rails

Real visitor numbers for your Rails app. No cookies, no cookie pop-up, no bots.

You want to know if people use your Rails app. But Google Analytics uses cookies, so in many countries you must show a cookie pop-up first (the box that asks visitors to say yes to cookies). Many people click no, so they never show up in your numbers. Bots add fake visits on top.

This gem adds the Privatus Analytics script to your Rails app with a view helper, or a Rack middleware that puts it in the page head for you. The script sets no cookies and keeps no IP addresses. Bots are filtered out before they are counted.

So you see how many real people came, which pages they looked at and where they came from, on one simple dashboard. A report comes to your inbox each week, and you can set alerts for when traffic jumps or drops. And you don't need a cookie pop-up for your analytics.

Free plan, no card: 10,000 events a month (an event is a page view or an action you track) on up to 3 sites. Start free at privatusanalytics.com.

  • No cookies. No cookies and no hidden IDs on your visitors' devices, and no IP addresses are kept. Laws differ by country, so check your own legal advice.
  • Real people, not bots. Known bots, data centers and spam visits are left out on every plan.
  • Small and fast. The script is about 3 KB when compressed.

What this package does

  • Adds the tracking script to your Rails app with a view helper.
  • An optional Rack middleware can put the script in the <head> for you.
  • The gem only prints the browser script. It never sends events from your server and sets no cookies.

You need a Privatus Analytics account to see your reports, and there is a free plan. Sign up at privatusanalytics.com.

Requirements

  • Ruby 3.2 or later.
  • Rails 6.1 to 8.x.
  • A Privatus Analytics site id (it starts with pa_). You find it under Site settings, Tracking.

Install

Add the gem to your Gemfile:

gem "privatus-analytics-rails"

Then run bundle install. For the full setup guide and every tracker option, see the Privatus Analytics docs.

Set your site id with an environment variable:

PRIVATUS_SITE_ID=pa_YOURSITEID

or in config/environments/production.rb (or config/application.rb):

config.privatus_analytics.site_id = "pa_YOURSITEID"

Add the tag

Put the helper in the <head> of your layout, for example app/views/layouts/application.html.erb:

<head>
  <title>My app</title>
  <%= privatus_analytics_tag %>
</head>

In production it prints:

<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID"></script>

The tag prints nothing when there is no site id, and nothing outside the production environment (see below). The one exception is the queue stub: when you turn it on, it still prints. If your app uses a Content Security Policy nonce, the helper adds it for you.

Or let the gem add it

If you would rather not touch your layouts, switch on the middleware:

config.privatus_analytics.auto_inject = true

It adds the tag just before </head> in uncompressed text/html responses with status 200. It skips Ajax requests, pages without a <head>, and pages that already have the tag. Responses that have a Content-Encoding header are left alone, so if you compress inside the Rack stack, add the middleware above Rack::Deflater or use the helper instead. The middleware also adds the Rails Content Security Policy nonce when your app has one.

Configuration

Every setting is optional except the site id.

# config/environments/production.rb
Rails.application.configure do
  config.privatus_analytics.site_id        = "pa_YOURSITEID"      # or ENV["PRIVATUS_SITE_ID"]
  config.privatus_analytics.script_host    = "https://privatusstats.com" # or ENV["PRIVATUS_SCRIPT_HOST"]
  config.privatus_analytics.modules        = %w[engage auto vitals clicks ask]
  config.privatus_analytics.excluded_paths = %w[/admin/* /preview/*]
  config.privatus_analytics.domains        = %w[example.com www.example.com]
  config.privatus_analytics.spa            = "auto"   # auto, history, hash or off
  config.privatus_analytics.manual         = false    # true turns off the automatic pageview
  config.privatus_analytics.auto_inject    = false
  config.privatus_analytics.enabled_environments = %w[production]
  config.privatus_analytics.extra_attributes = { "data-mask" => "/orders/*->/orders/:id" }
  config.privatus_analytics.props          = ->(request) { { plan: Current.account&.plan } }
  config.privatus_analytics.queue_stub     = false
end
Setting Default What it does
site_id ENV["PRIVATUS_SITE_ID"] The data-site attribute. Nothing prints without it
script_host https://privatusstats.com Where /js/pa.js is served. Change it only if you host your own copy
modules tracker default (engage) The data-modules attribute. Setting it replaces the default, so keep engage for time on page and scroll depth. [] loads none
excluded_paths none The data-exclude attribute. Path globs the tracker never counts
domains all hosts The data-domains attribute. Count only these hostnames
spa auto The data-spa attribute
manual false The data-manual attribute
enabled_environments ["production"] Rails environments that get the tag
enabled nil true or false forces the tag on or off in every environment
auto_inject false Adds the tag with a Rack middleware
extra_attributes {} Any other data-* attribute
props {} Page props sent with every pageview and event. A hash, or a lambda that gets the request. See below
queue_stub false Prints a small script first, so privatus.track calls made before the tracker loads are kept and sent later. It still prints when the tag is off, so those calls never fail

Each tracker attribute is explained in the script attributes reference, and the modules in Modules.

You can also override a setting for one page:

<%= privatus_analytics_tag(modules: %w[engage auto], excluded_paths: ["/preview/*"]) %>

Development and test

Development, test and staging requests are not counted by default, so your own clicking around never reaches your reports. To see the tag while you build, set config.privatus_analytics.enabled = true. The tracker also ignores localhost unless you pass data-allow-local or data-debug (see Debug mode):

config.privatus_analytics.extra_attributes = { "data-debug" => "true" }

Leaving people out

Use data-exclude through excluded_paths for admin areas. To leave out signed-in staff, render the helper conditionally:

<%= privatus_analytics_tag unless current_user&.admin? %>

Product analytics: see which features people use

Privatus Analytics can show which buttons, forms and features people really use. You add a few attributes to your HTML. The tracker reads them in the browser and sends the events. Your server still sends nothing.

Name events well

Use a short "Noun verb" name, like Report exported or Invite sent. Put the details in props, not in the name. So write Report exported with format: "csv", not Report exported as CSV. Then all exports add up in one place, and you can split them by format.

Events on buttons, links and forms

privatus_event_attrs prints the attributes inside a tag you write yourself:

<button <%= privatus_event_attrs("Report exported", format: "csv", source: "toolbar") %>>Export</button>

It prints:

<button data-privatus-event="Report exported" data-privatus-prop-format="csv" data-privatus-prop-source="toolbar">Export</button>

With the Rails tag helpers, use privatus_event_data. It returns a hash with the full attribute names, so you spread it into the options:

<%= tag.button "Export", **privatus_event_data("Report exported", format: "csv"), class: "btn" %>
<%= button_tag "Export", privatus_event_data("Report exported", format: "csv") %>
<%= link_to "Read the guide", guide_path, **privatus_event_data("Guide opened") %>

On a form, the event is sent when the form is submitted. form_with needs the hash under html::

<%= form_with url: newsletter_path, html: privatus_event_data("Newsletter joined", list: "weekly") do |f| %>

The rules:

  • The name is trimmed and cut to 120 characters. An empty name prints nothing.
  • Prop keys are made lowercase. Anything that is not a letter, a digit or _ becomes _, so "Report Type" becomes report_type. Keys are cut to 64 characters.
  • At most 30 props. Values become text: true, false, numbers as written, lists joined with commas. nil values are left out. Values are cut to 500 characters.
  • Everything is escaped for HTML.
  • The attributes print in every environment, even when the tag is off. They do nothing without the tracker, so your pages look the same in development and production.

Click maps

A click map shows where people click on a page. Give the important parts a name so they stay easy to find, even when the page changes:

<a href="/pricing" <%= privatus_click_attr("pricing.cta") %>>See plans</a>
<%= link_to "See plans", pricing_path, **privatus_click_data("pricing.cta") %>

A name may use letters, digits and _ . : -, up to 100 characters. Anything else prints nothing, and in development the gem writes a warning to the Rails log.

Page props: plan, role and other groups

Page props are sent with every pageview and event on the page. Use them to split your reports by plan, role, app version, feature flag or account tier.

Set them for every page with a lambda. It runs on each request and gets the request:

config.privatus_analytics.props = lambda do |request|
  { plan: Current.account&.plan, role: Current.user&.role, app_version: "2.4" }
end

A plain hash works too. You can also set or change props for one page. They are merged over the configured ones, and nil removes one:

<%= privatus_analytics_tag(props: { section: "billing", role: nil }) %>

The tag then prints a small script before the tracker, with your Content Security Policy nonce when you have one, and points the tracker at it:

<script nonce="...">window.privatusPageProps=function(){return {"plan":"pro","role":"admin"}}</script>
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID" data-props="privatusPageProps"></script>

Keys follow the same rules as event props. Numbers and true or false stay as they are, and lists stay lists (up to 30 items). The props are escaped so they are safe inside a script. The middleware uses the same setting. There it calls your lambda with a Rack::Request, so only use what both kinds of request have, like path, params, session and env.

Never put names, emails, user ids or anything that points to one person in props. Privatus Analytics counts visitors without any id. Put groups (like the plan or the role), not people. The server also removes emails and long ids it finds.

Track from your own JavaScript

For things that are not a click or a submit, call the tracker from your JavaScript, as the JavaScript API docs explain:

privatus.track("Feature used", { feature: "export" })

If your code may run before the tracker has loaded, switch on the queue stub. It prints a tiny script before the tracker that keeps your calls in a list and sends them once the tracker is ready:

config.privatus_analytics.queue_stub = true

or for one layout: <%= privatus_analytics_tag(queue_stub: true) %>. The stub prints even where the tag is off (in development, or with enabled: false). Then only the stub prints: no tracker and no page props. So you can always call privatus.track(...). In development the calls just wait in the queue and are never sent.

See the results

  • Funnels: how many people go from one step to the next.
  • Click maps: where people click.
  • Goals: turn an event into a goal to count conversions.
  • Events: every custom event and its props.

Privacy

The script sets no cookies and stores nothing in the browser. The event, click and props helpers only print HTML attributes and a small script. Keep people out of your props and event props: no names, emails or user ids. See the privacy documentation for what is collected.

If you have a privacy policy, it should say which tools handle your visitors' data. You can paste this:

This website uses Privatus Analytics to count visits. It does not use cookies and does not follow you from one website to another. Your IP address is used for a moment to count visits and is never stored.

Change it to fit your site. The privacy policy generator makes a longer version with your site's name in it.

Development

The tests run in Docker so your local Ruby does not matter:

./run-docker.sh

It runs the Minitest suite against a small dummy Rails app, RuboCop, and gem build.

Contributing

Bug reports and pull requests are welcome at https://github.com/Privatus-Analytics/rails.

License

MIT. See LICENSE.txt. Privatus Analytics is a product of Two Phase LLC, Wyoming, USA.