All Projects → ankane → Ahoy

ankane / Ahoy

Licence: mit
Simple, powerful, first-party analytics for Rails

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Ahoy

Ahoy email
First-party email analytics for Rails
Stars: ✭ 876 (-74.81%)
Mutual labels:  analytics, rails
Osem
Open Source Event Manager. An event management tool tailored to Free and Open Source Software conferences.
Stars: ✭ 649 (-81.34%)
Mutual labels:  events, rails
Baremetrics V1
This was the very first version of Baremetrics from 2013. It's published here for posterity.
Stars: ✭ 73 (-97.9%)
Mutual labels:  analytics, rails
Pivorak Web App
Rails App for PivorakMeetup
Stars: ✭ 64 (-98.16%)
Mutual labels:  events, rails
Ahoy.js
Simple, powerful JavaScript analytics
Stars: ✭ 355 (-89.79%)
Mutual labels:  events, analytics
Timber Ruby
🌲 Great Ruby logging made easy.
Stars: ✭ 154 (-95.57%)
Mutual labels:  events, rails
Atom Wakatime
Atom plugin for automatic time tracking and metrics generated from your programming activity.
Stars: ✭ 303 (-91.29%)
Mutual labels:  analytics
Clickhouse Native Jdbc
ClickHouse Native Protocol JDBC implementation
Stars: ✭ 310 (-91.09%)
Mutual labels:  analytics
Enumerate it
Enumerations for Ruby with some magic powers! 🎩
Stars: ✭ 300 (-91.37%)
Mutual labels:  rails
Dropzonejs Rails
Spice your Rails apps with some Dropzone sugar!
Stars: ✭ 299 (-91.4%)
Mutual labels:  rails
Milia
Easy multi-tenanting for Rails5 (or Rails4) + Devise
Stars: ✭ 326 (-90.63%)
Mutual labels:  rails
Perspective
A data visualization and analytics component, especially well-suited for large and/or streaming datasets.
Stars: ✭ 3,989 (+14.69%)
Mutual labels:  analytics
Delta
An open-source storage layer that brings scalable, ACID transactions to Apache Spark™ and big data workloads.
Stars: ✭ 3,903 (+12.22%)
Mutual labels:  analytics
Awesome Aws Workshops
(Unofficial) curated list of awesome workshops found around in the internet. As we all have been there, finding that workshop that you have just attended shouldn't be hard. The idea is to provide an easy central repository, in a collaborative way.
Stars: ✭ 302 (-91.32%)
Mutual labels:  analytics
Housepricing
HousePricing旨在提供房价的可视化预测,帮助用户更好的评估房产和预测未来的价格(dev)
Stars: ✭ 314 (-90.97%)
Mutual labels:  rails
Html5 validators
A gem/plugin for Rails 3, Rails 4, Rails 5, and Rails 6 that enables client-side validation using ActiveModel + HTML5 Form Validation
Stars: ✭ 302 (-91.32%)
Mutual labels:  rails
Six
Ultra lite authorization library
Stars: ✭ 323 (-90.71%)
Mutual labels:  rails
Plylst
Smart playlists for Spotify! Stop relying on fancy pants algorithms to organize your library and instead build playlists the way you want.
Stars: ✭ 301 (-91.35%)
Mutual labels:  rails
Matomo Sdk Android
SDK for Android to measure your apps with Matomo. Works on Android phones, tablets, Fire TV sticks, and more!
Stars: ✭ 309 (-91.12%)
Mutual labels:  analytics
Ansible Rails
Ansible: Ruby on Rails Server
Stars: ✭ 317 (-90.89%)
Mutual labels:  rails

Ahoy

🔥 Simple, powerful, first-party analytics for Rails

Track visits and events in Ruby, JavaScript, and native apps. Data is stored in your database by default, and you can customize it for any data store as you grow.

Ahoy 4.0 was recently released - see how to upgrade

📮 Check out Ahoy Email for emails and Field Test for A/B testing

🍊 Battle-tested at Instacart

Build Status

Installation

Add this line to your application’s Gemfile:

gem 'ahoy_matey'

And run:

bundle install
rails generate ahoy:install
rails db:migrate

Restart your web server, open a page in your browser, and a visit will be created 🎉

Track your first event from a controller with:

ahoy.track "My first event", language: "Ruby"

JavaScript, Native Apps, & AMP

Enable the API in config/initializers/ahoy.rb:

Ahoy.api = true

And restart your web server.

JavaScript

For Rails 6 / Webpacker, run:

yarn add ahoy.js

And add to app/javascript/packs/application.js:

import ahoy from "ahoy.js"

For Rails 5 / Sprockets, add to app/assets/javascripts/application.js:

//= require ahoy

For Rails 7 / Importmap (experimental), add to config/importmap.rb:

pin "ahoy", to: "ahoy.js"

And add to app/javascript/application.js:

import "ahoy"

Track an event with:

ahoy.track("My second event", {language: "JavaScript"});

Native Apps

Check out Ahoy iOS and Ahoy Android.

Geocoding Setup

To enable geocoding, see the Geocoding section.

GDPR Compliance

Ahoy provides a number of options to help with GDPR compliance. See the GDPR section for more info.

How It Works

Visits

When someone visits your website, Ahoy creates a visit with lots of useful information.

  • traffic source - referrer, referring domain, landing page
  • location - country, region, city, latitude, longitude
  • technology - browser, OS, device type
  • utm parameters - source, medium, term, content, campaign

Use the current_visit method to access it.

Prevent certain Rails actions from creating visits with:

skip_before_action :track_ahoy_visit

This is typically useful for APIs. If your entire Rails app is an API, you can use:

Ahoy.api_only = true

You can also defer visit tracking to JavaScript. This is useful for preventing bots (that aren’t detected by their user agent) and users with cookies disabled from creating a new visit on each request. :when_needed will create visits server-side only when needed by events, and false will disable server-side creation completely, discarding events without a visit.

Ahoy.server_side_visits = :when_needed

Events

Each event has a name and properties. There are several ways to track events.

Ruby

ahoy.track "Viewed book", title: "Hot, Flat, and Crowded"

Track actions automatically with:

class ApplicationController < ActionController::Base
  after_action :track_action

  protected

  def track_action
    ahoy.track "Ran action", request.path_parameters
  end
end

JavaScript

ahoy.track("Viewed book", {title: "The World is Flat"});

See Ahoy.js for a complete list of features.

Native Apps

See the docs for Ahoy iOS and Ahoy Android.

AMP

<head>
  <script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
</head>
<body>
  <%= amp_event "Viewed article", title: "Analytics with Rails" %>
</body>

Associated Models

Say we want to associate orders with visits. Just add visitable to the model.

class Order < ApplicationRecord
  visitable :ahoy_visit
end

When a visitor places an order, the ahoy_visit_id column is automatically set 🎉

See where orders are coming from with simple joins:

Order.joins(:ahoy_visit).group("referring_domain").count
Order.joins(:ahoy_visit).group("city").count
Order.joins(:ahoy_visit).group("device_type").count

Here’s what the migration to add the ahoy_visit_id column should look like:

class AddVisitIdToOrders < ActiveRecord::Migration[6.1]
  def change
    add_column :orders, :ahoy_visit_id, :bigint
  end
end

Customize the column with:

visitable :sign_up_visit

Users

Ahoy automatically attaches the current_user to the visit. With Devise, it attaches the user even if they sign in after the visit starts.

With other authentication frameworks, add this to the end of your sign in method:

ahoy.authenticate(user)

To see the visits for a given user, create an association:

class User < ApplicationRecord
  has_many :visits, class_name: "Ahoy::Visit"
end

And use:

User.find(123).visits

Custom User Method

Use a method besides current_user

Ahoy.user_method = :true_user

or use a proc

Ahoy.user_method = ->(controller) { controller.true_user }

Doorkeeper

To attach the user with Doorkeeper, be sure you have a current_resource_owner method in ApplicationController.

class ApplicationController < ActionController::Base
  private

  def current_resource_owner
    User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

Knock

To attach the user with Knock, either include Knock::Authenticablein ApplicationController:

class ApplicationController < ActionController::API
  include Knock::Authenticable
end

Or include it in Ahoy:

Ahoy::BaseController.include Knock::Authenticable

And use:

Ahoy.user_method = ->(controller) { controller.send(:authenticate_entity, "user") }

Exclusions

Bots are excluded from tracking by default. To include them, use:

Ahoy.track_bots = true

Add your own rules with:

Ahoy.exclude_method = lambda do |controller, request|
  request.ip == "192.168.1.1"
end

Visit Duration

By default, a new visit is created after 4 hours of inactivity. Change this with:

Ahoy.visit_duration = 30.minutes

Cookies

To track visits across multiple subdomains, use:

Ahoy.cookie_domain = :all

Set other cookie options with:

Ahoy.cookie_options = {same_site: :lax}

You can also disable cookies

Token Generation

Ahoy uses random UUIDs for visit and visitor tokens by default, but you can use your own generator like Druuid.

Ahoy.token_generator = -> { Druuid.gen }

Throttling

You can use Rack::Attack to throttle requests to the API.

class Rack::Attack
  throttle("ahoy/ip", limit: 20, period: 1.minute) do |req|
    if req.path.start_with?("/ahoy/")
      req.ip
    end
  end
end

Exceptions

Exceptions are rescued so analytics do not break your app. Ahoy uses Safely to try to report them to a service by default. To customize this, use:

Safely.report_exception_method = ->(e) { Rollbar.error(e) }

Geocoding

Ahoy uses Geocoder for geocoding. We recommend configuring local geocoding or load balancer geocoding so IP addresses are not sent to a 3rd party service. If you do use a 3rd party service and adhere to GDPR, be sure to add it to your subprocessor list. If Ahoy is configured to mask IPs, the masked IP is used (this can reduce accuracy but is better for privacy).

To enable geocoding, add this line to your application’s Gemfile:

gem 'geocoder'

And update config/initializers/ahoy.rb:

Ahoy.geocode = true

Geocoding is performed in a background job so it doesn’t slow down web requests. The default job queue is :ahoy. Change this with:

Ahoy.job_queue = :low_priority

Local Geocoding

For privacy and performance, we recommend geocoding locally. Add this line to your application’s Gemfile:

gem 'maxminddb'

For city-level geocoding, download the GeoLite2 City database and create config/initializers/geocoder.rb with:

Geocoder.configure(
  ip_lookup: :geoip2,
  geoip2: {
    file: "path/to/GeoLite2-City.mmdb"
  }
)

For country-level geocoding, install the geoip-database package. It’s preinstalled on Heroku. For Ubuntu, use:

sudo apt-get install geoip-database

And create config/initializers/geocoder.rb with:

Geocoder.configure(
  ip_lookup: :maxmind_local,
  maxmind_local: {
    file: "/usr/share/GeoIP/GeoIP.dat",
    package: :country
  }
)

Load Balancer Geocoding

Some load balancers can add geocoding information to request headers.

Update config/initializers/ahoy.rb with:

Ahoy.geocode = false

class Ahoy::Store < Ahoy::DatabaseStore
  def track_visit(data)
    data[:country] = request.headers["<country-header>"]
    data[:region] = request.headers["<region-header>"]
    data[:city] = request.headers["<city-header>"]
    super(data)
  end
end

GDPR Compliance

Ahoy provides a number of options to help with GDPR compliance.

Update config/initializers/ahoy.rb with:

class Ahoy::Store < Ahoy::DatabaseStore
  def authenticate(data)
    # disables automatic linking of visits and users
  end
end

Ahoy.mask_ips = true
Ahoy.cookies = false

This:

  • Masks IP addresses
  • Switches from cookies to anonymity sets
  • Disables automatic linking of visits and users

If you use JavaScript tracking, also set:

ahoy.configure({cookies: false});

IP Masking

Ahoy can mask IPs with the same approach Google Analytics uses for IP anonymization. This means:

  • For IPv4, the last octet is set to 0 (8.8.4.4 becomes 8.8.4.0)
  • For IPv6, the last 80 bits are set to zeros (2001:4860:4860:0:0:0:0:8844 becomes 2001:4860:4860::)
Ahoy.mask_ips = true

IPs are masked before geolocation is performed.

To mask previously collected IPs, use:

Ahoy::Visit.find_each do |visit|
  visit.update_column :ip, Ahoy.mask_ip(visit.ip)
end

Anonymity Sets & Cookies

Ahoy can switch from cookies to anonymity sets. Instead of cookies, visitors with the same IP mask and user agent are grouped together in an anonymity set.

Ahoy.cookies = false

Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:

ahoy.configure({cookies: false});

Data Retention

Data should only be retained for as long as it’s needed. Delete older data with:

Ahoy::Visit.where("started_at < ?", 2.years.ago).find_in_batches do |visits|
  visit_ids = visits.map(&:id)
  Ahoy::Event.where(visit_id: visit_ids).delete_all
  Ahoy::Visit.where(id: visit_ids).delete_all
end

You can use Rollup to aggregate important data before you do.

Ahoy::Visit.rollup("Visits", interval: "hour")

Delete data for a specific user with:

user_id = 123
visit_ids = Ahoy::Visit.where(user_id: user_id).pluck(:id)
Ahoy::Event.where(visit_id: visit_ids).delete_all
Ahoy::Visit.where(id: visit_ids).delete_all
Ahoy::Event.where(user_id: user_id).delete_all

Development

Ahoy is built with developers in mind. You can run the following code in your browser’s console.

Force a new visit

ahoy.reset(); // then reload the page

Log messages

ahoy.debug();

Turn off logging

ahoy.debug(false);

Debug API requests in Ruby

Ahoy.quiet = false

Data Stores

Data tracked by Ahoy is sent to your data store. Ahoy ships with a data store that uses your Rails database by default. You can find it in config/initializers/ahoy.rb:

class Ahoy::Store < Ahoy::DatabaseStore
end

There are four events data stores can subscribe to:

class Ahoy::Store < Ahoy::BaseStore
  def track_visit(data)
    # new visit
  end

  def track_event(data)
    # new event
  end

  def geocode(data)
    # visit geocoded
  end

  def authenticate(data)
    # user authenticates
  end
end

Data stores are designed to be highly customizable so you can scale as you grow. Check out examples for Kafka, RabbitMQ, Fluentd, NATS, NSQ, and Amazon Kinesis Firehose.

Track Additional Data

class Ahoy::Store < Ahoy::DatabaseStore
  def track_visit(data)
    data[:accept_language] = request.headers["Accept-Language"]
    super(data)
  end
end

Two useful methods you can use are request and controller.

You can pass additional visit data from JavaScript with:

ahoy.configure({visitParams: {referral_code: 123}});

And use:

class Ahoy::Store < Ahoy::DatabaseStore
  def track_visit(data)
    data[:referral_code] = request.parameters[:referral_code]
    super(data)
  end
end

Use Different Models

class Ahoy::Store < Ahoy::DatabaseStore
  def visit_model
    MyVisit
  end

  def event_model
    MyEvent
  end
end

Explore the Data

Blazer is a great tool for exploring your data.

With ActiveRecord, you can do:

Ahoy::Visit.group(:search_keyword).count
Ahoy::Visit.group(:country).count
Ahoy::Visit.group(:referring_domain).count

Chartkick and Groupdate make it easy to visualize the data.

<%= line_chart Ahoy::Visit.group_by_day(:started_at).count %>

Querying Events

Ahoy provides a few methods on the event model to make querying easier.

To query on both name and properties, you can use:

Ahoy::Event.where_event("Viewed product", product_id: 123).count

Or just query properties with:

Ahoy::Event.where_props(product_id: 123, category: "Books").count

Group by properties with:

Ahoy::Event.group_prop(:product_id, :category).count

Note: MySQL and MariaDB always return string keys (including "null" for nil) for group_prop.

Funnels

It’s easy to create funnels.

viewed_store_ids = Ahoy::Event.where(name: "Viewed store").distinct.pluck(:user_id)
added_item_ids = Ahoy::Event.where(user_id: viewed_store_ids, name: "Added item to cart").distinct.pluck(:user_id)
viewed_checkout_ids = Ahoy::Event.where(user_id: added_item_ids, name: "Viewed checkout").distinct.pluck(:user_id)

The same approach also works with visitor tokens.

Rollups

Improve query performance by pre-aggregating data with Rollup.

Ahoy::Event.where(name: "Viewed store").rollup("Store views")

This is only needed if you have a lot of data.

Forecasting

To forecast future visits and events, check out Prophet.

daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
Prophet.forecast(daily_visits)

Anomaly Detection

To detect anomalies in visits and events, check out AnomalyDetection.rb.

daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
AnomalyDetection.detect(daily_visits, period: 7)

Breakout Detection

To detect breakouts in visits and events, check out Breakout.

daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
Breakout.detect(daily_visits)

Recommendations

To make recommendations based on events, check out Disco.

Tutorials

API Spec

Visits

Generate visit and visitor tokens as UUIDs, and include these values in the Ahoy-Visit and Ahoy-Visitor headers with all requests.

Send a POST request to /ahoy/visits with Content-Type: application/json and a body like:

{
  "visit_token": "<visit-token>",
  "visitor_token": "<visitor-token>",
  "platform": "iOS",
  "app_version": "1.0.0",
  "os_version": "11.2.6"
}

After 4 hours of inactivity, create another visit (use the same visitor token).

Events

Send a POST request to /ahoy/events with Content-Type: application/json and a body like:

{
  "visit_token": "<visit-token>",
  "visitor_token": "<visitor-token>",
  "events": [
    {
      "id": "<optional-random-id>",
      "name": "Viewed item",
      "properties": {
        "item_id": 123
      },
      "time": "2018-01-01T00:00:00-07:00"
    }
  ]
}

Upgrading

4.0

There are two notable changes to geocoding:

  1. Geocoding is now disabled by default (this was already the case for new installations with 3.2.0+). Check out the instructions for how to enable it.

  2. The geocoder gem is now an optional dependency. To use geocoding, add it to your Gemfile:

gem 'geocoder'

Also, check out the upgrade notes for Ahoy.js.

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone https://github.com/ankane/ahoy.git
cd ahoy
bundle install
bundle exec rake test

To test query methods, use:

# Postgres
createdb ahoy_test
bundle exec rake test:query_methods:postgresql

# SQLite
bundle exec rake test:query_methods:sqlite

# MySQL and MariaDB
mysqladmin create ahoy_test
bundle exec rake test:query_methods:mysql

# MongoDB
bundle exec rake test:query_methods:mongoid
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].