Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Applying the Adapter Pattern for Analytics in Ember.js Apps

Applying the Adapter Pattern for Analytics in Ember.js Apps

Presented at Boston Ember

At DockYard, most (if not all) Ember apps we build for our clients require analytics in some form or other. For many of these apps, including the Google Analytics tracking script is sufficient. However, we’ve begun to notice an increased interest in other services such as Mixpanel or Kissmetrics for more data minded clients.

Including multiple services (typically we see about 3–5 different analytics in use) with different APIs leads to code duplication, which is less than ideal and generally a maintenance nightmare. I don’t know about you, but deleting repetitive code is one of my favorite things to do — so Michael and I set out to build an Ember addon that would apply the adapter pattern to analytics.

Lauren Tan

August 13, 2015
Tweet

More Decks by Lauren Tan

Other Decks in Programming

Transcript

  1. I. What is the Adapter Pattern? II. Creating the Ember

    Metrics Addon III. Adapters and Blueprints
  2. I. What is the Adapter Pattern? II. Creating the Ember

    Metrics Addon III. Adapters and Blueprints
  3. class Borrower { constructor(name, bank) { this.name = name; this.bank

    = bank; } loan(amount) { this.bank.lendTo(this, amount); // bank lends money to borrower } }
  4. class Lender { constructor(name, bank) { this.name = name; this.bank

    = bank; } deposit(amount) { this.bank.borrowFrom(this, amount); // bank borrows money from lender } }
  5. const piggyBank = new Bank(); const michael = new Lender('Michael

    Bolton', piggyBank); const milton = new Lender('Milton Waddams', piggyBank); const bill = new Lender('Bill Lumbergh', piggyBank); const acmeCo = new Borrower('Acme Co', piggyBank); michael.deposit(5000); milton.deposit(10000); bill.deposit(5000); console.log(piggyBank.cash()); // 20000 acmeCo.loan(13000); console.log(acmeCo.report()); // -13000 console.log(piggyBank.cash()); // 7000
  6. I. What is the Adapter Pattern? II. Creating the Ember

    Metrics Addon III.Adapters and Blueprints
  7. module.exports = function(environment) { var ENV = { metricsAdapters: [

    { name: 'GoogleAnalytics', config: { id: 'UA-XXXX-Y' } }, { name: 'Mixpanel', config: { token: 'xxx' } } ] } }
  8. import Ember from 'ember'; export default Ember.Route.extend({ metrics: inject.service(), activate()

    { this._trackPage(); }, _trackPage() { run.scheduleOnce('afterRender', this, () => { const page = document.location.href; const title = this.routeName; get(this, 'metrics').trackPage({ page, title }); }); } });
  9. import config from '../config/environment'; export function initialize(_container, application) { const

    { metricsAdapters = {} } = config; application.register('config:metrics', metricsAdapters, { instantiate: false }); application.inject('service:metrics', 'metricsAdapters', 'config:metrics'); } export default { name: 'metrics', initialize };
  10. import Ember from 'ember'; export default Ember.Service.extend({ _adapters: {}, init()

    { const adapters = Ember.getWithDefault(this, 'metricsAdapters', Ember.A()); this.activateAdapters(adapters); }, activateAdapters(adapterOptions = []) { // 1. activates adapters and adds them to internal cache // 2. is idempotent } });
  11. import Ember from 'ember'; export default Ember.Route.extend({ metrics: Ember.inject.service(), afterModel(model)

    { const metrics = Ember.get(this, 'metrics'); const id = Ember.get(model, 'googleAnalyticsKey'); metrics.activateAdapters([ { name: 'GoogleAnalytics', config: { id } } ]); } });
  12. _activateAdapter(adapterOption = {}) { const metrics = this; const {

    name, config } = adapterOption; const Adapter = this._lookupAdapter(name); return Adapter.create({ metrics, config }); }
  13. identify(...args) { this.invoke('identify', ...args); }, alias(...args) { this.invoke('alias', ...args); },

    trackEvent(...args) { this.invoke('trackEvent', ...args); }, trackPage(...args) { this.invoke('trackPage', ...args); }
  14. invoke(methodName, ...args) { const adaptersObj = Ember.get(this, '_adapters'); const adapterNames

    = Object.keys(adaptersObj); const adapters = adapterNames.map((adapterName) => { return Ember.get(adaptersObj, adapterName); }); if (args.length > 1) { let [ adapterName, options ] = args; const adapter = Ember.get(adaptersObj, adapterName); adapter[methodName](options); } else { adapters.forEach((adapter) => adapter[methodName](...args)); } }
  15. I. What is the Adapter Pattern? II. Creating the Ember

    Metrics Addon III. Adapters and Blueprints
  16. import Ember from 'ember'; export default Ember.Object.extend({ init() { Ember.assert(`[ember-metrics]

    ${this.toString()} must implement the init hook!`); }, willDestroy() { Ember.assert(`[ember-metrics] ${this.toString()} must implement the willDestroy hook!`); }, metrics: null, config: null, identify: Ember.K, trackEvent: Ember.K, trackPage: Ember.K, alias: Ember.K });
  17. import Ember from 'ember'; import BaseAdapter from './base'; export default

    BaseAdapter.extend({ init() { // setup library and API key }, trackEvent(options = {}) { // tracks event }, trackPage(options = {}) { // tracks pageview }, willDestroy() { // tearsdown library } });
  18. I. What is the Adapter Pattern? II. Creating the Ember

    Metrics Addon III. Adapters and Blueprints
  19. thx