<commit_before>/**
 * Prepros
 * (c) Subash Pathak
 * sbshpthk@gmail.com
 * License: MIT
 */

/*jshint browser: true, node: true*/
/*global prepros, angular, _, $*/

prepros.factory('config', function () {

    'use strict';

    var fs = require('fs-extra'),
        path = require('path'),
        os = require('os');

    //Base path
    var basePath = path.join(path.dirname(process.execPath), 'app');

    //Package.json file url
    var packageFileUrl = path.join(basePath, '../package.json');

    //Read package.json file and get data of app in prepros object
    var packageData = JSON.parse(fs.readFileSync(packageFileUrl).toString());

    //We need package path because everything is relative to this file
    var packagePath = path.join(basePath, '..');

    var dataPath = '';

    //for windows > vista
    if(process.env.LOCALAPPDATA){

        //User data path
        dataPath = path.join(process.env.LOCALAPPDATA, 'Prepros/Data');

    } else {

        //for windows Xp
        dataPath = path.join(process.env.APPDATA, 'Prepros/Data');

    }

    //User config file
    var configFile = path.join(dataPath, 'config.json');

    //CachePath
    var cachePath = path.join(process.env.TEMP, 'PreprosCache');

    //Node modules required by the app
    var node_modules = packageData.dependencies;

    //App version
    var version = packageData.version;

    //App urls
    var online = {
        url: 'http://alphapixels.com/prepros',
        helpUrl: 'http://alphapixels.com/prepros',
        loveUrl: 'http://alphapixels.com/prepros#love',
        updateFileUrl: 'http://alphapixels.com/prepros/update.json',
        githubUrl: 'http://github.com/sbspk/prepros',
        authorTwitter: 'http://twitter.com/sbspk',
        authorUrl: 'http://alphapixels.com'
    };

    var ruby  = {};

    //Ruby Executable
    ruby = {
        path: path.join(packagePath, packageData.ruby.path),
        version: packageData.ruby.version
    };

    //Gems
    ruby.gems = {};
    var gemKeys = Object.keys(packageData.ruby.gems);

    _.each(gemKeys, function(key){

        ruby.gems[key] = {

            path: path.join(packagePath, packageData.ruby.gems[key].path),
            version: packageData.ruby.gems[key].version
        };

    });

    //User options
    var userConfig = {};

    //Private function to save user configurations
    function _saveOptions() {

        try {

            fs.outputFileSync(configFile, angular.toJson(userConfig, true));

        } catch(e){

            //Can't use notification api here
            window.alert('Unable to save configuration file.');

        }
    }

    //Read user config
    if (fs.existsSync(configFile)) {

        try {

            userConfig = JSON.parse(fs.readFileSync(configFile).toString());

        } catch(e){

            //Can't use notification api here
            window.alert('Unable to read configuration file.');
        }

    }

    var defaultConfig = {
        cssPath: 'css',
        jsPath: 'js',
        htmlPath: 'html',
        htmlExtension: '.html',
        enableSuccessNotifications: true,
        enableErrorNotifications: true,
        filterPatterns: '',
        useCustomRuby: false,
        customRubyPath: '',

        //Default Less Options
        less : {
            autoCompile: true,
            compress : true,
            lineNumbers: false
        },

        //Default Sass options
        sass : {
            autoCompile : true,
            lineNumbers : false,
            debug: false,
            compass : false,
            fullCompass: false,
            outputStyle : 'compressed' //compressed, nested, expanded, compact
        },

        //Default Stylus Options
        stylus : {
            autoCompile: true,
            lineNumbers : false,
            nib : false,
            compress : true
        },

        //Default Markdown Options
        markdown : {
            autoCompile: true,
            sanitize: false,
            gfm: true
        },

        //Default Coffeescript Options
        coffee: {
            autoCompile: true,
            bare: false,
            uglify: false
        },

        //Default Jade  Options
        jade: {
            autoCompile: true,
            pretty: true
        },

        //Default Haml Options
        haml: {
            autoCompile: true,
            format: 'html5', //xhtml, html5
            outputStyle: 'indented', //indented, ugly
            doubleQuotes: false
        },

        //Default Slim  Options
        slim: {
            autoCompile: true,
            pretty: true
        }
    };

    //Fill in the undefined values from default configurations
    userConfig = _.defaults(userConfig, defaultConfig);

    //Create configuration file if it doesn't exist
    if(!fs.existsSync(configFile)) {

        _saveOptions();

    }

    //Wrap user options in a function to prevent angular data sharing between services
    //If user config data is shared between files changing configuration of one file will affect another file
    function getUserOptions() {

        return $.parseJSON(angular.toJson(userConfig));

    }

    function saveUserOptions(options) {

        userConfig = $.parseJSON(angular.toJson(options));

        _saveOptions();

    }

    var config =  {
        dataPath: dataPath,
        cachePath: cachePath,
        basePath: basePath,
        ruby: ruby,
        node_modules: node_modules,
        online: online,
        version: version,
        getUserOptions: getUserOptions,
        saveUserOptions: saveUserOptions
    };

    //Push to global so other windows can also read configurations
    global.preprosConfig = config;

    return config;

});<commit_msg>get temporary folder with os module<commit_after>/**
 * Prepros
 * (c) Subash Pathak
 * sbshpthk@gmail.com
 * License: MIT
 */

/*jshint browser: true, node: true*/
/*global prepros, angular, _, $*/

prepros.factory('config', function () {

    'use strict';

    var fs = require('fs-extra'),
        path = require('path'),
        os = require('os');

    //Base path
    var basePath = path.join(path.dirname(process.execPath), 'app');

    //Package.json file url
    var packageFileUrl = path.join(basePath, '../package.json');

    //Read package.json file and get data of app in prepros object
    var packageData = JSON.parse(fs.readFileSync(packageFileUrl).toString());

    //We need package path because everything is relative to this file
    var packagePath = path.join(basePath, '..');

    var dataPath = '';

    //for windows > vista
    if(process.env.LOCALAPPDATA){

        //User data path
        dataPath = path.join(process.env.LOCALAPPDATA, 'Prepros/Data');

    } else {

        //for windows Xp
        dataPath = path.join(process.env.APPDATA, 'Prepros/Data');

    }

    //User config file
    var configFile = path.join(dataPath, 'config.json');

    //CachePath
    var cachePath = path.join(os.tmpdir(), 'PreprosCache');

    //Node modules required by the app
    var node_modules = packageData.dependencies;

    //App version
    var version = packageData.version;

    //App urls
    var online = {
        url: 'http://alphapixels.com/prepros',
        helpUrl: 'http://alphapixels.com/prepros',
        loveUrl: 'http://alphapixels.com/prepros#love',
        updateFileUrl: 'http://alphapixels.com/prepros/update.json',
        githubUrl: 'http://github.com/sbspk/prepros',
        authorTwitter: 'http://twitter.com/sbspk',
        authorUrl: 'http://alphapixels.com'
    };

    var ruby  = {};

    //Ruby Executable
    ruby = {
        path: path.join(packagePath, packageData.ruby.path),
        version: packageData.ruby.version
    };

    //Gems
    ruby.gems = {};
    var gemKeys = Object.keys(packageData.ruby.gems);

    _.each(gemKeys, function(key){

        ruby.gems[key] = {

            path: path.join(packagePath, packageData.ruby.gems[key].path),
            version: packageData.ruby.gems[key].version
        };

    });

    //User options
    var userConfig = {};

    //Private function to save user configurations
    function _saveOptions() {

        try {

            fs.outputFileSync(configFile, angular.toJson(userConfig, true));

        } catch(e){

            //Can't use notification api here
            window.alert('Unable to save configuration file.');

        }
    }

    //Read user config
    if (fs.existsSync(configFile)) {

        try {

            userConfig = JSON.parse(fs.readFileSync(configFile).toString());

        } catch(e){

            //Can't use notification api here
            window.alert('Unable to read configuration file.');
        }

    }

    var defaultConfig = {
        cssPath: 'css',
        jsPath: 'js',
        htmlPath: 'html',
        htmlExtension: '.html',
        enableSuccessNotifications: true,
        enableErrorNotifications: true,
        filterPatterns: '',
        useCustomRuby: false,
        customRubyPath: '',

        //Default Less Options
        less : {
            autoCompile: true,
            compress : true,
            lineNumbers: false
        },

        //Default Sass options
        sass : {
            autoCompile : true,
            lineNumbers : false,
            debug: false,
            compass : false,
            fullCompass: false,
            outputStyle : 'compressed' //compressed, nested, expanded, compact
        },

        //Default Stylus Options
        stylus : {
            autoCompile: true,
            lineNumbers : false,
            nib : false,
            compress : true
        },

        //Default Markdown Options
        markdown : {
            autoCompile: true,
            sanitize: false,
            gfm: true
        },

        //Default Coffeescript Options
        coffee: {
            autoCompile: true,
            bare: false,
            uglify: false
        },

        //Default Jade  Options
        jade: {
            autoCompile: true,
            pretty: true
        },

        //Default Haml Options
        haml: {
            autoCompile: true,
            format: 'html5', //xhtml, html5
            outputStyle: 'indented', //indented, ugly
            doubleQuotes: false
        },

        //Default Slim  Options
        slim: {
            autoCompile: true,
            pretty: true
        }
    };

    //Fill in the undefined values from default configurations
    userConfig = _.defaults(userConfig, defaultConfig);

    //Create configuration file if it doesn't exist
    if(!fs.existsSync(configFile)) {

        _saveOptions();

    }

    //Wrap user options in a function to prevent angular data sharing between services
    //If user config data is shared between files changing configuration of one file will affect another file
    function getUserOptions() {

        return $.parseJSON(angular.toJson(userConfig));

    }

    function saveUserOptions(options) {

        userConfig = $.parseJSON(angular.toJson(options));

        _saveOptions();

    }

    var config =  {
        dataPath: dataPath,
        cachePath: cachePath,
        basePath: basePath,
        ruby: ruby,
        node_modules: node_modules,
        online: online,
        version: version,
        getUserOptions: getUserOptions,
        saveUserOptions: saveUserOptions
    };

    //Push to global so other windows can also read configurations
    global.preprosConfig = config;

    return config;

});<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Mixin.create({
  copyable: true,
  copy: function(options, copied) {
    options = options || {};
    copied = copied || {};

    var _this = this;
    return new Ember.RSVP.Promise(function(resolve) {

      var model = _this.constructor;

      var id = _this.get('id');
      if (copied.hasOwnProperty(id)) {
        return resolve(copied[id]);
      }

      var copy = _this.get('store').createRecord(model.modelName || model.typeKey);
      copied[id] = copy;
      var queue = [];

      model.eachAttribute(function(attr) {
        switch(Ember.typeOf(options[attr])) {
          case 'undefined':
            copy.set(attr, _this.get(attr));
            break;
          case 'null':
            copy.set(attr, null);
            break;
          default:
            copy.set(attr, options[attr]);
        }
      });

      model.eachRelationship(function(relName, meta) {
        var rel = _this.get(relName);
        if (!rel) { return; }

        var overwrite;
        var passedOptions = {};
        switch(Ember.typeOf(options[relName])) {
          case 'null':
          return;
          case 'instance':
            overwrite = options[relName];
            break;
          case 'object':
            passedOptions = options[relName];
            break;
          case 'array':
            overwrite = options[relName];
            break;
          default:
        }

        if (rel.constructor === DS.PromiseObject) {

          queue.push(rel.then(function(obj) {

            if (obj && obj.get('copyable') && !overwrite) {
              return obj.copy(passedOptions, copied).then(function(objCopy) {
                copy.set(relName, objCopy);
              });

            } else {
              copy.set(relName, overwrite || obj);
            }

          }));

        } else if (rel.constructor === DS.PromiseManyArray) {

          if (overwrite) {
            copy.get(relName).setObjects(overwrite);
          } else {
            queue.push(rel.then(function(array) {
              var resolvedCopies =
                array.map(function(obj) {
                  if (obj.get('copyable')) {
                    return obj.copy(passedOptions, copied);
                  } else {
                    return obj;
                  }
                });
              return Ember.RSVP.all(resolvedCopies).then(function(copies){
                copy.get(relName).setObjects(copies);
              });
            }));
          }
        } else {
          if (meta.kind === 'belongsTo') {
            var obj = rel;

            if (obj && obj.get('copyable') && !overwrite) {
              queue.push( obj.copy(passedOptions, copied).then(function(objCopy) {
                copy.set(relName, objCopy);
              }));
            } else {
              copy.set(relName, overwrite || obj);
            }

          } else {
            var objs = rel;

            if (objs.get('content')) {
              objs = objs.get('content').compact();
            }

            if (objs.get('firstObject.copyable') && !overwrite) {

              var copies = objs.map(function(obj) {
                return obj.copy(passedOptions, copied);
              });

              queue.push( Ember.RSVP.all(copies).then( function(resolvedCopies) {
                copy.get(relName).setObjects(resolvedCopies);
              }));

            } else {
              copy.get(relName).setObjects(overwrite || objs);
            }
          }

        }
      });

      Ember.RSVP.all(queue).then(function() {
        resolve(copy);
      });
    });
  }
});
<commit_msg>make sure id is uniq<commit_after>import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Mixin.create({
  copyable: true,
  copy: function(options, copied) {
    options = options || {};
    copied = copied || {};

    var _this = this;
    return new Ember.RSVP.Promise(function(resolve) {

      var model = _this.constructor;
      var modelName = model.modelName || model.typeKey;
      var id = modelName + "--" + _this.get('id');
      if (copied.hasOwnProperty(id)) {
        return resolve(copied[id]);
      }

      var copy = _this.get('store').createRecord(modelName);
      copied[id] = copy;
      var queue = [];

      model.eachAttribute(function(attr) {
        switch(Ember.typeOf(options[attr])) {
          case 'undefined':
            copy.set(attr, _this.get(attr));
            break;
          case 'null':
            copy.set(attr, null);
            break;
          default:
            copy.set(attr, options[attr]);
        }
      });

      model.eachRelationship(function(relName, meta) {
        var rel = _this.get(relName);
        if (!rel) { return; }

        var overwrite;
        var passedOptions = {};
        switch(Ember.typeOf(options[relName])) {
          case 'null':
          return;
          case 'instance':
            overwrite = options[relName];
            break;
          case 'object':
            passedOptions = options[relName];
            break;
          case 'array':
            overwrite = options[relName];
            break;
          default:
        }

        if (rel.constructor === DS.PromiseObject) {

          queue.push(rel.then(function(obj) {

            if (obj && obj.get('copyable') && !overwrite) {
              return obj.copy(passedOptions, copied).then(function(objCopy) {
                copy.set(relName, objCopy);
              });

            } else {
              copy.set(relName, overwrite || obj);
            }

          }));

        } else if (rel.constructor === DS.PromiseManyArray) {

          if (overwrite) {
            copy.get(relName).setObjects(overwrite);
          } else {
            queue.push(rel.then(function(array) {
              var resolvedCopies =
                array.map(function(obj) {
                  if (obj.get('copyable')) {
                    return obj.copy(passedOptions, copied);
                  } else {
                    return obj;
                  }
                });
              return Ember.RSVP.all(resolvedCopies).then(function(copies){
                copy.get(relName).setObjects(copies);
              });
            }));
          }
        } else {
          if (meta.kind === 'belongsTo') {
            var obj = rel;

            if (obj && obj.get('copyable') && !overwrite) {
              queue.push( obj.copy(passedOptions, copied).then(function(objCopy) {
                copy.set(relName, objCopy);
              }));
            } else {
              copy.set(relName, overwrite || obj);
            }

          } else {
            var objs = rel;

            if (objs.get('content')) {
              objs = objs.get('content').compact();
            }

            if (objs.get('firstObject.copyable') && !overwrite) {

              var copies = objs.map(function(obj) {
                return obj.copy(passedOptions, copied);
              });

              queue.push( Ember.RSVP.all(copies).then( function(resolvedCopies) {
                copy.get(relName).setObjects(resolvedCopies);
              }));

            } else {
              copy.get(relName).setObjects(overwrite || objs);
            }
          }

        }
      });

      Ember.RSVP.all(queue).then(function() {
        resolve(copy);
      });
    });
  }
});
<|endoftext|>


<commit_before>//Found here originally
//https://www.data.gov/highlights/
//Full data
//https://data.seattle.gov/Public-Safety/Seattle-Real-Time-Fire-911-Calls/kzjm-xkqj
//April Seattle 911 Calls (depending on time zone, will actually start/end before/after April)
//https://data.seattle.gov/resource/grwu-wqtk.json?$where=Datetime%3E%272016-04-1T00:00:00%27%20AND%20DateTime%20%3C%20%272016-05-1T00:00:00%27&$order=Datetime%20asc&$limit=50000

(function (app) {
  "use strict";

  var speed = 1000,
    paused = false,
    currentTime = new Date(),
    displayedData = [],
    newestItems = [];

  var pullData = function(callback) {
    d3.json("data/seattle911Calls.json", function (err, data){
      if (err) return console.warn(err);

      callback(data.map(function (val) {
        val.date = new Date(val.datetime);

        return val;
      }));
    })
  }

  app.updateDisplay = function () {
    d3.select(".time-display")
      .text(currentTime);

    d3.select(".speed-display")
      .text(speed + "x" + (paused ? "(paused)" : ""));
  }

  app.increaseSpeed = function () {
    if (speed < 1000000) {
      speed *= 10;
    }
  }

  app.decreaseSpeed = function () {
    if (speed > 1) {
      speed /= 10;
    }
  }

  app.pause = function () {
    paused = !paused;
  }

  d3.select(".increase-speed")
    .on("click", function () {
      app.increaseSpeed();
    });

  d3.select(".decrease-speed")
    .on("click", function () {
      app.decreaseSpeed();
    });

  d3.select(".pause")
    .on("click", function () {
      app.pause();
    });

  //setup bar chart
  var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = parseInt(d3.select('#barChart').style("width"), 10) - margin.left - margin.right,
    height = parseInt(d3.select('#barChart').style("height"), 10) - margin.top - margin.bottom;

  var colors = ['#3182bd','#6baed6','#9ecae1','#c6dbef','#e6550d','#fd8d3c','#fdae6b','#fdd0a2','#31a354','#74c476','#a1d99b','#c7e9c0','#756bb1','#9e9ac8','#bcbddc','#dadaeb', '636363', '969696', 'bdbdbd', 'd9d9d9'];
  var colorScale = d3.scale.category20();

  var canvas = d3.select('#barChart')
    .append('svg')
    .attr({'width':width,'height':height});

  var chart = canvas.append('g')
    .attr("transform", "translate(200,0)")
    .attr('id','bars');

  var yAxisElement = canvas.append('g')
    .attr("transform", "translate(200,10)")
    .attr('id','yaxis');

  //setup bubble chart
  var bubbleWidth = parseInt(d3.select('#bubbleChart').style("width"), 10) - margin.left - margin.right,
    bubbleHeight = parseInt(d3.select('#bubbleChart').style("height"), 10) - margin.top - margin.bottom;

  var bubbleCanvas = d3.select('#bubbleChart')
    .append('svg')
    .attr({'width': bubbleWidth,'height': bubbleHeight});

  var bubbleChart = bubbleCanvas.append('g')
    .attr('id','bubbles');

  //update
  var redrawChart = function () {
    var types = displayedData
      .map(function (item) { return item.type })
      .filter(function(item, i, ar){ return ar.indexOf(item) === i; });

    var typeCounts = [];

    for (var i = 0; i < types.length; i++) {
      typeCounts.push(
        displayedData.filter(function (item) { return item.type === types[i]; }).length
      );
    }

    var maxCount = d3.max(typeCounts);

    //Bar chart
    var xscale = d3.scale.linear()
      .domain([0,maxCount])
      .range([0,width - 200 - 50]); //Remove the left offset and add padding for labels

    var yscale = d3.scale.linear()
      .domain([0,types.length])
      .range([0,height]);

    var yAxis = d3.svg.axis();
    yAxis
      .orient('left')
      .scale(yscale)
      .tickSize(1)
      .tickFormat(function(d,i){ return types[i]; })
      .tickValues(d3.range(types.length));

    var y_xis = yAxisElement
      .transition()
      .duration(1000)
      .ease("quad")
      .call(yAxis);

    var rects = chart.selectAll('rect')
      .data(typeCounts);

    rects
      .enter()
      .append('rect')
      .attr({
        fill: function (d, i) { return colorScale(i); },
        class: "bar",
        y: function (d, i) { return yscale(i + 1); }
      });

    rects
      .transition()
      .duration(1000)
      .ease("quad")
      .attr({
        height: height / types.length,
        width: function(d) { return xscale(d); },
        y: function(d,i){ return yscale(i); }
      });

    var texts = d3.select('#bars')
      .selectAll('text')
      .data(typeCounts);

    texts
      .enter()
      .append('text')
      .attr("class", "bar-label");

    texts
      .transition()
      .duration(1000)
      .ease("quad")
      .text(function(d){ return d; })
      .attr({
        x:function(d) { return xscale(d)+10; },
        y:function(d,i){ return yscale(i)+16; }
      });

    //Bubble chart
    var newTypes = newestItems
      .map(function (item) { return item.type })
      .filter(function(item, i, ar){ return ar.indexOf(item) === i; });

    var circles = bubbleChart.selectAll("circle.bubble")
      .data(newTypes);

    circles.enter().append("circle")
      .attr({
        cy: function () { return Math.floor(Math.random() * (bubbleHeight - 100)) + 50; },
        cx: function () { return Math.floor(Math.random() * (bubbleWidth - 100)) + 50; },
        r: 0,
        fill: function (d, i) { return colorScale(types.indexOf(d)); }
      })
      .style("opacity", 1)
      .transition()
      .delay(function () { return Math.floor(Math.random() * (200)); })
      .duration(function () { return Math.floor(Math.random() * (2000 - 800)) + 800; })
      .attr("r", 50)
      .style("opacity", 0)
      .remove()
      //TODO: Maybe make the radius dependent on how many new items this cycle
      //.attr("r", function(d) { return Math.sqrt(d); });

    //circles.exit().remove();
  }

  var startClockCycle = function(data) {
    currentTime = data[0].date;

    //Fire initial blob here
    var removedItem = data.splice(0, 1);

    newestItems = removedItem;

    displayedData = displayedData.concat(removedItem);

    redrawChart();
    app.updateDisplay();

    setInterval(function(){
      if (!paused) {
        //redraw blobs and histogram (TODO)
        currentTime.setSeconds(currentTime.getSeconds() + (1 * speed))

        var newestItemIndex = null;

        for (var i = 0; i < data.length; i++) {
          if (data[i].date < currentTime) {
            newestItemIndex = i;
          }
          else {
            break;
          }
        }

        newestItems = [];

        if (newestItemIndex !== null) {
          newestItems = data.splice(0, newestItemIndex + 1);
        }

        displayedData = displayedData.concat(newestItems);

        console.log(currentTime, speed, newestItems);

        redrawChart();
      }
      app.updateDisplay();
    }, 1000);
  }

  pullData(startClockCycle);

})(window.app = window.app || {});<commit_msg>Refactored to allow for millisecond clock cycles and adjusted clock cycle time (may need to change again as this makes transitions fast). Added chart resizing.<commit_after>//Found here originally
//https://www.data.gov/highlights/
//Full data
//https://data.seattle.gov/Public-Safety/Seattle-Real-Time-Fire-911-Calls/kzjm-xkqj
//April Seattle 911 Calls (depending on time zone, will actually start/end before/after April)
//https://data.seattle.gov/resource/grwu-wqtk.json?$where=Datetime%3E%272016-04-1T00:00:00%27%20AND%20DateTime%20%3C%20%272016-05-1T00:00:00%27&$order=Datetime%20asc&$limit=50000

(function (app) {
  "use strict";

  var speed = 1000,
    cycleTime = 200,
    paused = false,
    currentTime = new Date(),
    displayedData = [],
    newestItems = [];

  var pullData = function(callback) {
    d3.json("data/seattle911Calls.json", function (err, data){
      if (err) return console.warn(err);

      callback(data.map(function (val) {
        val.date = new Date(val.datetime);

        return val;
      }));
    })
  }

  app.updateDisplay = function () {
    d3.select(".time-display")
      .text(currentTime);

    d3.select(".speed-display")
      .text(speed + "x" + (paused ? "(paused)" : ""));
  }

  app.increaseSpeed = function () {
    if (speed < 1000000) {
      speed *= 10;
    }
  }

  app.decreaseSpeed = function () {
    if (speed > 1) {
      speed /= 10;
    }
  }

  app.pause = function () {
    paused = !paused;
  }

  //setup bar chart
  var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = parseInt(d3.select('#barChart').style("width"), 10) - margin.left - margin.right,
    height = parseInt(d3.select('#barChart').style("height"), 10) - margin.top - margin.bottom;

  var colors = ['#3182bd','#6baed6','#9ecae1','#c6dbef','#e6550d','#fd8d3c','#fdae6b','#fdd0a2','#31a354','#74c476','#a1d99b','#c7e9c0','#756bb1','#9e9ac8','#bcbddc','#dadaeb', '636363', '969696', 'bdbdbd', 'd9d9d9'];
  var colorScale = d3.scale.category20();

  var canvas = d3.select('#barChart')
    .append('svg')
    .attr({'width':width,'height':height});

  var chart = canvas.append('g')
    .attr("transform", "translate(200,0)")
    .attr('id','bars');

  var yAxisElement = canvas.append('g')
    .attr("transform", "translate(200,10)")
    .attr('id','yaxis');

  //setup bubble chart
  var bubbleWidth = parseInt(d3.select('#bubbleChart').style("width"), 10) - margin.left - margin.right,
    bubbleHeight = parseInt(d3.select('#bubbleChart').style("height"), 10) - margin.top - margin.bottom;

  var bubbleCanvas = d3.select('#bubbleChart')
    .append('svg')
    .attr({'width': bubbleWidth,'height': bubbleHeight});

  var bubbleChart = bubbleCanvas.append('g')
    .attr('id','bubbles');

  //TODO: Clean up code duplication
  var resizeChart = function () {
    //bar
    width = parseInt(d3.select('#barChart').style("width"), 10) - margin.left - margin.right;
    height = parseInt(d3.select('#barChart').style("height"), 10) - margin.top - margin.bottom;

    canvas.attr({'width':width,'height':height});

    //bubble
    bubbleWidth = parseInt(d3.select('#bubbleChart').style("width"), 10) - margin.left - margin.right;
    bubbleHeight = parseInt(d3.select('#bubbleChart').style("height"), 10) - margin.top - margin.bottom;

    bubbleCanvas.attr({'width': bubbleWidth,'height': bubbleHeight});

    redrawChart();
  }

  //update
  var redrawChart = function () {
    var types = displayedData
      .map(function (item) { return item.type })
      .filter(function(item, i, ar){ return ar.indexOf(item) === i; });

    var typeCounts = [];

    for (var i = 0; i < types.length; i++) {
      typeCounts.push(
        displayedData.filter(function (item) { return item.type === types[i]; }).length
      );
    }

    var maxCount = d3.max(typeCounts);

    //Bar chart
    var xscale = d3.scale.linear()
      .domain([0,maxCount])
      .range([0,width - 200 - 50]); //Remove the left offset and add padding for labels

    var yscale = d3.scale.linear()
      .domain([0,types.length])
      .range([0,height]);

    var yAxis = d3.svg.axis();
    yAxis
      .orient('left')
      .scale(yscale)
      .tickSize(0)
      .tickFormat(function(d,i){ return types[i]; })
      .tickValues(d3.range(types.length));

    var y_xis = yAxisElement
      .transition()
      .duration(cycleTime)
      .ease("quad")
      .call(yAxis);

    var rects = chart.selectAll('rect')
      .data(typeCounts);

    rects
      .enter()
      .append('rect')
      .attr({
        fill: function (d, i) { return colorScale(i); },
        class: "bar",
        y: function (d, i) { return yscale(i + 1); }
      });

    rects
      .transition()
      .duration(cycleTime)
      .ease("quad")
      .attr({
        height: height / types.length,
        width: function(d) { return xscale(d); },
        y: function(d,i){ return yscale(i); }
      });

    var texts = d3.select('#bars')
      .selectAll('text')
      .data(typeCounts);

    texts
      .enter()
      .append('text')
      .attr("class", "bar-label");

    texts
      .transition()
      .duration(cycleTime)
      .ease("quad")
      .text(function(d){ return d; })
      .attr({
        x:function(d) { return xscale(d)+10; },
        y:function(d,i){ return yscale(i)+13; }
      });

    //Bubble chart
    var newTypes = newestItems
      .map(function (item) { return item.type })
      .filter(function(item, i, ar){ return ar.indexOf(item) === i; });

    var circles = bubbleChart.selectAll("circle.bubble")
      .data(newTypes);

    circles.enter().append("circle")
      .attr({
        cy: function () { return Math.floor(Math.random() * (bubbleHeight - 100)) + 50; },
        cx: function () { return Math.floor(Math.random() * (bubbleWidth - 100)) + 50; },
        r: 0,
        fill: function (d, i) { return colorScale(types.indexOf(d)); }
      })
      .style("opacity", 1)
      .transition()
      .delay(function () { return Math.floor(Math.random() * (200)); })
      .duration(function () { return Math.floor(Math.random() * (2000 - 800)) + 800; })
      .attr("r", 50)
      .style("opacity", 0)
      .remove()
      //TODO: Maybe make the radius dependent on how many new items this cycle
      //.attr("r", function(d) { return Math.sqrt(d); });

    //circles.exit().remove();
  }

  var startClockCycle = function(data) {
    currentTime = data[0].date;

    //Fire initial blob here
    var removedItem = data.splice(0, 1);

    newestItems = removedItem;

    displayedData = displayedData.concat(removedItem);

    redrawChart();
    app.updateDisplay();

    setInterval(function(){
      if (!paused) {
        //redraw blobs and histogram (TODO)
        currentTime.setMilliseconds(currentTime.getMilliseconds() + (cycleTime * speed))

        var newestItemIndex = null;

        for (var i = 0; i < data.length; i++) {
          if (data[i].date < currentTime) {
            newestItemIndex = i;
          }
          else {
            break;
          }
        }

        newestItems = [];

        if (newestItemIndex !== null) {
          newestItems = data.splice(0, newestItemIndex + 1);
        }

        displayedData = displayedData.concat(newestItems);

        console.log(currentTime, speed, newestItems);

        redrawChart();
      }
      app.updateDisplay();
    }, cycleTime);
  }

  d3.select(".increase-speed")
    .on("click", function () {
      app.increaseSpeed();
    });

  d3.select(".decrease-speed")
    .on("click", function () {
      app.decreaseSpeed();
    });

  d3.select(".pause")
    .on("click", function () {
      app.pause();
    });

  d3.select(window)
    .on('resize', resizeChart);

  pullData(startClockCycle);

})(window.app = window.app || {});<|endoftext|>


<commit_before>'use strict'

const express = require('express')
const Slapp = require('slapp')
const ConvoStore = require('slapp-convo-beepboop')
const Context = require('slapp-context-beepboop')

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

//*********************************************
// Setup different handlers for messages
//*********************************************

// response to the user typing "help"
slapp.message('^([0-9]{9,12})$', (msg) => {
  console.log(msg);
  msg.say('https://teamleader.zoom.us/j/' + msg.body.event.text)
})

slapp.message('^\<tel\:[0-9-]{9,12}\|([0-9-]{9,12})\>', (msg) => {
  console.log(msg.body.event.text.replace(/^\<tel\:[0-9-]{9,12}\|/, ''));
  msg.say('https://teamleader.zoom.us/j/' + msg.body.event.text.replace(/^\<tel\:[0-9-]{9,12}\|/, '').replace('-', '').replace('-', '').replace('>', ''))
})

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<commit_msg>Add some text<commit_after>'use strict'

const express = require('express')
const Slapp = require('slapp')
const ConvoStore = require('slapp-convo-beepboop')
const Context = require('slapp-context-beepboop')

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

//*********************************************
// Setup different handlers for messages
//*********************************************

var sendMessage = function(msg, zoomId) {
  msg.say('Hey, that looks like a Zoom ID! Join now: https://teamleader.zoom.us/j/' + zoomId + ".\n\nHave a good meeting, Andreas.")
}

// response to the user typing "help"
slapp.message('^([0-9]{9,12})$', (msg) => {
  sendMessage(msg, msg.body.event.text)
})

slapp.message('^\<tel\:[0-9-]{9,12}\|([0-9-]{9,12})\>', (msg) => {
  var zoomId = msg.body.event.text.replace(/^\<tel\:[0-9-]{9,12}\|/, '').replace('-', '').replace('-', '').replace('>', '');
  sendMessage(msg, zoomId);
})

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<|endoftext|>


<commit_before>import Ember from 'ember';

const {
  Mixin,
  $,
  on,
  run: {
    bind
  }
} = Ember;

export default Mixin.create({
  gestures: ['tap', 'doubletap', 'press', 'pan', 'swipe'],

  recognizers: {
    tap: {},
    press: {},
    pan: {direction: Hammer.DIRECTION_ALL},
    swipe: {direction: Hammer.DIRECTION_ALL},
    rotate: {enable: true},
    pinch: {enable: true}
  },

  setup: on('didInsertElement', function() {
    if (!this.get('gestures')) {
      return;
    }

    this.$().hammer();

    this.$().find('[data-ember-action]').hammer({
      domEvents: true
    });

    this.get('gestures').forEach((gesture) => {
      let options = this.get('recognizers')[gesture];

      if (options) {
        this.$().data('hammer').get(gesture).set(options);
      }

      if (typeof this[gesture] === 'function') {
        this.$().on(gesture, bind(this, gesture));
      }
    });
  }),

  teardown: on('willDestroyElement', function() {
    if (!this.get('gestures')) {
      return;
    }

    this.get('gestures').forEach((gesture) => {
      if (typeof this[gesture] === 'function') {
        this.$().off(gesture, bind(this, gesture));
      }
    });
  })
});
<commit_msg>trigger dom events<commit_after>import Ember from 'ember';

const {
  Mixin,
  $,
  on,
  run: {
    bind
  }
} = Ember;

export default Mixin.create({
  gestures: ['tap', 'doubletap', 'press', 'pan', 'swipe'],

  recognizers: {
    tap: {},
    press: {},
    pan: {direction: Hammer.DIRECTION_ALL},
    swipe: {direction: Hammer.DIRECTION_ALL},
    rotate: {enable: true},
    pinch: {enable: true}
  },

  setup: on('didInsertElement', function() {
    if (!this.get('gestures')) {
      return;
    }

    this.$().hammer();

    this.$().hammer({
      domEvents: true
    });

    this.get('gestures').forEach((gesture) => {
      let options = this.get('recognizers')[gesture];

      if (options) {
        this.$().data('hammer').get(gesture).set(options);
      }
    });
  })
});
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hoi...')
                .then(() => 'speak');
        }
    },

    speak: {
        receive: (bot, message) => {

            let upperText = message.text.trim().toUpperCase();

            function updateSilent() {
                switch (upperText) {
                    case "SCHADE NOT OK":
                        return bot.setProp("silent", true);
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                if (!_.has(scriptRules, upperText)) {
                    return bot.say(`Sorry - maar ik begrijp je even niet`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log(line);
                        return wait(80).then(function() {
                            return bot.say(line);
                        });
                    });
                });

                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }
});
<commit_msg>Update script.js<commit_after>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hoi...')
                .then(() => 'vraagNaam');
        }
    },

    vraagNaam: {
        prompt: (bot) => bot.say('Met wie heb ik het genoegen?'),
        receive: (bot, message) => {
            const name = message.text;
            return bot.setProp('name', name)
                .then(() => bot.say(`Top! Welkom ${name} - Bezwaar als ik je bij je voornaam noem?
                %[Prima](postback:yes) %[Liever niet](postback:no)`))
                .then(() => 'speak');
        }
    },

    speak: {
        receive: (bot, message) => {

            let upperText = message.text.trim().toUpperCase();

            function updateSilent() {
                switch (upperText) {
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                if (!_.has(scriptRules, upperText)) {
                    return bot.say(`Sorry - maar ik begrijp je even niet`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log(line);
                        return wait(80).then(function() {
                            return bot.say(line);
                        });
                    });
                });

                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }

    finish: {
        receive: (bot, message) => {
           // return bot.getProp('name')
            .then(() => 'finish');
        }
    }
});
<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var cors = require('cors');
var request = require('request');
var winston = require('winston');

mongoose.connect('mongodb://192.168.0.21:27017/datagamer'); // connect to our database
//mongoose.connect('mongodb://localhost:27017/datagamer'); // connect to our database

// CORS request
app.use(cors());

// Configure app to use bodyParser(), this will let us get the data from a POST
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Set our port
var port = process.env.PORT || 8084;

// Models
var User = require('./app/models/user');

// Enums
var CODE = require('./app/enums/codes');

// Middleware to use for all requests
app.use(function (req, res, next) {

    winston.info('-- API request : ' + req.headers.apikey);

    // Check if the API key exist
    User.findByApiKey(req.headers.apikey, function (err, users) {
        if (!err) {
            if (users.length > 0) {
                next(); // make sure we go to the next routes and don't stop here
            } else {
                winston.info(CODE.FORBIDDEN);
                res.json(CODE.FORBIDDEN);
            }
        } else {
            winston.error(CODE.FORBIDDEN);
            res.json(CODE.SERVER_ERROR);
        }
    });
});

// Test route to make sure everything is working (accessed at GET http://localhost:8080/api)
app.get('/', function (req, res) {
    winston.info('Test route called !');
    res.json({message: 'Hooray ! Welcome to Datagamer API !'});
});

// REGISTER OUR ROUTES -------------------------------
// API routes
app.use('/api', require('./app/routes/api/developer'));
app.use('/api', require('./app/routes/api/editor'));
app.use('/api', require('./app/routes/api/game'));
app.use('/api', require('./app/routes/api/genre'));
app.use('/api', require('./app/routes/api/platform'));
app.use('/api', require('./app/routes/api/user'));

// Extractor routes
app.use('/extractor', require('./app/routes/extractor/metacritic'));
app.use('/extractor', require('./app/routes/extractor/thegamesdb'));

// START THE SERVER
// =============================================================================
app.listen(port);
winston.info('Datagamer-api v2 is running on port ' + port);<commit_msg>Clean up old files.<commit_after>var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var cors = require('cors');
var request = require('request');
var winston = require('winston');

mongoose.connect('mongodb://192.168.0.21:27017/datagamer'); // connect to our database
//mongoose.connect('mongodb://localhost:27017/datagamer'); // connect to our database

// CORS request
app.use(cors());

// Configure app to use bodyParser(), this will let us get the data from a POST
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Set our port
var port = process.env.PORT || 8084;

// Models
var User = require('./app/models/user');

// Enums
var CODE = require('./app/enums/codes');

// Config logger file
winston.add(winston.transports.File, { filename: 'datagamer-api-v2.log' });

// Middleware to use for all requests
app.use(function (req, res, next) {

    winston.info('-- API request : ' + req.headers.apikey);

    // Check if the API key exist
    User.findByApiKey(req.headers.apikey, function (err, users) {
        if (!err) {
            if (users.length > 0) {
                next(); // make sure we go to the next routes and don't stop here
            } else {
                winston.info(CODE.FORBIDDEN);
                res.json(CODE.FORBIDDEN);
            }
        } else {
            winston.error(CODE.FORBIDDEN);
            res.json(CODE.SERVER_ERROR);
        }
    });
});

// Test route to make sure everything is working (accessed at GET http://localhost:8080/api)
app.get('/', function (req, res) {
    winston.info('Test route called !');
    res.json({message: 'Hooray ! Welcome to Datagamer API !'});
});

// REGISTER OUR ROUTES -------------------------------
// API routes
app.use('/api', require('./app/routes/api/developer'));
app.use('/api', require('./app/routes/api/editor'));
app.use('/api', require('./app/routes/api/game'));
app.use('/api', require('./app/routes/api/genre'));
app.use('/api', require('./app/routes/api/platform'));
app.use('/api', require('./app/routes/api/user'));

// Extractor routes
app.use('/extractor', require('./app/routes/extractor/metacritic'));

// START THE SERVER
// =============================================================================
app.listen(port);
winston.info('Datagamer-api v2 is running on port ' + port);<|endoftext|>


<commit_before>import Ember from "ember";

var dynamicEachAttrs = [];

function factory(mixin) {
    return mixin.get("constructor.ClassMixin.ownerConstructor");
}

function eachAttrs(mixin) {
    var attributes = [];
    dynamicEachAttrs = [];
    factory(mixin).eachComputedProperty(function(field, meta) {
        if (meta.validateEach) {
            dynamicEachAttrs.push(field);
            attributes.push({field: field, options: meta.options});
        }
    });
    return attributes;
}

function attrs(mixin) {
    var attributes = [];
    factory(mixin).eachComputedProperty(function(field) {
        if (field.indexOf("Validation") > 0) {
            attributes.push(field);
        }
    });
    dynamicEachAttrs.forEach(function(field) {
        mixin.get("model").forEach(function(model, index) {
            attributes.push("%@1%@2Validation".fmt(field, index));
        });
    });
    return attributes;
}

function run(value, options, context) {
    context.notifyPropertyChange("valid");
    if (options instanceof RegExp) {
        return options.test(value);
    }
    if (options instanceof Function) {
        return options.apply(context, arguments);
    }
    if (typeof value === "boolean") {
        return value;
    }
    if (typeof value === "string") {
        return value.trim().length > 0;
    }
    return value;
}

var ValidationMixin = Ember.Mixin.create({
    valid: function() {
        var self = this;
        var result = true;
        attrs(this).forEach(function(attr) {
            result = self.get(attr) && result;
        });
        return result;
    }.property(),
    each: function() {
        var self = this;
        eachAttrs(this).forEach(function(attr) {
            self.get("model").forEach(function(obj, index) {
                self.notifyPropertyChange("valid");
                Ember.defineProperty(self, "%@1%@2Validation".fmt(attr.field, index), Ember.computed(function() {
                    if(self.get("model").objectAt(index)) {
                        var value = self.get("model").objectAt(index).getWithDefault(attr.field, "");
                        return run(value, attr.options, self, index);
                    }
                }).property("model.@each." + attr.field));
            });
        });
    }.observes("model.@each.isDone")
});

var validate = function(field, options) {
    return function() {
        var value = this.getWithDefault(field, "");
        return run(value, options, this);
    }.property(field);
};

var validateEach = function(field, options) {
    return function() {
        return true;
    }.property().meta({validateEach: true, options: options});
};

export { ValidationMixin, validate, validateEach };
<commit_msg>[Enhancement]: moved the notifyPropertyChange in each method<commit_after>import Ember from "ember";

var dynamicEachAttrs = [];

function factory(mixin) {
    return mixin.get("constructor.ClassMixin.ownerConstructor");
}

function eachAttrs(mixin) {
    var attributes = [];
    dynamicEachAttrs = [];
    factory(mixin).eachComputedProperty(function(field, meta) {
        if (meta.validateEach) {
            dynamicEachAttrs.push(field);
            attributes.push({field: field, options: meta.options});
        }
    });
    return attributes;
}

function attrs(mixin) {
    var attributes = [];
    factory(mixin).eachComputedProperty(function(field) {
        if (field.indexOf("Validation") > 0) {
            attributes.push(field);
        }
    });
    dynamicEachAttrs.forEach(function(field) {
        mixin.get("model").forEach(function(model, index) {
            attributes.push("%@1%@2Validation".fmt(field, index));
        });
    });
    return attributes;
}

function run(value, options, context) {
    context.notifyPropertyChange("valid");
    if (options instanceof RegExp) {
        return options.test(value);
    }
    if (options instanceof Function) {
        return options.apply(context, arguments);
    }
    if (typeof value === "boolean") {
        return value;
    }
    if (typeof value === "string") {
        return value.trim().length > 0;
    }
    return value;
}

var ValidationMixin = Ember.Mixin.create({
    valid: function() {
        var self = this;
        var result = true;
        attrs(this).forEach(function(attr) {
            result = self.get(attr) && result;
        });
        return result;
    }.property(),
    each: function() {
        var self = this;
        this.notifyPropertyChange("valid");
        eachAttrs(this).forEach(function(attr) {
            self.get("model").forEach(function(obj, index) {
                Ember.defineProperty(self, "%@1%@2Validation".fmt(attr.field, index), Ember.computed(function() {
                    if(self.get("model").objectAt(index)) {
                        var value = self.get("model").objectAt(index).getWithDefault(attr.field, "");
                        return run(value, attr.options, self, index);
                    }
                }).property("model.@each." + attr.field));
            });
        });
    }.observes("model.@each.isDone")
});

var validate = function(field, options) {
    return function() {
        var value = this.getWithDefault(field, "");
        return run(value, options, this);
    }.property(field);
};

var validateEach = function(field, options) {
    return function() {
        return true;
    }.property().meta({validateEach: true, options: options});
};

export { ValidationMixin, validate, validateEach };
<|endoftext|>


<commit_before>/**
  @module ember-jsonapi-resources
  @submodule resource
**/

import Ember from 'ember';
import { pluralize, singularize } from 'ember-inflector';

/**
  A Resource class to create JSON API resource objects

  See <http://jsonapi.org/format/#document-resource-objects>

  @class Resource
  @requires Ember.Inflector
  @static
*/
const Resource = Ember.Object.extend({

  /**
    The service object for the entity (adapter with cache and serializer)

    @property service
    @type Object
    @required
  */
  service: null,

  /**
    Extending Prototypes Must define a `type` value for the entity, e.g. `posts`

    @property type
    @type String
    @required
  */
  type: null,

  /**
    Persisted resource ID value

    @property id
    @type String
    @required
  */
  id: null,

  /**
    An optional `attributes` property of for a JSON API Resource object, setup in create()

    This object will keep the values from the response object and may be mutable
    Use this as a refence for creating computed properties

    For example the `attr()` helper sets up a properties based on this content

    @protected
    @property attributes
    @type Object
  */
  attributes: null,

  /**
    An optional `relationships` property of for a JSON API Resource object, setup in create()

    @protected
    @property relationships
    @type Object
  */
  relationships: null,

  /**
    An optional `links` property of for a JSON API Resource object, setup in create()

    @protected
    @property links
    @type Object
  */
  links: null,

  /**
    An optional property of for a JSON API Resource object, setup in create()

    @protected
    @property meta
    @type Object
  */
  meta: null,

  /**
    Hash of attributes for changed/previous values

    @private
    @property _attributes
    @type Object
  */
  _attributes: null,

  /**
    Flag for new instance, e.g. not peristed

    @property isNew
    @type Boolean
  */
  isNew: false,

  /**
    Custom `toString` method used for clarity that the instance is a JSON API Resource kind of object

    @method toString
  */
  toString() {
    let type = this.get('type') || 'null';
    let id = this.get('id') || 'null';
    return `[JSONAPIResource|${type}:${id}]`;
  },

  /**
    Adds related links object on the relationship hash

    @method addRelationship
    @param {String} related - resource name
    @param {String} id
  */
  addRelationship(related, id) {
    const key = ['relationships', related, 'data'].join('.');
    let data = this.get(key);
    const type = pluralize(related);
    const linkage = { type: type, id: id };
    if (Array.isArray(data)) {
      data.pushObject(linkage);
    } else {
      data = linkage;
    }
    return this.set(key, data);
  },

  /**
    Remove related links object on the relationship hash to have `null` data

    @method removeRelationship
    @param {String} related - resource name
    @param {String} id
  */
  removeRelationship(related, id) {
    let relation = this.get('relationships.' + related);
    if (Array.isArray(relation.data)) {
      for (let i = 0; i < relation.data.length; i++) {
        if (relation.data[i].id === id) {
          relation.data.splice(i, 1);
          break;
        }
      }
    } else if (typeof relation === 'object') {
      relation.data = null;
    }
  },

  /**
    @method changedAttributes
    @return {Object} the changed attributes
  */
  changedAttributes: Ember.computed('attributes', {
    get: function () {
      const attrs = {};
      for (let key in this._attributes) {
        if (this._attributes.hasOwnProperty(key)) {
          if (this._attributes[key].changed !== this._attributes[key].previous) {
            attrs[key] = this._attributes[key].changed;
          }
        }
      }
      return attrs;
    }
  }).volatile(),

  /**
    @method previousAttributes
    @return {Object} the previous attributes
  */
  previousAttributes: Ember.computed('attributes', {
    get: function () {
      const attrs = {};
      for (let key in this._attributes) {
        if (this._attributes.hasOwnProperty(key)) {
          if (this._attributes[key].changed !== this._attributes[key].previous) {
            attrs[key] = this._attributes[key].previous;
          }
        }
      }
      return attrs;
    }
  }).volatile(),

  /**
    Initialize events to communicate with the service object, listen for `didUpdateResource`

    @method initEvents
  */
  initEvents: Ember.on('init', function () {
    const service = this.get('service');
    if (service) {
      service.on('didUpdateResource', this, this.didUpdateResource);
    }
  }),

  /**
    Handler for `didUpdateResource` event, resets private _attributes used for changed/previous tracking

    @method didUpdateResource
    @param json the updated data for the resource
  */
  didUpdateResource(json) {
    if (this.get('id') !== json.id) { return; }
    this.setProperties(json);
    for (let attr in this._attributes) {
      if (this._attributes.hasOwnProperty(attr)) {
        delete this._attributes[attr];
      }
    }
  },

  /**
    A local cache duration, to minimize duplicate fetch requests

    @property cacheDuration
    @type Number
  */
  cacheDuration: /* minutes */ 7 * /* seconds */ 60 * /* milliseconds */ 1000,

  /**
    @property isCacheExpired
    @type Boolean
  */
  isCacheExpired: Ember.computed('meta.timeStamps.local', 'cacheDuration', function () {
    const localTime = this.get('meta.timeStamps.local');
    const expiresTime = localTime + this.get('cacheDuration');
    return (localTime) ? Date.now() >= expiresTime : false;
  })
});

Resource.reopenClass({
  /**
    To protect the JSON API Resource properties for attributes, links and
    relationships these objects are setup during create(). This has to be
    defined since the attr() helper needs to have new objects for each instance,
    to project from keeping a reference on the prototype.

    The create method should only be called after looking up a factory from the
    container, for example in a route's model hook:

    ```
    model() {
      return this.container.lookupFactory('model:posts').create({
        attributes: {
          title: 'The JSON API 1.0 Spec Rocks!'
        }
      });
    }
    ```

    The create method uses the container to lookup the factory's prototype and
    find the computed properties used for relations to setup the relationships
    for the Resource instance you create. Calling Resource#create without using
    the factory lookup will result in an instance without a reference to the
    application's container and you will have to manually setup the relationships
    object prior to adding a relationship.

    @method create
    @returns {Resource} instance with protected objects:
      `attributes`, `links` and `relationships`
  */
  create(properties) {
    const prototype = {};
    const attrs = Ember.String.w('_attributes attributes links meta relationships');
    for (let i = 0; i < attrs.length; i++) {
      prototype[attrs[i]] = {};
    }
    const instance = this._super(prototype);
    if (properties) {
      instance.setProperties(properties);
    }
    let type = instance.get('type');
    let msg = (type) ? Ember.String.capitalize(singularize(type)) : 'Resource';
    if (!type) {
      Ember.Logger.warn(msg + '#create called, instead you should first use ' + msg + '.extend({type:"entity"})');
    }
    let factory = 'model:' + type;
    if (instance.container) {
      factory = instance.container.lookupFactory(factory);
      let proto = factory.proto();
      factory.eachComputedProperty(function(prop) {
        if (proto[prop] && proto[prop]._meta && typeof proto[prop]._meta === 'object') {
          if (proto[prop]._meta.kind === 'hasOne') {
            setupRelationship.call(instance, prop);
          } else if (proto[prop]._meta.kind === 'hasMany') {
            setupRelationship.call(instance, prop, Ember.A([]));
          }
        }
      });
    } else {
      msg += '#create should only be called from a container lookup (relationships not setup) ';
      msg += 'use this.container.lookupFactory("' + factory + '").create() instead.';
      Ember.Logger.warn(msg);
    }
    return instance;
  }
});

export default Resource;

/**
  Helper to setup computed property for resource attributes

  @method attr
*/
export function attr(type, mutable = true) {
  const _mutable = mutable;
  return Ember.computed('attributes', {
    get: function (key) {
      assertDasherizedAttr(key);
      return this.get('attributes.' + key);
    },

    set: function (key, value) {
      const lastValue = this.get('attributes.' + key);
      if (!_mutable) { return lastValue; }
      if (value === lastValue) { return value; }
      this.set('attributes.' + key, value);
      if (!this.get('isNew')) {
        this._attributes[key] = this._attributes[key] || {};
        this._attributes[key].changed = value;
        this._attributes[key].previous = lastValue;
        const service = this.get('service');
        if (service) {
          service.trigger('attributeChanged', this);
        }
      }
      return this.get('attributes.' + key);
    }
  });
}

function assertDasherizedAttr(name) {
  try {
    let attrName = Ember.String.dasherize(name);
    let msg = "Attributes are recommended to use dasherized names, e.g `'"+ attrName +"': attr()`";
    msg += ", instead of `"+ name +": attr()`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

/**
  Mixin for creating promise proxy objects for related resources

  @class RelatedProxyUtil
  @static
*/
const RelatedProxyUtil = Ember.Object.extend({

  /**
    Checks for required `relationship` property

    @method init
  */
  init: function () {
    this._super();
    if (typeof this.get('relationship') !== 'string') {
      throw new Error('RelatedProxyUtil#init expects `relationship` property to exist.');
    }
    return this;
  },

  /**
    The name of the relationship

    @property resource
    @type String
    @required
  */
  relationship: null,

  /**
    Proxy for the requested relation, resolves w/ content from fulfilled promise

    @method createProxy
    @param {Resource} resource
    @param {Ember.ObjectProxy|Ember.ArrayProxy} proxyFactory
    @return {PromiseProxy} proxy
  */
  createProxy: function (resource, type, proxyFactory) {
    const relation = this.get('relationship');
    const url = this.proxyUrl(resource, relation);
    const service = resource.container.lookup('service:' + pluralize(type));
    let promise = this.promiseFromCache(resource, relation, service);
    promise = promise || service.findRelated(type, url);
    let proxy = proxyFactory.extend(Ember.PromiseProxyMixin, {
      'promise': promise, 'type': relation
    });
    proxy = proxy.create();
    proxy.then(
      function (resources) {
        proxy.set('content', resources);
      },
      function (error) {
        console.error(error);
        throw error;
      }
    );
    return proxy;
  },

  /**
    Proxy url to fetch for the resource's relation

    @method proxyUrl
    @param {Resource} resource
    @param {String} relation
    @return {PromiseProxy} proxy
  */
  proxyUrl(resource, relation) {
    const related = linksPath(relation);
    const url = resource.get(related);
    if (typeof url !== 'string') {
      throw new Error('RelatedProxyUtil#_proxyUrl expects `model.'+ related +'` property to exist.');
    }
    return url;
  },

  /**
    Lookup relation from service cache and pomisify result

    @method promiseFromCache
    @param {Resource} resource
    @param {String} relation
    @param {Object} service
    @return {Promise|null}
  */
  promiseFromCache(resource, relation, service) {
    let data = resource.get('relationships.' + relation + '.data');
    if (!data) { return; }
    let content = Ember.A([]), found;
    if (Array.isArray(data)) {
      for (let i = 0; i < data.length; i++) {
        found = this.serviceCacheLookup(service, data[i]);
        if (found) {
          content.push(found);
        }
      }
      content = (data.length && data.length === content.length) ? content : null;
    } else {
      content = this.serviceCacheLookup(service, data);
    }
    return (content) ? Ember.RSVP.Promise.resolve(content) : null;
  },

  /**
    Lookup data in service cache

    @method serviceCacheLookup
    @param {Object} service
    @param {Object} data
    @return {Resource|undefined}
  */
  serviceCacheLookup(service, data) {
    return (typeof data === 'object' && data.id) ? service.cacheLookup(data.id) : undefined;
  }
});

function linksPath(relation) {
  return ['relationships', relation, 'links', 'related'].join('.');
}

/**
  Helper to setup a has one relationship to another resource,
  type is optional and defaults to relation

  @method hasOne
  @param {String} relation
  @param {String} type
*/
export function hasOne(relation, type) {
  assertDasherizedHasOneRelation(relation);
  const util = RelatedProxyUtil.create({'relationship': relation});
  const path = linksPath(relation);
  type = (type ? type : relation);
  return Ember.computed(path, function () {
    return util.createProxy(this, type, Ember.ObjectProxy);
  }).meta({relation: relation, type: type, kind: 'hasOne'});
}

function assertDasherizedHasOneRelation(name) {
  try {
    let relationName = Ember.String.dasherize(name);
    let msg = " are recommended to use dasherized names, e.g `hasOne('"+ relationName +"')`";
    msg += ", instead of `hasOne('"+ name +"')`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

/**
  Helper to setup a has many relationship to another resource,
  type is optional and defaults to relation

  @method hasMany
  @param {String} relation
  @param {String} type
*/
export function hasMany(relation, type) {
  assertDasherizedHasManyRelation(relation);
  const util = RelatedProxyUtil.create({'relationship': relation});
  const path = linksPath(relation);
  type = (type ? type : relation);
  return Ember.computed(path, function () {
    return util.createProxy(this, type, Ember.ArrayProxy);
  }).meta({relation: relation, type: type, kind: 'hasMany'});
}

function assertDasherizedHasManyRelation(name) {
  try {
    let relationName = Ember.String.dasherize(name);
    let msg = " are recommended to use dasherized names, e.g `hasMany('"+ relationName +"')`";
    msg += ", instead of `hasMany('"+ name +"')`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

function setupRelationship(relation, data = null) {
  if (!this.relationships[relation]) {
    this.relationships[relation] = { links: {}, data: data };
  }
  if (!this.relationships[relation].links) {
    this.relationships[relation].links = {};
  }
  if (!this.relationships[relation].data) {
    this.relationships[relation].data = data;
  }
}

function isDasherized(name) {
  return (Ember.String.dasherize(name) === name);
}
<commit_msg>Updated the format of relationships<commit_after>/**
  @module ember-jsonapi-resources
  @submodule resource
**/

import Ember from 'ember';
import { pluralize, singularize } from 'ember-inflector';

/**
  A Resource class to create JSON API resource objects

  See <http://jsonapi.org/format/#document-resource-objects>

  @class Resource
  @requires Ember.Inflector
  @static
*/
const Resource = Ember.Object.extend({

  /**
    The service object for the entity (adapter with cache and serializer)

    @property service
    @type Object
    @required
  */
  service: null,

  /**
    Extending Prototypes Must define a `type` value for the entity, e.g. `posts`

    @property type
    @type String
    @required
  */
  type: null,

  /**
    Persisted resource ID value

    @property id
    @type String
    @required
  */
  id: null,

  /**
    An optional `attributes` property of for a JSON API Resource object, setup in create()

    This object will keep the values from the response object and may be mutable
    Use this as a refence for creating computed properties

    For example the `attr()` helper sets up a properties based on this content

    @protected
    @property attributes
    @type Object
  */
  attributes: null,

  /**
    An optional `relationships` property of for a JSON API Resource object, setup in create()

    @protected
    @property relationships
    @type Object
  */
  relationships: null,

  /**
    An optional `links` property of for a JSON API Resource object, setup in create()

    @protected
    @property links
    @type Object
  */
  links: null,

  /**
    An optional property of for a JSON API Resource object, setup in create()

    @protected
    @property meta
    @type Object
  */
  meta: null,

  /**
    Hash of attributes for changed/previous values

    @private
    @property _attributes
    @type Object
  */
  _attributes: null,

  /**
    Flag for new instance, e.g. not peristed

    @property isNew
    @type Boolean
  */
  isNew: false,

  /**
    Custom `toString` method used for clarity that the instance is a JSON API Resource kind of object

    @method toString
  */
  toString() {
    let type = this.get('type') || 'null';
    let id = this.get('id') || 'null';
    return `[JSONAPIResource|${type}:${id}]`;
  },

  /**
    Adds related links object on the relationship hash

    @method addRelationship
    @param {String} related - resource name
    @param {String} id
  */
  addRelationship(related, id) {
    const key = ['relationships', related, 'data'].join('.');
    let data = this.get(key);
    const type = pluralize(related);
    const linkage = { type: type, id: id };
    if (Array.isArray(data)) {
      data.pushObject(linkage);
    } else {
      data = linkage;
    }
    return this.set(key, data);
  },

  /**
    Remove related links object on the relationship hash to have `null` data

    @method removeRelationship
    @param {String} related - resource name
    @param {String} id
  */
  removeRelationship(related, id) {
    let relation = this.get('relationships.' + related);
    if (Array.isArray(relation.data)) {
      for (let i = 0; i < relation.data.length; i++) {
        if (relation.data[i].id === id) {
          relation.data.splice(i, 1);
          break;
        }
      }
    } else if (typeof relation === 'object') {
      relation.data = null;
    }
  },

  /**
    @method changedAttributes
    @return {Object} the changed attributes
  */
  changedAttributes: Ember.computed('attributes', {
    get: function () {
      const attrs = {};
      for (let key in this._attributes) {
        if (this._attributes.hasOwnProperty(key)) {
          if (this._attributes[key].changed !== this._attributes[key].previous) {
            attrs[key] = this._attributes[key].changed;
          }
        }
      }
      return attrs;
    }
  }).volatile(),

  /**
    @method previousAttributes
    @return {Object} the previous attributes
  */
  previousAttributes: Ember.computed('attributes', {
    get: function () {
      const attrs = {};
      for (let key in this._attributes) {
        if (this._attributes.hasOwnProperty(key)) {
          if (this._attributes[key].changed !== this._attributes[key].previous) {
            attrs[key] = this._attributes[key].previous;
          }
        }
      }
      return attrs;
    }
  }).volatile(),

  /**
    Initialize events to communicate with the service object, listen for `didUpdateResource`

    @method initEvents
  */
  initEvents: Ember.on('init', function () {
    const service = this.get('service');
    if (service) {
      service.on('didUpdateResource', this, this.didUpdateResource);
    }
  }),

  /**
    Handler for `didUpdateResource` event, resets private _attributes used for changed/previous tracking

    @method didUpdateResource
    @param json the updated data for the resource
  */
  didUpdateResource(json) {
    if (this.get('id') !== json.id) { return; }
    this.setProperties(json);
    for (let attr in this._attributes) {
      if (this._attributes.hasOwnProperty(attr)) {
        delete this._attributes[attr];
      }
    }
  },

  /**
    A local cache duration, to minimize duplicate fetch requests

    @property cacheDuration
    @type Number
  */
  cacheDuration: /* minutes */ 7 * /* seconds */ 60 * /* milliseconds */ 1000,

  /**
    @property isCacheExpired
    @type Boolean
  */
  isCacheExpired: Ember.computed('meta.timeStamps.local', 'cacheDuration', function () {
    const localTime = this.get('meta.timeStamps.local');
    const expiresTime = localTime + this.get('cacheDuration');
    return (localTime) ? Date.now() >= expiresTime : false;
  })
});

Resource.reopenClass({
  /**
    To protect the JSON API Resource properties for attributes, links and
    relationships these objects are setup during create(). This has to be
    defined since the attr() helper needs to have new objects for each instance,
    to project from keeping a reference on the prototype.

    The create method should only be called after looking up a factory from the
    container, for example in a route's model hook:

    ```
    model() {
      return this.container.lookupFactory('model:posts').create({
        attributes: {
          title: 'The JSON API 1.0 Spec Rocks!'
        }
      });
    }
    ```

    The create method uses the container to lookup the factory's prototype and
    find the computed properties used for relations to setup the relationships
    for the Resource instance you create. Calling Resource#create without using
    the factory lookup will result in an instance without a reference to the
    application's container and you will have to manually setup the relationships
    object prior to adding a relationship.

    @method create
    @returns {Resource} instance with protected objects:
      `attributes`, `links` and `relationships`
  */
  create(properties) {
    const prototype = {};
    const attrs = Ember.String.w('_attributes attributes links meta relationships');
    for (let i = 0; i < attrs.length; i++) {
      prototype[attrs[i]] = {};
    }
    const instance = this._super(prototype);
    if (properties) {
      instance.setProperties(properties);
    }
    let type = instance.get('type');
    let msg = (type) ? Ember.String.capitalize(singularize(type)) : 'Resource';
    if (!type) {
      Ember.Logger.warn(msg + '#create called, instead you should first use ' + msg + '.extend({type:"entity"})');
    }
    let factory = 'model:' + type;
    if (instance.container) {
      factory = instance.container.lookupFactory(factory);
      let proto = factory.proto();
      factory.eachComputedProperty(function(prop) {
        if (proto[prop] && proto[prop]._meta && typeof proto[prop]._meta === 'object') {
          if (proto[prop]._meta.kind === 'hasOne') {
            setupRelationship.call(instance, prop);
          } else if (proto[prop]._meta.kind === 'hasMany') {
            setupRelationship.call(instance, prop, Ember.A([]));
          }
        }
      });
    } else {
      msg += '#create should only be called from a container lookup (relationships not setup) ';
      msg += 'use this.container.lookupFactory("' + factory + '").create() instead.';
      Ember.Logger.warn(msg);
    }
    return instance;
  }
});

export default Resource;

/**
  Helper to setup computed property for resource attributes

  @method attr
*/
export function attr(type, mutable = true) {
  const _mutable = mutable;
  return Ember.computed('attributes', {
    get: function (key) {
      assertDasherizedAttr(key);
      return this.get('attributes.' + key);
    },

    set: function (key, value) {
      const lastValue = this.get('attributes.' + key);
      if (!_mutable) { return lastValue; }
      if (value === lastValue) { return value; }
      this.set('attributes.' + key, value);
      if (!this.get('isNew')) {
        this._attributes[key] = this._attributes[key] || {};
        this._attributes[key].changed = value;
        this._attributes[key].previous = lastValue;
        const service = this.get('service');
        if (service) {
          service.trigger('attributeChanged', this);
        }
      }
      return this.get('attributes.' + key);
    }
  });
}

function assertDasherizedAttr(name) {
  try {
    let attrName = Ember.String.dasherize(name);
    let msg = "Attributes are recommended to use dasherized names, e.g `'"+ attrName +"': attr()`";
    msg += ", instead of `"+ name +": attr()`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

/**
  Mixin for creating promise proxy objects for related resources

  @class RelatedProxyUtil
  @static
*/
const RelatedProxyUtil = Ember.Object.extend({

  /**
    Checks for required `relationship` property

    @method init
  */
  init: function () {
    this._super();
    if (typeof this.get('relationship') !== 'string') {
      throw new Error('RelatedProxyUtil#init expects `relationship` property to exist.');
    }
    return this;
  },

  /**
    The name of the relationship

    @property resource
    @type String
    @required
  */
  relationship: null,

  /**
    Proxy for the requested relation, resolves w/ content from fulfilled promise

    @method createProxy
    @param {Resource} resource
    @param {Ember.ObjectProxy|Ember.ArrayProxy} proxyFactory
    @return {PromiseProxy} proxy
  */
  createProxy: function (resource, type, proxyFactory) {
    const relation = this.get('relationship');
    const url = this.proxyUrl(resource, relation);
    const service = resource.container.lookup('service:' + pluralize(type));
    let promise = this.promiseFromCache(resource, relation, service);
    promise = promise || service.findRelated(type, url);
    let proxy = proxyFactory.extend(Ember.PromiseProxyMixin, {
      'promise': promise, 'type': relation
    });
    proxy = proxy.create();
    proxy.then(
      function (resources) {
        proxy.set('content', resources);
      },
      function (error) {
        console.error(error);
        throw error;
      }
    );
    return proxy;
  },

  /**
    Proxy url to fetch for the resource's relation

    @method proxyUrl
    @param {Resource} resource
    @param {String} relation
    @return {PromiseProxy} proxy
  */
  proxyUrl(resource, relation) {
    const related = linksPath(relation);
    const url = resource.get(related);
    if (typeof url !== 'string') {
      throw new Error('RelatedProxyUtil#_proxyUrl expects `model.'+ related +'` property to exist.');
    }
    return url;
  },

  /**
    Lookup relation from service cache and pomisify result

    @method promiseFromCache
    @param {Resource} resource
    @param {String} relation
    @param {Object} service
    @return {Promise|null}
  */
  promiseFromCache(resource, relation, service) {
    let data = resource.get('relationships.' + relation + '.data');
    if (!data) { return; }
    let content = Ember.A([]), found;
    if (Array.isArray(data)) {
      for (let i = 0; i < data.length; i++) {
        found = this.serviceCacheLookup(service, data[i]);
        if (found) {
          content.push(found);
        }
      }
      content = (data.length && data.length === content.length) ? content : null;
    } else {
      content = this.serviceCacheLookup(service, data);
    }
    return (content) ? Ember.RSVP.Promise.resolve(content) : null;
  },

  /**
    Lookup data in service cache

    @method serviceCacheLookup
    @param {Object} service
    @param {Object} data
    @return {Resource|undefined}
  */
  serviceCacheLookup(service, data) {
    return (typeof data === 'object' && data.id) ? service.cacheLookup(data.id) : undefined;
  }
});

function linksPath(relation) {
  return ['relationships', relation, 'links', 'related'].join('.');
}

/**
  Helper to setup a has one relationship to another resource,
  type is optional and defaults to relation

  @method hasOne
  @param {Object|String} relation
*/
export function hasOne(relation) {
  let relationName = relation;
  if (typeof relation !== 'string') {
    relationName = relation.resource;
  }
  const util = RelatedProxyUtil.create({'relationship': relationName});
  const path = linksPath(relationName);
  assertDasherizedHasOneRelation(relationName);
  const type = (relation.type ? relation.type : relationName);
  return Ember.computed(path, function () {
    return util.createProxy(this, type, Ember.ObjectProxy);
  }).meta({relation: relationName, type: type, kind: 'hasOne'});
}

function assertDasherizedHasOneRelation(name) {
  try {
    let relationName = Ember.String.dasherize(name);
    let msg = " are recommended to use dasherized names, e.g `hasOne('"+ relationName +"')`";
    msg += ", instead of `hasOne('"+ name +"')`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

/**
  Helper to setup a has many relationship to another resource,
  type is optional and defaults to relation

  @method hasMany
  @param {String|Object} relation
*/
export function hasMany(relation) {
  let relationName = relation;
  if (typeof relation !== 'string') {
    relationName = relation.resource;
  }
  const util = RelatedProxyUtil.create({'relationship': relationName});
  const path = linksPath(relationName);
  assertDasherizedHasManyRelation(relationName);
  const type = (relation.type ? relation.type : relationName);
  return Ember.computed(path, function () {
    return util.createProxy(this, type, Ember.ArrayProxy);
  }).meta({relation: relationName, type: type, kind: 'hasMany'});
}

function assertDasherizedHasManyRelation(name) {
  try {
    let relationName = Ember.String.dasherize(name);
    let msg = " are recommended to use dasherized names, e.g `hasMany('"+ relationName +"')`";
    msg += ", instead of `hasMany('"+ name +"')`";
    Ember.assert(msg, isDasherized(name));
  } catch(e) {
    console.warn(e.message);
  }
}

function setupRelationship(relation, data = null) {
  if (!this.relationships[relation]) {
    this.relationships[relation] = { links: {}, data: data };
  }
  if (!this.relationships[relation].links) {
    this.relationships[relation].links = {};
  }
  if (!this.relationships[relation].data) {
    this.relationships[relation].data = data;
  }
}

function isDasherized(name) {
  return (Ember.String.dasherize(name) === name);
}
<|endoftext|>