module DashFu

Use me to push data to Dash-fu in real time.

The only configuration you need is setting the API key in config/environments/production.rb:

DashFu.api_key = "<your API key>"

You only want to push data in production, so make sure the API key is only set in production environment. Calls to created/active with no API key are simply ignored.

You can send events by calling DashFu.push with UID and event, or using the specialized created and active methods.

For example, to assign a user to a cohort, we’re going to notify Dash-fu whenever an acount gets created:

class User < ActiveRecord::Model
  after_create do
    DashFu.created id
  end
end

In this example, we consider a user active whenever they post a status update, and notify Dash-fu accordingly:

class StatusUpdate < ActiveRecord::Model
  after_create do
    DashFu.active id
  end
end

Attributes

api_key[RW]

Set the API key with:

DashFu.api_key = "<your API key>"
host[RW]

DashFu.host is set by default, this is only useful for testing.

port[RW]

DashFu.host is set by default, this is only useful for testing.

Public Class Methods

active(uid) click to toggle source

Records that a user has been active in the app.

# File lib/dash_fu.rb, line 142
def active(uid)
  push uid, "active"
end
close() click to toggle source

Close connection. If you like crossing your t’s you can call this when the app shutsdown.

# File lib/dash_fu.rb, line 154
def close
  @mutex.synchronize do
    socket, @socket = @socket, nil
    socket.close if socket
  end
rescue Exception
end
created(uid) click to toggle source

Records that a new user account has been created (associate them with cohort).

# File lib/dash_fu.rb, line 148
def created(uid)
  push uid, "created"
end
log(*args) click to toggle source

Logs an activity. An activity has an actor (the user who performed the activity), the action performed (e.g. posted, commented) and the object of the action (e.g. the post).

You can call this method with actor, action and optional object (two or three arguments), or with a Hash with the keys actor, action, object and timestamp.

For example:

DashFu.log user, 'posted', post.title
DashFu.log actor: user, action: 'posted', object: { content: post.title }

The actor argument is either the ID of a user, an object that responds to to_dashboard, or Hash with the following keys:

  • uid – User identifier (must be unique in application, required)

  • created – Date/time instance when user account was created

  • name – Display name

  • email – Email address

  • image – URL for profile photo

  • url – URL for profile

UID is required, all other properties are optional.

When using an object that responds to to_dashboard, the to_dashboard method must return a Hash with these keys.

The action argument is any string, e.g. ‘posted’, ‘commented’.

The object argument is either a string containing HTML markup, an object that respobds to to_dashboard, or Hash with the following keys:

  • content – HTML markup describing the object

HTML markup may use semantic styling elements such as b, em, p, blockquote. Styles and scripts are stripped, and only links to HTTP/S URLs are preserved.

When using an object that responds to to_dashboard, the to_dashboard method must return a Hash with these keys.

The timestamp argument is the date/time instance the activity occurred.

# File lib/dash_fu.rb, line 93
def log(*args)
  return unless @api_key # in test/dev mode, return quickly

  if args.length == 1 && args[0].respond_to?(:values_at)
    actor, action, object, timestamp = args[0].values_at(:actor, :action, :object, :timestamp)
  elsif args.length == 2 || args.length == 3
    actor, action, object = *args
  else
    raise ArgumentError.new("Expected Hash or actor, action, (object)")
  end

  if actor.respond_to?(:to_dashboard)
    actor = actor.to_dashboard
  elsif String === actor
    actor = { uid: actor }
  end

  action = action.to_s

  if object.respond_to?(:to_dashboard)
    object = object.to_dashboard
  elsif String === object
    object = { content: object }
  end

  
  params = { actor: actor, action: action, object: object, timestamp: timestamp }
  json = MultiJson.encode(params)
  puts json
  if socket = @socket
    socket.sendmsg_nonblock "POST /v1/activity?api_key=#{@api_key} HTTP/1.1\r\nHost: #{@host}\r\nConnection: keep-alive\r\nContent-Type: application/json\r\nContent-Length: #{json.length}\r\n\r\n#{json}", 0
    socket.recv_nonblock 512 rescue nil
  else
    Thread.new do
      @mutex.synchronize do
        @socket ||= TCPSocket.open(@host, @port || 80)
      end
      log params
    end
  end
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT
  close
  retry
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH
  # No @socket so we'll try to connect next time
end
push(uid, event) click to toggle source

Push update for the specified user ID and event. Or you can use active/created.

# File lib/dash_fu.rb, line 164
def push(uid, event)
  return unless @api_key
  if socket = @socket
    socket.sendmsg_nonblock "POST /v1/push?api_key=#{@api_key}&uid=#{uid}&event=#{event} HTTP/1.1\r\nHost: #{@host}\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n", 0
    socket.recv_nonblock 512 rescue nil
  else
    Thread.new do
      @mutex.synchronize do
        @socket ||= TCPSocket.open(@host, @port || 80)
      end
      push uid, event
    end
  end
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT
  close
  retry
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH
  # No @socket so we'll try to connect next time
end