Giter Site home page Giter Site logo

ahoy's Introduction

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 5.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 Importmap (Rails 7 default), add to config/importmap.rb:

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

And add to app/javascript/application.js:

import "ahoy"

For Webpacker (Rails 6 default), run:

yarn add ahoy.js

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

import ahoy from "ahoy.js"

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

//= require 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 AddAhoyVisitToOrders < ActiveRecord::Migration[7.1]
  def change
    add_reference :orders, :ahoy_visit
  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

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

Visitor Duration

By default, a new visitor_token is generated after 2 years. Change this with:

Ahoy.visitor_duration = 30.days

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 ULID.

Ahoy.token_generator = -> { ULID.generate }

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.

For city-level geocoding, download the GeoLite2 City database.

Add this line to your application’s Gemfile:

gem "maxminddb"

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

Add this line to your application’s Gemfile:

gem "geoip"

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 = :none

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 = :none

Note: If Ahoy was installed before v5, add an index before making this change.

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 Active Record, 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

5.0

Visits now expire with anonymity sets. If using Ahoy.cookies = false, a new index is needed.

For Active Record, create a migration with:

add_index :ahoy_visits, [:visitor_token, :started_at]

For Mongoid, set:

class Ahoy::Visit
  index({visitor_token: 1, started_at: 1})
end

Create the index before upgrading, and set:

Ahoy.cookies = :none

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 different adapters, use:

ADAPTER=postgresql bundle exec rake test
ADAPTER=mysql2 bundle exec rake test
ADAPTER=mongoid bundle exec rake test

ahoy's People

Contributors

ahoward avatar alexandrapersea avatar ankane avatar atul9 avatar bahanix avatar beathyate avatar byzg avatar cbillen avatar cbothner avatar chrislo avatar codebreach avatar enomotodev avatar jughead avatar monfresh avatar motephyr avatar moxie avatar mreigen avatar ndbroadbent avatar nilcolor avatar oguzbilgic avatar pazaricha avatar ryanharkins avatar sbeckeriv avatar seanhussey avatar seanlinsley avatar wa0x6e avatar westoque avatar zachhweed avatar zencocoon avatar zspencer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ahoy's Issues

Unique Violation events primary key

I'm seeing these errors pop up. Am I misusing this library somehow?

PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "ahoy_events_pkey"
DETAIL: Key (id)=(8731dd56-0a9f-4722-9229-4a843c144b68) already exists.

I initialize ahoy javascript like this:

$(document).ready(ahoy.trackAll)

The only custom code I have is that in the action when users sign up I call this:

 ahoy.track "Signed Up"

I'm using turbolinks if that has anything to do with it.

Track Visit per controller action?

Hi,

Is there any simple way to track visits per controller action? Basically I want to create Visit manually for particular model on some specific controller actions.

For example, I have a shop and shop has many items and I want to track visits when anybody visit shop#show action and item#show action and log this Visit as a visit for a Shop model.

Something like:

# ShopsController
def show
  @shop = Shop.find(params[:id])
  Visit.create(@shop, current_visit)
end
# ItemsController
def show
  @item = @shop.items.find(params[:id])
  Visit.create(@shop, current_visit)
end

# or

def show
  @item = @shop.items.find(params[:id])
  current_visit.create(@shop)
end

#etc

A/B testing - how to detect 1st time time visitor?

Hi,
I am using Ahoy in our project and was about to add A/B testing framework when it occurred to me I could just start a new test when a new user comes to the site and just assign them the right variant and be able to compare both variants with all the event we track.

I am going to modify track_visit to see if this is a first visit of the visitor, but that requires one extra lookup when I could somehow detect if the visitor_id was just generated or not from ahoy.

So I studied the gem a bit, but I couldn't find the right place to tap into quickly enough. If you could point me to the right place that would be most helpful.

Thanks!

Mongoid support

Hey,

How do I set this up in a Mongoid project? Is there any mongo port available?

Thanks
Vasu

Visit does not get updated if user signs in

According to the docs:

With Devise, it will attach the user even if he / she signs in
after the visit starts.

However this does not seem to be the case. I had to manually add in the custom option:

if current_visit and !current_visit.user
  current_visit.user = current_user
  current_visit.save!
end

Rails 4.1
Devise 3.2.4
Ruby 2.1.1
Ahoy 0.1.3

Also, if tracking conversion / sign in rates, what would be the best route? Use the new ahoy_events gem and attached to the current_visit?

add column to evet table

Hi,
How do I add a column to ahoy_events table?
I want to save another attribute to the table in order to optimize the SQL script, I don't want to add it as an option into 'properties' because if I use this option I would have to load all rows related with a specific user and then apply a criteria and bla bla bla..

What would be my solution?
I added a column into ahoy_events table, but after the rake db:migrate it launches an error in Ahoy::Store. I don't know why, I think I missing something.

Sorry if this is not the place to ask an answer.
Thanks
onthebeachh

How do I use ahoy in tests ?

I'm currently hacking together a dashboard for my app to list all the ahoy events which are in the DB. I was thinking there would be some module I could easily include in my test so I could use "ahoy.track" inside my tests to setup some seed data for the test. I was hoping to find it in ahoy's test suite but I was surprised to find no tests 😄 (but it's still a kickass library and very useful, thank you) . So is there a module I can include to get hold of the controller helper methods ?

Untracked Visits

When using Firefox on my desktop or Safari on my iOS 8 there is no record created in the visits database. Whereas using Chrome on desktop or chrome on iOS 8 works fine. On Firefox and iOS Safari events are created in the database and there is a visit_id assigned but that visit ID doesn't exist in the visits table.

Can `Ahoy::Store` be put in `app/models` vs. `config/initializers`?

It seems easier during development of an application to have the class defined in models, so that changes to the class are immediate, versus having to reload the rails environment. But I have not looked far enough into the implementation to see if moving that file would break something.

How can I avoid tracking certain types of users?

Hi,
I am using Ahoy successfully, but I get errors when I sign in to Active Admin, for which I use different user model - AdminUser.

ActiveRecord throws:
User(#2222278420) expected, got AdminUser(#2194620560)

From the exception I can see that Ahoy listens for signups:

Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
  request = ActionDispatch::Request.new(auth.env)
  ahoy = Ahoy::Tracker.new(request: request)
  ahoy.authenticate(user)
end

Is there a way I can avoid tracking visits of admins altogether, or just avoid the issue of setting admin's user to the visit?

Thanks!

Ahoy with caching

I noticed the Ahoy ruby gem sets a cookie using the Set-Cookie header. This makes it harder to cache pages using varnish etc. It seems that Ahoy.js has a set_cookie method - is it possible to configure Ahoy to set cookies using javascript instead of headers, in a similar way to Google Analytics, for example?

Add or Extract data to properties in Events

I'm using a slug for my URL, but would also like to capture the ID. I've narrowed it down to two routes I could take, but I'm not sure which is best.

  1. Track an additional property and pull this out of the JSON? This seems far more difficult and can't figure out how to grab one of the properties like under Explore the Data or Funnels.

For instance, I added <%= ahoy.track "Viewed Model", title: "#{@model.id}" %> to my template and that works, but can I pull out the id easily for reports? That's where I'm stuck on this route.

  1. Add an additional column under ahoy_events that references the ID. This seems far simpler? Is this correct route to take?

Still learning Ruby so sorry if this one is obvious.

& great work on all of your awesome plugins, @ankane! Loving this, chartkick and groupdate. Powerful stuff.

Report User Interface

Are there already any approaches to build a generic mixpanel/kissmetrics like UI for creating graphs, funnels etc on top of ahoy?

Duplicate visit_token

Believe it or not, I'm getting multiple daily collisions on visit_token (which should be unique).

ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry 'oTDxqISK2Aa49zLRRTdta5qaIeNidtcC' for key 'index_visits_on_visit_token': INSERT INTO visits (....

I wouldn't be surprised if it's some badly behaving browser causing this:
https://github.com/ankane/ahoy/blob/master/vendor/assets/javascripts/ahoy.js

// http://stackoverflow.com/a/2117523/1177228
function generateId() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}

But just wanted to share this with you first before I dig further in case it is a known issue.

ID-specific filtering?

Hi,

I am looking to use Ahoy for analytics, it seems like a brilliant gem and I am certainly excited to use it!

I'm wondering if it would be at all possible for me to somehow pass in a custom parameter for each view/event which specifies which current blog category it is on. So that I can create an analytics platform for each individual category while having a master platform which shows global views/events without category-specific filtering?

Any help with this would be insanely appreciated!

Tracking multiple page views

Have you thought about tracking multiple page views like say Google Analytics? Currently it looks to track the initial visit, but I think there could be a lot of power using this as a backend to tracking individual content unique/total page views.

Geocoding suddenly stopped working

In the middle of the day yesterday geocoding stopped working for me. My last geocoded visit was at 2014-06-12 13:23:49. I was on 0.2.1 at the time. Later that night I upgraded to 0.3.1 but the geocoding is still not working. After checking a similar issue I tried the following:

v = Visit.last
Geocoder.search(v.ip).first
Geocoding API connection refused.
=> nil

I'm assuming that's an API key issue? When I get a chance I'm going to try putting in some Geocoder config to set some API keys. I'm thinking this is a Geocoder issue and not necessarily an ahoy issue but I thought I'd mention this just in case. Is ahoy using geocoding API keys? If not, perhaps it should be mentioned in the README that ahoy users set their own keys.

ahoy + Geocoder

Looking at the sourcecode ahoy / lib / ahoy / model.rb I see a method called set_location which appears calls GeoCoder directly.

Is there anything special I have to do besides include the Geocode gem in my project to use geocoding by ip ?

I have added latitude/longitude fields to the visit model but not sure these fields are needed.

Any help would be greately appreciated!

Using the ActiveRecord adapter with Rails 4.1

Hi, I've followed the instructions from the README to enable ahoy using the built-in ActiveRecordStore but cannot get events or visits to work. The id is always NULL and the insert fails. The project is on Rails 4.1.4 on Postgres. It's all configured using the default generators and no extra configuration. Any ideas?

PG::NotNullViolation: ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, bc0ee5ec-2c25-4fad-801f-5160152c1cfc, 127.0.0.1, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.3..., null, http://localhost:4321/, null, null, null, Chrome, Mac OS X, Desktop, Reserved, null, null, null, null, null, null, null, Web, null, null, 2014-07-26 03:40:27.652412).
: INSERT INTO "visits" ("browser", "country", "device_type", "ip", "landing_page", "os", "platform", "started_at", "user_agent", "visitor_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)

PG::NotNullViolation - ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, bc0ee5ec-2c25-4fad-801f-5160152c1cfc, 127.0.0.1, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.3..., null, http://localhost:4321/, null, null, null, Chrome, Mac OS X, Desktop, Reserved, null, null, null, null, null, null, null, Web, null, null, 2014-07-26 03:40:27.652412).
:
  activerecord (4.1.4) lib/active_record/connection_adapters/postgresql_adapter.rb:834:in `block in exec_cache'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract_adapter.rb:373:in `block in log'
  activesupport (4.1.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract_adapter.rb:367:in `log'
  activerecord (4.1.4) lib/active_record/connection_adapters/postgresql_adapter.rb:831:in `exec_cache'
  activerecord (4.1.4) lib/active_record/connection_adapters/postgresql/database_statements.rb:138:in `exec_query'
  activerecord (4.1.4) lib/active_record/connection_adapters/postgresql/database_statements.rb:177:in `exec_insert'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/database_statements.rb:95:in `insert'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `insert'
  activerecord (4.1.4) lib/active_record/relation.rb:64:in `insert'
  activerecord (4.1.4) lib/active_record/persistence.rb:502:in `_create_record'
  activerecord (4.1.4) lib/active_record/attribute_methods/dirty.rb:87:in `_create_record'
  activerecord (4.1.4) lib/active_record/callbacks.rb:306:in `block in _create_record'
  activesupport (4.1.4) lib/active_support/callbacks.rb:113:in `call'
  activesupport (4.1.4) lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
  activesupport (4.1.4) lib/active_support/callbacks.rb:86:in `run_callbacks'
  activerecord (4.1.4) lib/active_record/callbacks.rb:306:in `_create_record'
  activerecord (4.1.4) lib/active_record/timestamp.rb:57:in `_create_record'
  activerecord (4.1.4) lib/active_record/persistence.rb:482:in `create_or_update'
  activerecord (4.1.4) lib/active_record/callbacks.rb:302:in `block in create_or_update'
  activesupport (4.1.4) lib/active_support/callbacks.rb:113:in `call'
  activesupport (4.1.4) lib/active_support/callbacks.rb:166:in `block in halting'
  activesupport (4.1.4) lib/active_support/callbacks.rb:166:in `block in halting'
  activesupport (4.1.4) lib/active_support/callbacks.rb:86:in `run_callbacks'
  activerecord (4.1.4) lib/active_record/callbacks.rb:302:in `create_or_update'
  activerecord (4.1.4) lib/active_record/persistence.rb:125:in `save!'
  activerecord (4.1.4) lib/active_record/validations.rb:57:in `save!'
  activerecord (4.1.4) lib/active_record/attribute_methods/dirty.rb:29:in `save!'
  activerecord (4.1.4) lib/active_record/transactions.rb:273:in `block in save!'
  activerecord (4.1.4) lib/active_record/transactions.rb:329:in `block in with_transaction_returning_status'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `block in transaction'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/database_statements.rb:209:in `within_new_transaction'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `transaction'
  activerecord (4.1.4) lib/active_record/transactions.rb:208:in `transaction'
  activerecord (4.1.4) lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
  activerecord (4.1.4) lib/active_record/transactions.rb:273:in `save!'
   () Users/mike/.rvm/gems/ruby-2.1.2@uglst/bundler/gems/ahoy-2ebf307c7aa6/lib/ahoy/stores/active_record_store.rb:21:in `track_visit'
   () Users/mike/.rvm/gems/ruby-2.1.2@uglst/bundler/gems/ahoy-2ebf307c7aa6/lib/ahoy/tracker.rb:35:in `track_visit'
   () Users/mike/.rvm/gems/ruby-2.1.2@uglst/bundler/gems/ahoy-2ebf307c7aa6/app/controllers/ahoy/visits_controller.rb:5:in `create'
  actionpack (4.1.4) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  actionpack (4.1.4) lib/abstract_controller/base.rb:189:in `process_action'
  actionpack (4.1.4) lib/action_controller/metal/rendering.rb:10:in `process_action'
  actionpack (4.1.4) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
  activesupport (4.1.4) lib/active_support/callbacks.rb:82:in `run_callbacks'
  actionpack (4.1.4) lib/abstract_controller/callbacks.rb:19:in `process_action'
  actionpack (4.1.4) lib/action_controller/metal/rescue.rb:29:in `process_action'
  actionpack (4.1.4) lib/action_controller/metal/instrumentation.rb:31:in `block in process_action'
  activesupport (4.1.4) lib/active_support/notifications.rb:159:in `block in instrument'
  activesupport (4.1.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (4.1.4) lib/active_support/notifications.rb:159:in `instrument'
  actionpack (4.1.4) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
  actionpack (4.1.4) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
  activerecord (4.1.4) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (4.1.4) lib/abstract_controller/base.rb:136:in `process'
  actionview (4.1.4) lib/action_view/rendering.rb:30:in `process'
  actionpack (4.1.4) lib/action_controller/metal.rb:196:in `dispatch'
  actionpack (4.1.4) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
  actionpack (4.1.4) lib/action_controller/metal.rb:232:in `block in action'
  actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:82:in `dispatch'
  actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:50:in `call'
  actionpack (4.1.4) lib/action_dispatch/journey/router.rb:71:in `block in call'
  actionpack (4.1.4) lib/action_dispatch/journey/router.rb:59:in `call'
  actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:678:in `call'
  railties (4.1.4) lib/rails/engine.rb:514:in `call'
  railties (4.1.4) lib/rails/railtie.rb:194:in `method_missing'
  actionpack (4.1.4) lib/action_dispatch/journey/router.rb:71:in `block in call'
  actionpack (4.1.4) lib/action_dispatch/journey/router.rb:59:in `call'
  actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:678:in `call'
  oink (0.10.1) lib/oink/middleware.rb:17:in `call'
  meta_request (0.3.3) lib/meta_request/middlewares/app_request_handler.rb:13:in `call'
  rack-contrib (1.1.0) lib/rack/contrib/response_headers.rb:17:in `call'
  meta_request (0.3.3) lib/meta_request/middlewares/headers.rb:16:in `call'
  meta_request (0.3.3) lib/meta_request/middlewares/meta_request_handler.rb:13:in `call'
  bullet (4.13.0) lib/bullet/rack.rb:12:in `call'
  rack-livereload (0.3.15) lib/rack/livereload.rb:23:in `_call'
  rack-livereload (0.3.15) lib/rack/livereload.rb:14:in `call'
  warden (1.2.3) lib/warden/manager.rb:35:in `block in call'
  warden (1.2.3) lib/warden/manager.rb:34:in `call'
  rack (1.5.2) lib/rack/etag.rb:23:in `call'
  rack (1.5.2) lib/rack/conditionalget.rb:35:in `call'
  rack (1.5.2) lib/rack/head.rb:11:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/flash.rb:254:in `call'
  rack (1.5.2) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.5.2) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.1.4) lib/active_record/query_cache.rb:36:in `call'
  activerecord (4.1.4) lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'
  activerecord (4.1.4) lib/active_record/migration.rb:380:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.1.4) lib/active_support/callbacks.rb:82:in `run_callbacks'
  actionpack (4.1.4) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/remote_ip.rb:76:in `call'
  better_errors (1.1.0) lib/better_errors/middleware.rb:84:in `protected_app_call'
  better_errors (1.1.0) lib/better_errors/middleware.rb:79:in `better_errors_call'
  better_errors (1.1.0) lib/better_errors/middleware.rb:56:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  railties (4.1.4) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.1.4) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.1.4) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.1.4) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.1.4) lib/rails/rack/logger.rb:20:in `call'
   () Users/mike/.rvm/gems/ruby-2.1.2@uglst/bundler/gems/ahoy-2ebf307c7aa6/lib/ahoy/engine.rb:20:in `call_with_quiet_ahoy'
  quiet_assets (1.0.3) lib/quiet_assets.rb:23:in `call_with_quiet_assets'
  request_store (1.0.7) lib/request_store/middleware.rb:8:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
  rack (1.5.2) lib/rack/runtime.rb:17:in `call'
  activesupport (4.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'
  rack (1.5.2) lib/rack/lock.rb:17:in `call'
  actionpack (4.1.4) lib/action_dispatch/middleware/static.rb:64:in `call'
  heroku_rails_deflate (1.0.3) lib/heroku_rails_deflate/serve_zipped_assets.rb:58:in `call'
  rack (1.5.2) lib/rack/deflater.rb:25:in `call'
  rack (1.5.2) lib/rack/sendfile.rb:112:in `call'
  railties (4.1.4) lib/rails/engine.rb:514:in `call'
  railties (4.1.4) lib/rails/application.rb:144:in `call'
  rack (1.5.2) lib/rack/lock.rb:17:in `call'
  rack (1.5.2) lib/rack/content_length.rb:14:in `call'
  rack (1.5.2) lib/rack/handler/webrick.rb:60:in `service'
  /Users/mike/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/httpserver.rb:138:in `service'
  /Users/mike/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/httpserver.rb:94:in `run'
  /Users/mike/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/server.rb:295:in `block in start_thread'

Doorkeeper and Devise

If using in an app that has both Devise current_user and Doorkeeper current_resource_owner methods, maybe one for web interface and one for API, how would ahoy know which method to use?

Would it be set using the example from the readme:

Ahoy.user_method = proc {|controller| controller.current_user }

... where you would check to see if it was an API controller?

Multiple Devise models

I just realized that Ahoy broke every other devise model (besides current_user) when attempting to login because it's always looking for user_id, instead of admin_id (current_admin) for instance. I'm thinking I can customize the user based on the README, but everything I'm trying isn't working. Any help?

Thanks.

Ahoy in Rails 3

Hi there,
I just added ahoy_matey gem in my Rails 3 project and I am struggling a bit.

The postgre manual didn't work for me so I used the uuid gem and now I am fine.

However, tracking view doesn't for for me yet as the /ahoy/event route falls into catchall 404 route.

I don't see any route for ahoy, nor do I see a controller. Did I miss anything?

inject_into_class "app/models/visit.rb fails if used in Rails Engine

  def inject_ahoy_content
    inject_into_class "app/models/visit.rb", "Visit", "  ahoy_visit\n"
  end

Error:
$ rails generate ahoy:install
identical db/migrate/20140427150300_install_ahoy.rb
identical app/models/marketing/visit.rb
invoke rspec
identical spec/models/marketing/visit_spec.rb
invoke factory_girl
identical spec/factories/marketing_visits.rb
insert app/models/visit.rb
/Users/xxxx/.rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/thor-0.19.1/lib/thor/actions/inject_into_file.rb:98:in `binread': No such file or directory @ rb_sysopen - /Users/efries/apps/getmail/apps/marketing/app/models/visit.rb (Errno::ENOENT)

Hook to store additional fields

Have you started working on this feature yet? I can help with this, since I need it for my own project. Any particular way you were thinking of implementing it?

p.s. AMAZING job with this. It just made my life so much easier.

Logging vs. Database

Events are no longer inserted in the database, but I do see them in events.log. It seems to have changed when I ran "rails generate ahoy:stores:log". I expected error logging to go there, not to completely change where events went.

How can I undo this and put them back in the database?

Async LocationDeckhand

Hi, @ankane
When using v0.3x, I override the set_location callback, and add another after_create callback to enqueue the real set_location(the networ accessing will add 200-500ms delay in our country :( )
since we got v1.x, using Deckhand to set the properties, is it possible to have an async deckhand to work which need network accessing?

search_keyword is always NULL

No entry in my visits table has a search_keyword. Even if the referer column looks like this:
http://www.bing.com/search?q=restaurant+m%C3%BCnchen&src=IE-SearchBox&FORM=IESR02

When I parse the referer manually with RefererParser::Parser, I get this result:
{:known=>true, :uri=>"http://www.bing.com/search?q=restaurant+m%C3%BCnchen&src=IE-SearchBox&FORM=IESR02", :source=>"Bing", :medium=>"search", :domain=>"bing.com", :term=>"restaurant münchen"}

I use this (custom) track_visit method:

  def track_visit options
    super do |visit|
      visit.site_id = get_site_by_host(request.host).id
    end
  end

I don't know if my custom method could be a problem.

Add PostgreSQL subscriber

Take advantage of PostgreSQL features like JSON to make properties queryable.

Visit

  • id - UUID (primary key)
  • visitor_id - UUID
  • other fields, except for visit_token and visitor_token

Event

  • id - UUID (primary key)
  • visit_id - UUID
  • user_id - integer
  • user_type - optional and disabled by default
  • name
  • properties - JSON
  • time

Can the locations deckhand be disabled?

I would like to use my own implementation of IP geocoding, using a subscription database and tracking different fields. I can easily add that by implementing the track_visit method; however, the default geo location will take place anyway before it gets there - could that be avoided?

Screen Sizes

Browser height and width sent from Visit should be stored.

Dynamically enable/disable Geocode

FreeGeoIP works great, but up until a point. At that point (or depending on the internet weather) FreeGeoIP can slow down a lot.

  1. It would be great if there could be something like Ahoy.geocoder_enabled = true in ahoy.rb.

  2. It would be even better if there was a way to reconfigure Ahoy during runtime to stop geocoding when FreeGeoIP got slow.

1 seems totally doable. What do you think would make 2 more approachable?

Also when being front-ended by cloudflare, it is less important to geocode since cloudflare already provides the country as an HTTP header.

Email analytics

Most likely a separate gem, but this would:

  1. Create Ahoy::Message (user, subject, opened_at, clicked_at, unique_id / token)
  2. Add UTM parameters to links (HTML only)
  3. Add open pixel (HTML only)

One step further, create an engine to manage marketing lists, unsubscribes, etc.

  • Ahoy::List - id, name, active (created dynamically as emails are tagged)
  • Ahoy::OptOut - list (or all lists), user, reason (unsubscribe, bounce)

Authlogic gives "You must activate the Authlogic::Session::Base.controller" error

Great gem! Many thanks. I'm trying to get it working with ActiveRecord and Authlogic.

I've followed the readme fairly closely and when trying to create a visit from javascript console via ahoy.trackView(), I get the following Authlogic error:

You must activate the Authlogic::Session::Base.controller with a controller object before creating objects

I've tried while logged in and logged out and am using standard "current_user".

Any ideas why this might be? Perhaps something not configured properly on my end?

Rails 3 compatibility?

Tried to integrate ahoy 1.0.1 into a Rails 3 application running ruby 2.0.0 and connecting to Postgres 9.3 and ran into some issues, so I thought I'd ask whether these are known issues or incompatibilities.

For a start, I believe I managed to deal with the errors raised in migrations about the json & uuid tupes. Adding "serialize" to the event model fixed saving the event record. I managed to successfully record visits and events, but randomly there are exceptions and data is not saved. This is what I see in the rails log:

Started POST "/ahoy/visits" for 127.0.0.1 at 2014-07-09 15:47:58 +0100
Processing by Ahoy::VisitsController#create as JSON
  Parameters: {"visit_token"=>"acb62091-d764-46b9-8816-390e215569f4", "visitor_token"=>"19d8485b-52d5-4f63-8c2c-b5817e2619d2", "platform"=>"Web", "landing_page"=>"http://localhost:3000/species#/taxon_concepts/12088/legal", "screen_width"=>"1920", "screen_height"=>"1080"}
DEPRECATION WARNING: You're trying to create an attribute `'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc. (called from write_attribute at /home/agnessa/.rvm/gems/ruby-2.0.0-p353/gems/activerecord-3.2.18/lib/active_record/attribute_methods/write.rb:32)
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.4ms)  INSERT INTO "ahoy_visits" ("browser", "city", "country", "device_type", "id", "ip", "landing_page", "os", "referrer", "referring_domain", "region", "search_keyword", "started_at", "user_agent", "user_id", "utm_campaign", "utm_content", "utm_medium", "utm_source", "utm_term", "visitor_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)  [["browser", "Firefox"], ["city", nil], ["country", "Reserved"], ["device_type", "Desktop"], ["id", nil], ["ip", "127.0.0.1"], ["landing_page", "http://localhost:3000/species#/taxon_concepts/12088/legal"], ["os", "Ubuntu"], ["referrer", nil], ["referring_domain", nil], ["region", nil], ["search_keyword", nil], ["started_at", Wed, 09 Jul 2014 14:47:58 UTC +00:00], ["user_agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0"], ["user_id", 3], ["utm_campaign", nil], ["utm_content", nil], ["utm_medium", nil], ["utm_source", nil], ["utm_term", nil], ["visitor_id", "19d8485b-52d5-4f63-8c2c-b5817e2619d2"]]
   (0.1ms)  ROLLBACK
Completed 500 Internal Server Error in 31.7ms

NoMethodError - undefined method `' for #

If it helps this is how I integrated ahoy: unepwcmc/SAPI@305cc41

Would be great to get this working! Thank you for the great work.

How to store visiting and event data to the database?

Hello, I am very new to ahoy. First of all, thank you for this awesome gem.

I install this gem following the readme instruction. The only thing i customized is the type of user_id in the table schema, because I use user_id as uuid not integer. After this tweak, it seems everything goes well. No error in migration and so on.

The problem I encountered is there is no records in Visit and Ahoy::Event after some page visiting and ahoy.trackAll(). I can find something happen In the log files, but NO records in the database.

What am I missing?

Duplicate visit_token - ahoy_matey (1.0.2)

FYI, still getting this type of error after moving to v1.0.2

Mysql2::Error: Duplicate entry 'ecf37acb-ef0d-4f5f-b536-093c3a899868' for key 'index_visits_on_visit_token': INSERT INTO visits (browser, cf_connecting_ip, country, created_at, device_type, ip, landing_page, os, platform, user_agent, utm_campaign, utm_medium, utm_source, visit_token, visitor_token) VALUES ('IE', '1.2.3.4', 'US', '2014-07-31 06:26:27', 'Desktop', '1.2.3.4', 'https://example.com/';, 'Windows 7', 'Web', 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko', 'xx', 'yy-under', 'zz', 'ecf37acb-ef0d-4f5f-b536-093c3a899868', 'd57022c6-7748-490d-825f-0cbda451b845')

Customizable mount path

First, thank you for such a great gem and its JS cousin!

Second, is there any way to customize the engine's mount path (e.g. /ahoy => /tracking)? Or rather, is there way to disable auto-mounting? This is only a very minor problem though. 😄

activeuuid broken in Rails 4.2

See this issue. I know this is theirs to fix, but it directly affects Ahoy users with mysql or sqlite as a backend, so it kind of breaks your Rails 4.2 compatiblity too.

how to store multiple visits for page

i want to track how many visitors(i.e visits) came a specific page.
i am using ruby 2.1,rails 4.1 and mysql
code:
i wrote following line in action
ahoy.track_visit();

console error:

INSERT INTO visits (browser, country, device_type, id, ip, landing_page, os, started_at, user_agent, user_id, visitable_id, visitable_type, visitor_id) VALUES ('Chrome', 'Reserved', 'Desktop', x'310766d384e6462a956a06be27a575ad', '127.0.0.1', 'http://localhost:3000/posts/2', 'Linux', '2014-07-09 12:27:30', 'Mozilla/5.0 (X11; Linux
x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36', 1, 2, 'Post', x'898a656ec3fd4651bd063cc54f4e9030')
Mysql2::Error: Duplicate entry '1\x07f\xD3\x84\xE6F*\x95j\x06\xBE'\xA5u\xAD' for key 'PRIMARY': INSERT INTO visits (browser, country, device_type, id, ip, landing_page, os, started_at, user_agent, user_id, visitable_id, visitable_type, visitor_id) VALUES ('Chrome', 'Reserved', 'Desktop', x'310766d384e6462a956a06be27a575ad', '127.0.0.1', 'http://localhost:3000/posts/2', 'Linux', '2014-07-09 12:27:30', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36', 1, 2, 'Post', x'898a656ec3fd4651bd063cc54f4e9030')
(0.1ms) ROLLBACK

Problems with MySQL visit_id

Hi,

I branched my code base, installed the gem and then did the migration. Everything was fine. So I reverted to master, forgot about it (I never could get Ahoy to actually track anything; never could find out why so I moved on) and then a few days later after spinning up a bunch of new models, I did a rake db:test:prepare to get

rake db:test:prepare
in LoadError rescue statement
rake aborted!
Mysql2::Error: BLOB/TEXT column 'visit_id' used in key specification without a key length: CREATE INDEX index_ahoy_events_on_visit_id ON ahoy_events (visit_id)

Tasks: TOP => db:schema:load
(See full trace by running task with --trace)

So I fired off a:

drop index index_ahoy_events_on_visit_id on ahoy_events;

and then I thought that this:

CREATE INDEX index_ahoy_events_on_visit_id ON ahoy_events visit_id(16) ;

should fix things nicely but, alas, it did not. I don't generally use binary keys. Any suggestions would be appreciated. Despite the issues, I still like what you're doing and I'd really like to see Ahoy actually work.

Best and thank you for your work.

Using ahoy inside an engine

Is there anything special to do if I'm using ahoy inside an engine? Our app is split up into multiple pieces similar to https://github.com/taskrabbit/rails_engines_example .

The problem is when the javascript calls back to http://localhost:3000/ahoy/events there is a 404.

ActionController::RoutingError - uninitialized constant Ahoy::ApplicationController:
  actionpack (4.1.5) lib/action_dispatch/routing/route_set.rb:71:in `rescue in controller'
  actionpack (4.1.5) lib/action_dispatch/routing/route_set.rb:66:in `controller'
  actionpack (4.1.5) lib/action_dispatch/routing/route_set.rb:46:in `call'
  actionpack (4.1.5) lib/action_dispatch/journey/router.rb:71:in `block in call'
  actionpack (4.1.5) lib/action_dispatch/journey/router.rb:59:in `call'
  actionpack (4.1.5) lib/action_dispatch/routing/route_set.rb:678:in `call'
  railties (4.1.5) lib/rails/engine.rb:514:in `call'
  railties (4.1.5) lib/rails/railtie.rb:194:in `method_missing'
  actionpack (4.1.5) lib/action_dispatch/journey/router.rb:71:in `block in call'
  actionpack (4.1.5) lib/action_dispatch/journey/router.rb:59:in `call'
  actionpack (4.1.5) lib/action_dispatch/routing/route_set.rb:678:in `call'
  rack (1.5.2) lib/rack/etag.rb:23:in `call'
  rack (1.5.2) lib/rack/conditionalget.rb:35:in `call'
  rack (1.5.2) lib/rack/head.rb:11:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/flash.rb:254:in `call'
  rack (1.5.2) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.5.2) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.1.5) lib/active_record/query_cache.rb:36:in `call'
  activerecord (4.1.5) lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'
  activerecord (4.1.5) lib/active_record/migration.rb:380:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.1.5) lib/active_support/callbacks.rb:82:in `run_callbacks'
  actionpack (4.1.5) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/remote_ip.rb:76:in `call'
  better_errors (2.0.0) lib/better_errors/middleware.rb:84:in `protected_app_call'
  better_errors (2.0.0) lib/better_errors/middleware.rb:79:in `better_errors_call'
  better_errors (2.0.0) lib/better_errors/middleware.rb:57:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  railties (4.1.5) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.1.5) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.1.5) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.1.5) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.1.5) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.1.5) lib/rails/rack/logger.rb:20:in `call'
  ahoy_matey (1.1.0) lib/ahoy/engine.rb:20:in `call_with_quiet_ahoy'
  request_store (1.1.0) lib/request_store/middleware.rb:8:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
  rack (1.5.2) lib/rack/runtime.rb:17:in `call'
  activesupport (4.1.5) lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'
  rack (1.5.2) lib/rack/lock.rb:17:in `call'
  actionpack (4.1.5) lib/action_dispatch/middleware/static.rb:64:in `call'
  rack (1.5.2) lib/rack/sendfile.rb:112:in `call'
  railties (4.1.5) lib/rails/engine.rb:514:in `call'
  railties (4.1.5) lib/rails/application.rb:144:in `call'
  rack (1.5.2) lib/rack/lock.rb:17:in `call'
  rack (1.5.2) lib/rack/content_length.rb:14:in `call'
  rack (1.5.2) lib/rack/handler/webrick.rb:60:in `service'
  /Users/me/.rbenv/versions/2.1.3/lib/ruby/2.1.0/webrick/httpserver.rb:138:in `service'
  /Users/me/.rbenv/versions/2.1.3/lib/ruby/2.1.0/webrick/httpserver.rb:94:in `run'
  /Users/me/.rbenv/versions/2.1.3/lib/ruby/2.1.0/webrick/server.rb:295:in `block in start_thread'

If I run rake routes to ensure that the ahoy engine is mounted I see

     Prefix Verb URI Pattern Controller#Action
ahoy_engine      /ahoy       Ahoy::Engine

Routes for Ahoy::Engine:
visits POST /visits(.:format) ahoy/visits#create
events POST /events(.:format) ahoy/events#create

Not work with app which has a catch-all route.

We had a catch-all route at the end of routes.rb file. (mainly for slient the 404 log on production.log)

  match "*path" => "errors#not_found",             via: :all

this can not work with ahoy, since ahoy's routes.rb will be loaded after application's routes.rb.
Maybe Ahoy should not mount itself on routes.rb automaticly, just add it to the README , and let developer mount it automaticly(wherever they want)。or provide a way to customize it .

Ahoy 1.0

  • Replace concept of tokens w/ ids (simpler and reduces database queries)
  • Store ids as 16-byte binary, not strings
  • Make events queryable

duplicate key value violates unique constraint "index_visits_on_visit_token"

Last night I upgraded from 0.2.1 to 0.3.1. Overnight I got the following error:

ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_visits_on_visit_token"
DETAIL: Key (visit_token)=(0iJM4HULQyM4edqAXcPoHV0g1VZPul1Q) already exists.
: INSERT INTO "visits" ("browser", "created_at", "device_type", "ip", "landing_page", "os", "user_agent", "visit_token", "visitor_token")

Also, I see in the README that ahoy is supposed to ignore bots. Is that still the case? I see two GoogleBot Rows in the visits table from last night.

Finally, is there anything else I need to do to upgrade? Migrations to run?

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.