<commit_before>'use strict';

var _ = require('lodash');
var util = require('util');
var BaseRelation = require('./base');
var BluebirdPromise = require('bluebird');
var attr = require('../model/attr').fn;
var Config = require('./belongs_to_config');
var Associations = require('./belongs_to_associations');
var Prefetch = require('./belongs_to_prefetch');

_.str = require('underscore.string');

/**
 * The belongs to property for models.
 *
 * Documentation forthcoming.
 *
 * For example:
 *
 *     var Article = Model.extend({
 *       author: belongsTo('user', { foreignKey: 'user_id', primaryKey: 'id' })
 *     });
 *
 * @since 1.0
 * @public
 * @param {Class|String} [relatedModel] The model to which this relates.
 * @param {Object} [options]
 * @param {String} [options.inverse] The name of the inverse relationship.
 * @param {String} [options.primaryKey] The name of the primary key in the
 * relationship.
 * @param {String} [options.foreignKey] The name of the foreign key in the
 * relationship.
 * @function Database#belongsTo
 */

/**
 * The belongs to property for models.
 *
 * @since 1.0
 * @protected
 * @constructor BelongsTo
 * @extends BaseRelation
 * @see {@link Database#belongsTo}
 */
var BelongsTo = BaseRelation.extend();

BelongsTo.reopen(Config);
BelongsTo.reopen(Associations);
BelongsTo.reopen(Prefetch);

BelongsTo.reopen(/** @lends BelongsTo# */ {
  init: function() {
    this._super.apply(this, arguments);
    this._itemCacheKey = '_' + this._name;
    this._itemObjectsQueryCacheKey =
      '_' + _.str.camelize(this._name + '_objectsQueryCache');
  },

  /**
   * Override of {@link BaseRelation#template}.
   *
   * @since 1.0
   * @protected
   * @method
   * @see {@link BaseRelation#template}
   */
  template: function(key) {
    return this._super(key)
      .replace('<foreignKey>', this.foreignKeyProperty);
  }

});

/**
 * This wrapper method is used to immediately invalidate the `objectsQuery`
 * cache when performing any sort of update to related objects.
 *
 * The resulting wrapper function expects to be called with the first argument
 * being the model instance on which it is operating. It will use this to
 * invalidate the `objectsQuery` cache. It also expects that it will be used as
 * an instance method on a `BelongsTo` relation.
 *
 * @since 1.0
 * @private
 * @method BelongsTo~invalidatesQuery
 * @param {Function} fn The function to wrap.
 * @return {Function} The wrapper function.
 * @see {@link BelongsTo#_cacheObjectsQuery}
 */
var invalidatesQuery = function(fn) {
  return function(instance) {
    this._cacheObjectsQuery(instance, undefined);
    return fn.apply(this, arguments);
  };
};

BelongsTo.reopen(/** @lends BelongsTo# */ {

  /**
   * The identity property for this relation.
   *
   * This property allows access to the underlying relation object, allowing
   * you to access details about how the relation was created/configured. This
   * method is currently only intended for internal use, but if you have a
   * reason for it to be documented publicly, please create an issue on GitHub
   * and let us know.
   *
   * It is accessible on an individual model via `<singular>Relation`. For
   * instance, an article that belongs to an author would cause this method to
   * get triggered via `article.authorRelation`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  identity: function(/*instance*/) {
    return this;
  },

  /**
   * The item property for this relation.
   *
   * This property allows access to the cached object that has been fetched
   * for a specific model in a given relation. Before the cache has been
   * filled, accessing this property will throw an exception.
   *
   * It is accessible on an individual model via `<singular>` and via
   * `get<Singular>`. For instance, an article that belongs to an author would
   * cause this method to get triggered via `article.author` or
   * `article.getAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  item: function(instance) {
    var result = this._related(instance);
    if (result === undefined) {
      throw new Error(util.format('The relation "%s" has not yet been ' +
        'loaded.', this._name));
    }
    return result;
  },

  /**
   * Get the {@link BelongsTo#item} cache value.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array} value The new value for the item cache.
   */
  _related: function(instance) {
    return instance[this._itemCacheKey];
  },

  /**
   * Update the {@link BelongsTo#item} cache to contain a new value. This will
   * always mark the cache as full. To clear the cache, use
   * {@link BelongsTo#_clearItem}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array} value The new value for the item cache.
   */
  _setRelated: function(instance, value) {
    instance[this._itemCacheKey] = value || null;
  },

  /**
   * The item property setter for this relation.
   *
   * This property allows altering the associated object of a specific model in
   * a given relation.
   *
   * It is accessible on an individual model via assignment with `<singular>`
   * and via `set<Singular>`. For instance, an article that belongs to an
   * author would cause this method to get triggered via
   * `article.author = '...'` or `article.setAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  set: invalidatesQuery(function(instance, value) {
    var current = this._related(instance);
    if (current) { this.disassociate(instance, current); }
    if (value) { this.associate(instance, value); }
  }),

  /**
   * The objects query property for this relation.
   *
   * This property allows you access to a query which you can use to fetch the
   * object for a relation on a given model and/or to configure the query to
   * build a new query with more restrictive conditions.
   *
   * When you fetch the object, the result will be cached and accessible via
   * the {@link BelongsTo#collection} (if loaded).
   *
   * The value of this property is a query object that is cached so it will
   * always be the exact same query object (see exceptions below). This allows
   * multiple fetches to simply return the query's cached result. A simple
   * {@link BaseQuery#clone} of the query will ensure that it is always
   * performing a new query in the database.
   *
   * The cache of this property will be invalided (and the resulting object a
   * new query object) when changes are made to the relation.
   *
   * It is currently not accessible on an individual model.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   */
  objectsQuery: function(instance) {
    var cacheName = this._itemObjectsQueryCacheKey;
    if (!instance[cacheName]) {
      var where = _.object([[this.primaryKey, instance.attrs[this.foreignKey]]]);
      var cacheResult = _.bind(this.associateFetchedObjects, this, instance);
      instance[cacheName] = this._relatedModel
        .objects.where(where).limit(1)
        .on('result', cacheResult);
    }
    return instance[cacheName];
  },

  /**
   * Update the {@link BelongsTo#objectsQuery} cache to contain a new value.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Object} value The new value.
   */
  _cacheObjectsQuery: function(instance, value) {
    instance[this._itemObjectsQueryCacheKey] = value;
  },

  /**
   * The create object method for this relation.
   *
   * This simply creates a new object that has the appropriate relation data
   * already set.
   *
   * This method invalidates the {@link BelongsTo#objectsQuery} cache and sets
   * the created object as the {@link BelongsTo#item}.
   *
   * It is accessible on an individual model via `create<Singular>`. For
   * instance, an article that belongs to an author would cause this method to
   * get triggered via `article.createAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {...Object} [args] The arguments to the method.
   * @see {@link BaseRelation#methods}
   */
  create: invalidatesQuery(function(instance) {
    var args = _.rest(arguments);
    var relatedModel = this._relatedModel;
    var result = relatedModel.create.apply(relatedModel, args);
    this.associate(instance, result);
    return result;
  }),

  /**
   * Documentation forthcoming.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  fetch: function(instance) {
    var self = this;
    var result = BluebirdPromise.bind();
    var fk = instance.getAttribute(this.foreignKey);
    if (fk) {
      result = result.then(function() {
        return self.objectsQuery(instance).execute();
      })
      .then(function(result) {
        self.validateFetchedObjects(instance, result);
        return result[0];
      });
    }
    else {
      result = result.then(function() {
        // pretend like we actually did the whole fetch & got back nothing
        self.associateFetchedObjects(instance, [null]);
      });
    }
    return result;
  },

  /**
   * Documentation forthcoming.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   */
  validateFetchedObjects: function(instance, objects) {
    var foreignKey = this.foreignKey;
    var fk = instance.getAttribute(foreignKey);
    if (fk && objects.length === 0) {
      var relatedModel = this._relatedModel;
      var relatedClass = relatedModel.__identity__;
      var relatedName = relatedClass.__name__;
      throw new Error(util.format('Found no %s with %j %d',
        relatedName, foreignKey, fk));
    }
  },

  /**
   * Convenience method for associating objects that have just been fetched
   * from the database. This simply associates the first object in the result
   * set.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array.<Model>} objects The objects to add to the association.
   * @see {@link HasMany~AssociationsMixin}
   */
  associateFetchedObjects: function(instance, objects) {
    this.associate(instance, objects[0], { attrs: false });
  },

  /**
   * Override of the model's save method.
   *
   * This allows the relation to save the related object in response to the
   * instance on which it is operating being saved.
   *
   * During the process of saving the related object, it checks to see if it
   * was a newly inserted object & will re-assign the foreign key (via the
   * foreign key property) once that object has been saved & assigned an id.
   *
   * It is accessible on an individual model via `save`, and as such is an
   * override of the builtin save method.
   *
   * As with all other relation methods, the naming conventions are set forth
   * in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {...Object} [args] The arguments to the method.
   * @see {@link BaseRelation#methods}
   */
  save: function(instance) {
    var args = _.rest(arguments);
    var _super = instance._super;
    var foreignKey = this.foreignKey;
    var primaryKey = this.primaryKey;
    var related = this._related(instance);
    var resetForeignKey = related && !related.persisted;

    var promise = BluebirdPromise.bind().then(function() {
      return related && related.save();
    });

    if (resetForeignKey) {
      promise = promise.then(function() {
        instance.setAttribute(foreignKey, related[primaryKey]);
      });
    }

    return promise.then(function() {
      return _super.apply(instance, args);
    });
  }
});

BelongsTo.reopenClass(/** @lends BelongsTo */ {
  methods: {
    '<singular>': BaseRelation.property('item', 'set'),
    '<singular>Relation': BaseRelation.property('identity'),
    'get<Singular>': BaseRelation.method('item'),
    'set<Singular>': BaseRelation.method('set'),
    'create<Singular>': BaseRelation.method('create'),
    'fetch<Singular>': BaseRelation.method('fetch'),
    '<foreignKey>': function(relation) {
      return attr(relation.foreignKey, { writable: false });
    },
    'save': BaseRelation.method('save')
  }
});

module.exports = BelongsTo.reopenClass({ __name__: 'BelongsTo' });
<commit_msg>Finished docs for belongs to.<commit_after>'use strict';

var _ = require('lodash');
var util = require('util');
var BaseRelation = require('./base');
var BluebirdPromise = require('bluebird');
var attr = require('../model/attr').fn;
var Config = require('./belongs_to_config');
var Associations = require('./belongs_to_associations');
var Prefetch = require('./belongs_to_prefetch');

_.str = require('underscore.string');

/**
 * The belongs to property for models.
 *
 * For example:
 *
 *     var Article = Model.extend({
 *       author: belongsTo('user', { foreignKey: 'user_id', primaryKey: 'id' })
 *     });
 *
 * @since 1.0
 * @public
 * @param {Class|String} [relatedModel] The model to which this relates.
 * @param {Object} [options]
 * @param {String} [options.inverse] The name of the inverse relationship.
 * @param {String} [options.primaryKey] The name of the primary key in the
 * relationship.
 * @param {String} [options.foreignKey] The name of the foreign key in the
 * relationship.
 * @function Database#belongsTo
 */

/**
 * The belongs to property for models.
 *
 * @since 1.0
 * @protected
 * @constructor BelongsTo
 * @extends BaseRelation
 * @see {@link Database#belongsTo}
 */
var BelongsTo = BaseRelation.extend();

BelongsTo.reopen(Config);
BelongsTo.reopen(Associations);
BelongsTo.reopen(Prefetch);

BelongsTo.reopen(/** @lends BelongsTo# */ {
  init: function() {
    this._super.apply(this, arguments);
    this._itemCacheKey = '_' + this._name;
    this._itemObjectsQueryCacheKey =
      '_' + _.str.camelize(this._name + '_objectsQueryCache');
  },

  /**
   * Override of {@link BaseRelation#template}.
   *
   * @since 1.0
   * @protected
   * @method
   * @see {@link BaseRelation#template}
   */
  template: function(key) {
    return this._super(key)
      .replace('<foreignKey>', this.foreignKeyProperty);
  }

});

/**
 * This wrapper method is used to immediately invalidate the `objectsQuery`
 * cache when performing any sort of update to related objects.
 *
 * The resulting wrapper function expects to be called with the first argument
 * being the model instance on which it is operating. It will use this to
 * invalidate the `objectsQuery` cache. It also expects that it will be used as
 * an instance method on a `BelongsTo` relation.
 *
 * @since 1.0
 * @private
 * @method BelongsTo~invalidatesQuery
 * @param {Function} fn The function to wrap.
 * @return {Function} The wrapper function.
 * @see {@link BelongsTo#_cacheObjectsQuery}
 */
var invalidatesQuery = function(fn) {
  return function(instance) {
    this._cacheObjectsQuery(instance, undefined);
    return fn.apply(this, arguments);
  };
};

BelongsTo.reopen(/** @lends BelongsTo# */ {

  /**
   * The identity property for this relation.
   *
   * This property allows access to the underlying relation object, allowing
   * you to access details about how the relation was created/configured. This
   * method is currently only intended for internal use, but if you have a
   * reason for it to be documented publicly, please create an issue on GitHub
   * and let us know.
   *
   * It is accessible on an individual model via `<singular>Relation`. For
   * instance, an article that belongs to an author would cause this method to
   * get triggered via `article.authorRelation`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  identity: function(/*instance*/) {
    return this;
  },

  /**
   * The item property for this relation.
   *
   * This property allows access to the cached object that has been fetched
   * for a specific model in a given relation. Before the cache has been
   * filled, accessing this property will throw an exception.
   *
   * It is accessible on an individual model via `<singular>` and via
   * `get<Singular>`. For instance, an article that belongs to an author would
   * cause this method to get triggered via `article.author` or
   * `article.getAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  item: function(instance) {
    var result = this._related(instance);
    if (result === undefined) {
      throw new Error(util.format('The relation "%s" has not yet been ' +
        'loaded.', this._name));
    }
    return result;
  },

  /**
   * Get the {@link BelongsTo#item} cache value.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array} value The new value for the item cache.
   */
  _related: function(instance) {
    return instance[this._itemCacheKey];
  },

  /**
   * Update the {@link BelongsTo#item} cache to contain a new value. This will
   * always mark the cache as full. To clear the cache, use
   * {@link BelongsTo#_clearItem}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array} value The new value for the item cache.
   */
  _setRelated: function(instance, value) {
    instance[this._itemCacheKey] = value || null;
  },

  /**
   * The item property setter for this relation.
   *
   * This property allows altering the associated object of a specific model in
   * a given relation.
   *
   * It is accessible on an individual model via assignment with `<singular>`
   * and via `set<Singular>`. For instance, an article that belongs to an
   * author would cause this method to get triggered via
   * `article.author = '...'` or `article.setAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  set: invalidatesQuery(function(instance, value) {
    var current = this._related(instance);
    if (current) { this.disassociate(instance, current); }
    if (value) { this.associate(instance, value); }
  }),

  /**
   * The objects query property for this relation.
   *
   * This property allows you access to a query which you can use to fetch the
   * object for a relation on a given model and/or to configure the query to
   * build a new query with more restrictive conditions.
   *
   * When you fetch the object, the result will be cached and accessible via
   * the {@link BelongsTo#collection} (if loaded).
   *
   * The value of this property is a query object that is cached so it will
   * always be the exact same query object (see exceptions below). This allows
   * multiple fetches to simply return the query's cached result. A simple
   * {@link BaseQuery#clone} of the query will ensure that it is always
   * performing a new query in the database.
   *
   * The cache of this property will be invalided (and the resulting object a
   * new query object) when changes are made to the relation.
   *
   * It is currently not accessible on an individual model.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   */
  objectsQuery: function(instance) {
    var cacheName = this._itemObjectsQueryCacheKey;
    if (!instance[cacheName]) {
      var where = _.object([[this.primaryKey, instance.attrs[this.foreignKey]]]);
      var cacheResult = _.bind(this.associateFetchedObjects, this, instance);
      instance[cacheName] = this._relatedModel
        .objects.where(where).limit(1)
        .on('result', cacheResult);
    }
    return instance[cacheName];
  },

  /**
   * Update the {@link BelongsTo#objectsQuery} cache to contain a new value.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Object} value The new value.
   */
  _cacheObjectsQuery: function(instance, value) {
    instance[this._itemObjectsQueryCacheKey] = value;
  },

  /**
   * The create object method for this relation.
   *
   * This simply creates a new object that has the appropriate relation data
   * already set.
   *
   * This method invalidates the {@link BelongsTo#objectsQuery} cache and sets
   * the created object as the {@link BelongsTo#item}.
   *
   * It is accessible on an individual model via `create<Singular>`. For
   * instance, an article that belongs to an author would cause this method to
   * get triggered via `article.createAuthor`.
   *
   * The naming conventions are set forth in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {...Object} [args] The arguments to the method.
   * @see {@link BaseRelation#methods}
   */
  create: invalidatesQuery(function(instance) {
    var args = _.rest(arguments);
    var relatedModel = this._relatedModel;
    var result = relatedModel.create.apply(relatedModel, args);
    this.associate(instance, result);
    return result;
  }),

  /**
   * Fetch the related object. If there is no foreign key defined on the
   * instance, this method will not actually query the database.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @see {@link BaseRelation#methods}
   */
  fetch: function(instance) {
    var self = this;
    var result = BluebirdPromise.bind();
    var fk = instance.getAttribute(this.foreignKey);
    if (fk) {
      result = result.then(function() {
        return self.objectsQuery(instance).execute();
      })
      .then(function(result) {
        self.validateFetchedObjects(instance, result);
        return result[0];
      });
    }
    else {
      result = result.then(function() {
        // pretend like we actually did the whole fetch & got back nothing
        self.associateFetchedObjects(instance, [null]);
      });
    }
    return result;
  },

  /**
   * Validate fetched objects, ensuring that exactly one object was obtained
   * when the foreign key is set.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   */
  validateFetchedObjects: function(instance, objects) {
    var foreignKey = this.foreignKey;
    var fk = instance.getAttribute(foreignKey);
    if (fk && objects.length === 0) {
      var relatedModel = this._relatedModel;
      var relatedClass = relatedModel.__identity__;
      var relatedName = relatedClass.__name__;
      throw new Error(util.format('Found no %s with %j %d',
        relatedName, foreignKey, fk));
    }
  },

  /**
   * Convenience method for associating objects that have just been fetched
   * from the database. This simply associates the first object in the result
   * set.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {Array.<Model>} objects The objects to add to the association.
   * @see {@link HasMany~AssociationsMixin}
   */
  associateFetchedObjects: function(instance, objects) {
    this.associate(instance, objects[0], { attrs: false });
  },

  /**
   * Override of the model's save method.
   *
   * This allows the relation to save the related object in response to the
   * instance on which it is operating being saved.
   *
   * During the process of saving the related object, it checks to see if it
   * was a newly inserted object & will re-assign the foreign key (via the
   * foreign key property) once that object has been saved & assigned an id.
   *
   * It is accessible on an individual model via `save`, and as such is an
   * override of the builtin save method.
   *
   * As with all other relation methods, the naming conventions are set forth
   * in {@link BelongsTo.methods}.
   *
   * @since 1.0
   * @protected
   * @method
   * @param {Model} instance The model instance on which to operate.
   * @param {...Object} [args] The arguments to the method.
   * @see {@link BaseRelation#methods}
   */
  save: function(instance) {
    var args = _.rest(arguments);
    var _super = instance._super;
    var foreignKey = this.foreignKey;
    var primaryKey = this.primaryKey;
    var related = this._related(instance);
    var resetForeignKey = related && !related.persisted;

    var promise = BluebirdPromise.bind().then(function() {
      return related && related.save();
    });

    if (resetForeignKey) {
      promise = promise.then(function() {
        instance.setAttribute(foreignKey, related[primaryKey]);
      });
    }

    return promise.then(function() {
      return _super.apply(instance, args);
    });
  }
});

BelongsTo.reopenClass(/** @lends BelongsTo */ {
  methods: {
    '<singular>': BaseRelation.property('item', 'set'),
    '<singular>Relation': BaseRelation.property('identity'),
    'get<Singular>': BaseRelation.method('item'),
    'set<Singular>': BaseRelation.method('set'),
    'create<Singular>': BaseRelation.method('create'),
    'fetch<Singular>': BaseRelation.method('fetch'),
    '<foreignKey>': function(relation) {
      return attr(relation.foreignKey, { writable: false });
    },
    'save': BaseRelation.method('save')
  }
});

module.exports = BelongsTo.reopenClass({ __name__: 'BelongsTo' });
<|endoftext|>


<commit_before>(function() {

function howMuch(single, plural) {
    return function(list) {
        if (!list) return 'Nothing';
        if (list.length == 0) return 'No ' + plural;
        if (list.length == 1) return 'One ' + single;
        if (list.length == 2) return 'Two ' + plural;
        return list.length + ' ' + plural;
    };
}

Rpd.channeltype('util/number', {
    default: 0,
    readonly: false,
    accept: function(val) {
        if (val === Infinity) return true;
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return parseFloat(val); }
});

Rpd.channeltype('util/wholenumber', {
    default: 0,
    readonly: true,
    allow: [ 'util/number' ],
    accept: function(val) {
        if (val === Infinity) return true;
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return Math.floor(parseFloat(val)); }
});

Rpd.channeltype('util/time', {
    default: 1000,
    accept: function(val) {
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return parseFloat(val); },
    show: function(val) { return (Math.floor(val / 10) / 100) + 's'; }
});

Rpd.channeltype('util/bang', {
    show: function(v) { return v ? '[Bang]' : '[None]'; },
    adapt: function(v) { return v ? {} : null; }
});

function numberToHex(num) { return (num > 15) ? num.toString(16) : '0' + num.toString(16); }

function toHexColor(color) {
    return '#' + numberToHex(color.r || 0)
               + numberToHex(color.g || 0)
               + numberToHex(color.b || 0);
}

Rpd.channeltype('util/color', { show: toHexColor });

Rpd.nodetype('util/number', {
    name: 'number',
    inlets:  { 'user-value': { type: 'util/number', default: 0, hidden: true } },
    outlets: { 'out':     { type: 'util/number' } },
    process: function(inlets) {
        return { 'out': inlets['user-value'] };
    }
});

Rpd.nodetype('util/random', function() {
    var lastEmitterId = 0;
    return {
        name: 'random',
        inlets:  { 'min': { type: 'util/number', default: 0 },
                   'max': { type: 'util/number', default: 100 },
                   'period': { type: 'util/time', default: 1000 } },
        outlets: { 'out':    { type: 'util/number' } },
        process: function(inlets) {
            if (!inlets.hasOwnProperty('period')) return;
            lastEmitterId++;
            return { 'out': Kefir.withInterval(inlets.period, function(emitter) {
                                      emitter.emit(Math.floor(inlets.min + (Math.random() * (inlets.max - inlets.min))));
                                  }).takeWhile((function(myId) {
                                      return function() { return myId === lastEmitterId; }
                                  })(lastEmitterId))
                   };
        }
    }
});

Rpd.nodetype('util/bounded-number', {
    name: 'bounded number',
    inlets:  { 'min': { type: 'util/number', default: 0 },
               'max': { type: 'util/number', default: Infinity },
               'spinner': { type: 'util/number', default: 0, hidden: true } },
    outlets: { 'out':     { type: 'util/number' } },
    process: function(inlets) {
         if (!inlets.hasOwnProperty('spinner')) return;
         // comparison logic is in the renderer, since it communicates with
         // this node through a hidden spinner inlet
         return { 'out': inlets.spinner };
    }
});

Rpd.channeltype('util/boolean', { default: false,
                                  readonly: false,
                                  adapt: function(val) {
                                      return (val ? true : false);
                                  } });

Rpd.nodedescription('util/empty',
                    'Does not allow adding any inlets or outlets.');
Rpd.nodetype('util/empty', {
    name: 'Empty',
    handle: {
        'inlet/add': function() {
            throw new Error('Empty node can not have any inlets');
        },
        'outlet/add': function() {
            throw new Error('Empty node can not have any outlets');
        }
    }
});

Rpd.nodetype('util/bang', {
    inlets: { 'trigger': { type: 'util/bang', hidden: true } },
    outlets: { 'out': { type: 'util/bang' } },
    process: function(inlets) {
        return inlets.trigger ? { 'out': {} } : {};
    }
});

Rpd.nodetype('util/metro', {
    inlets: { 'trigger': { type: 'util/bang', hidden: true } },
    outlets: { 'out': { type: 'util/bang' } },
    process: function(inlets) {
        return inlets.trigger ? { 'out': {} } : {};
    }
});

Rpd.nodetype('util/color', {
    inlets: {
        'r': { type: 'util/wholenumber', default: DEFAULT_COLOR.r, label: 'red' },
        'g': { type: 'util/wholenumber', default: DEFAULT_COLOR.g, label: 'green' },
        'b': { type: 'util/wholenumber', default: DEFAULT_COLOR.b, label: 'blue' }
    },
    outlets: {
        'color': { type: 'util/color' }
    },
    process: function(inlets) { return { color: inlets }; }
});

Rpd.nodetype('util/sum-of-three', {
    name: 'Sum of Three',
    inlets: {
        'a': { type: 'util/number', name: 'A' },
        'b': { type: 'util/number', name: 'B' },
        'c': { type: 'util/number', name: 'C' }
    },
    outlets: {
        'sum': { type: 'util/number', name: '∑' }
    },
    process: function(inlets) {
        return { 'sum': (inlets.a || 0) + (inlets.b || 0) + (inlets.c || 0) };
    }
});

function adaptToState(state, value) {
    return Math.floor((state.min + ((state.max - state.min) * value)) * 100) / 100;
}

Rpd.nodetype('util/knob', {
    inlets: {
        'min': { type: 'util/number', default: 0 },
        'max': { type: 'util/number', default: 100 },
        'submit': { type: 'util/number', default: 0, hidden: true }
    },
    outlets: {
        'number': { type: 'util/number' }
    },
    process: function(inlets) { return { number: adaptToState(inlets, inlets.submit) }; }
});

Rpd.channeltype('util/numbers', { show: howMuch('number', 'numbers') });

var DEFAULT_KNOB_COUNT = 4;

Rpd.nodetype('util/knobs', {
    inlets: {
        'min': { type: 'util/number', default: 0 },
        'max': { type: 'util/number', default: 100 },
        'count': { type: 'util/number', default: DEFAULT_KNOB_COUNT },
        'submit': { type: 'util/numbers', default: [], hidden: true }
    },
    outlets: {
        'numbers': { type: 'util/numbers' }
    },
    process: function(inlets) {
        return {
            numbers:
                inlets.submit ? inlets.submit.map(function(num) {
                                    return adaptToState(inlets, num);
                                })
                              : []
        };
    }
});

/*
Rpd.nodedescription('util/hot-and-cold', 'An example of cold inlet.');
Rpd.nodetype('util/hot-and-cold', {
    name: 'Hot and Cold',
    inlets: {
        'hot': { type: 'util/number', name: 'A', default: 1 },
        'cold': { type: 'util/number', name: 'B', default: 1, cold: true },
    },
    outlets: {
        'value': { type: 'util/any' }
    },
    process: function(inlets) {
        return { 'value': [ inlets.hot, inlets.cold ] };
    }
});
*/

Rpd.nodetype('util/log', {
    inlets: {
        'what': { type: 'core/any' }
    },
    process: function(inlets) {
        console.log(inlets.what);
    }
});

})();
<commit_msg>fix util toolkit to compile properly<commit_after>(function() {

function howMuch(single, plural) {
    return function(list) {
        if (!list) return 'Nothing';
        if (list.length == 0) return 'No ' + plural;
        if (list.length == 1) return 'One ' + single;
        if (list.length == 2) return 'Two ' + plural;
        return list.length + ' ' + plural;
    };
}

Rpd.channeltype('util/number', {
    default: 0,
    readonly: false,
    accept: function(val) {
        if (val === Infinity) return true;
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return parseFloat(val); }
});

Rpd.channeltype('util/wholenumber', {
    default: 0,
    readonly: true,
    allow: [ 'util/number' ],
    accept: function(val) {
        if (val === Infinity) return true;
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return Math.floor(parseFloat(val)); }
});

Rpd.channeltype('util/time', {
    default: 1000,
    accept: function(val) {
        var parsed = parseFloat(val);
        return !isNaN(parsed) && isFinite(parsed);
    },
    adapt: function(val) { return parseFloat(val); },
    show: function(val) { return (Math.floor(val / 10) / 100) + 's'; }
});

Rpd.channeltype('util/bang', {
    show: function(v) { return v ? '[Bang]' : '[None]'; },
    adapt: function(v) { return v ? {} : null; }
});

function numberToHex(num) { return (num > 15) ? num.toString(16) : '0' + num.toString(16); }

function toHexColor(color) {
    return '#' + numberToHex(color.r || 0)
               + numberToHex(color.g || 0)
               + numberToHex(color.b || 0);
}

Rpd.channeltype('util/color', { show: toHexColor });

Rpd.nodetype('util/number', {
    name: 'number',
    inlets:  { 'user-value': { type: 'util/number', default: 0, hidden: true } },
    outlets: { 'out':     { type: 'util/number' } },
    process: function(inlets) {
        return { 'out': inlets['user-value'] };
    }
});

Rpd.nodetype('util/random', function() {
    var lastEmitterId = 0;
    return {
        name: 'random',
        inlets:  { 'min': { type: 'util/number', default: 0 },
                   'max': { type: 'util/number', default: 100 },
                   'period': { type: 'util/time', default: 1000 } },
        outlets: { 'out':    { type: 'util/number' } },
        process: function(inlets) {
            if (!inlets.hasOwnProperty('period')) return;
            lastEmitterId++;
            return { 'out': Kefir.withInterval(inlets.period, function(emitter) {
                                      emitter.emit(Math.floor(inlets.min + (Math.random() * (inlets.max - inlets.min))));
                                  }).takeWhile((function(myId) {
                                      return function() { return myId === lastEmitterId; }
                                  })(lastEmitterId))
                   };
        }
    }
});

Rpd.nodetype('util/bounded-number', {
    name: 'bounded number',
    inlets:  { 'min': { type: 'util/number', default: 0 },
               'max': { type: 'util/number', default: Infinity },
               'spinner': { type: 'util/number', default: 0, hidden: true } },
    outlets: { 'out':     { type: 'util/number' } },
    process: function(inlets) {
         if (!inlets.hasOwnProperty('spinner')) return;
         // comparison logic is in the renderer, since it communicates with
         // this node through a hidden spinner inlet
         return { 'out': inlets.spinner };
    }
});

Rpd.channeltype('util/boolean', { default: false,
                                  readonly: false,
                                  adapt: function(val) {
                                      return (val ? true : false);
                                  } });

Rpd.nodedescription('util/empty',
                    'Does not allow adding any inlets or outlets.');
Rpd.nodetype('util/empty', {
    name: 'Empty',
    handle: {
        'inlet/add': function() {
            throw new Error('Empty node can not have any inlets');
        },
        'outlet/add': function() {
            throw new Error('Empty node can not have any outlets');
        }
    }
});

Rpd.nodetype('util/bang', {
    inlets: { 'trigger': { type: 'util/bang', hidden: true } },
    outlets: { 'out': { type: 'util/bang' } },
    process: function(inlets) {
        return inlets.trigger ? { 'out': {} } : {};
    }
});

Rpd.nodetype('util/metro', {
    inlets: { 'trigger': { type: 'util/bang', hidden: true } },
    outlets: { 'out': { type: 'util/bang' } },
    process: function(inlets) {
        return inlets.trigger ? { 'out': {} } : {};
    }
});

var DEFAULT_COLOR = { r: 0xED, g: 0x22, b: 0x5D };
Rpd.nodetype('util/color', {
    inlets: {
        'r': { type: 'util/wholenumber', default: DEFAULT_COLOR.r, label: 'red' },
        'g': { type: 'util/wholenumber', default: DEFAULT_COLOR.g, label: 'green' },
        'b': { type: 'util/wholenumber', default: DEFAULT_COLOR.b, label: 'blue' }
    },
    outlets: {
        'color': { type: 'util/color' }
    },
    process: function(inlets) { return { color: inlets }; }
});

Rpd.nodetype('util/sum-of-three', {
    name: 'Sum of Three',
    inlets: {
        'a': { type: 'util/number', name: 'A' },
        'b': { type: 'util/number', name: 'B' },
        'c': { type: 'util/number', name: 'C' }
    },
    outlets: {
        'sum': { type: 'util/number', name: '∑' }
    },
    process: function(inlets) {
        return { 'sum': (inlets.a || 0) + (inlets.b || 0) + (inlets.c || 0) };
    }
});

function adaptToState(state, value) {
    return Math.floor((state.min + ((state.max - state.min) * value)) * 100) / 100;
}

Rpd.nodetype('util/knob', {
    inlets: {
        'min': { type: 'util/number', default: 0 },
        'max': { type: 'util/number', default: 100 },
        'submit': { type: 'util/number', default: 0, hidden: true }
    },
    outlets: {
        'number': { type: 'util/number' }
    },
    process: function(inlets) { return { number: adaptToState(inlets, inlets.submit) }; }
});

Rpd.channeltype('util/numbers', { show: howMuch('number', 'numbers') });

var DEFAULT_KNOB_COUNT = 4;

Rpd.nodetype('util/knobs', {
    inlets: {
        'min': { type: 'util/number', default: 0 },
        'max': { type: 'util/number', default: 100 },
        'count': { type: 'util/number', default: DEFAULT_KNOB_COUNT },
        'submit': { type: 'util/numbers', default: [], hidden: true }
    },
    outlets: {
        'numbers': { type: 'util/numbers' }
    },
    process: function(inlets) {
        return {
            numbers:
                inlets.submit ? inlets.submit.map(function(num) {
                                    return adaptToState(inlets, num);
                                })
                              : []
        };
    }
});

/*
Rpd.nodedescription('util/hot-and-cold', 'An example of cold inlet.');
Rpd.nodetype('util/hot-and-cold', {
    name: 'Hot and Cold',
    inlets: {
        'hot': { type: 'util/number', name: 'A', default: 1 },
        'cold': { type: 'util/number', name: 'B', default: 1, cold: true },
    },
    outlets: {
        'value': { type: 'util/any' }
    },
    process: function(inlets) {
        return { 'value': [ inlets.hot, inlets.cold ] };
    }
});
*/

Rpd.nodetype('util/log', {
    inlets: {
        'what': { type: 'core/any' }
    },
    process: function(inlets) {
        console.log(inlets.what);
    }
});

})();
<|endoftext|>


<commit_before>var term = require("ringo/term");
var fs = require("fs");
var semver = require("ringo-semver");
var log = require("ringo/logging").getLogger(module.id);
var packages = require("../utils/packages");

exports.help = function help() {
    term.writeln("\nActivates a package.\n");
    term.writeln("Usage:");
    term.writeln("  rp activate <name> <version>");
    return;
};

exports.info = function help() {
    term.writeln(term.BOLD, "  activate", term.RESET, "-", "Activates a package");
    return;
};

// TODO: use latest version if no version argument is given
exports.activate = function activate(args) {
    var [name, version] = args;
    if (!name) {
        term.writeln(term.RED, "Please specify the name of the package to activate", term.RESET);
        return;
    } else if (!version) {
        term.writeln(term.RED, "Please specify the version to activate", term.RESET);
        return;
    }
    try {
        version = semver.cleanVersion(version);
        packages.activate(name, version);
        term.writeln(term.GREEN, "Successfully activated", name,
                "(v" + version + ")", term.RESET);
    } catch (e) {
        term.writeln(term.RED, e.message, term.RESET);
        return;
    }
    return;
};
<commit_msg>activate the latest version if none is specified<commit_after>var term = require("ringo/term");
var fs = require("fs");
var semver = require("ringo-semver");
var log = require("ringo/logging").getLogger(module.id);
var packages = require("../utils/packages");

exports.help = function help() {
    term.writeln("\nActivates a package.\n");
    term.writeln("Usage:");
    term.writeln("  rp activate <name> <version>");
    return;
};

exports.info = function help() {
    term.writeln(term.BOLD, "  activate", term.RESET, "-", "Activates a package");
    return;
};

exports.activate = function activate(args) {
    var [name, version] = args;
    if (!name) {
        term.writeln(term.RED, "Please specify the name of the package to activate", term.RESET);
    } else if (!isInstalled(name)) {
        term.writeln(term.RED, "The package", name, "isn't installed. Maybe a typo?", term.RESET);
    } else if (!version && !(version = getLatestVersion(name))) {
        term.writeln(term.RED, "Please specify the version to activate", term.RESET);
    } else {
        try {
            version = semver.cleanVersion(version);
            packages.activate(name, version);
            term.writeln(term.GREEN, "Successfully activated", name,
                    "(v" + version + ")", term.RESET);
        } catch (e) {
            term.writeln(term.RED, e.message, term.RESET);
        }
    }
};

/**
 * Returns true if a package with the given name is installed (regardless
 * which version)
 * @param {String} name The name of the package
 * @returns True if a package with this name is installed
 * @type Boolean
 */
function isInstalled(name) {
    var list = fs.list(packages.getInstallDir()).filter(function(dirName) {
        return dirName.substring(0, dirName.lastIndexOf("-")) == name;
    });
    return list.length > 0;
}

function getLatestVersion(name) {
    var list = fs.list(packages.getInstallDir()).filter(function(dirName) {
        return dirName.substring(0, dirName.lastIndexOf("-")) == name;
    });
    var version = null;
    if (list.length > 0) {
        if (list.length > 1) {
            semver.sort(list, -1);
        }
        version = list[0].substring(list[0].lastIndexOf("-") + 1);
    }
    return version;
}<|endoftext|>


<commit_before>var config = require('./config'),
    geogoose = require('./geogoose'),
    console = require('./ext-console'),
    mongoose = require('mongoose'),
    mongooseTypes = require("mongoose-types"),
    _ = require('cloneextend'),
    util = require('util');

var useTimestamps = function (schema, options) {
    schema.add({
        createdAt: Date
      , updatedAt: Date
    });
    schema.pre('save', function (next) {
      if (!this.createdAt) {
        this.createdAt = this.updatedAt = new Date;
      } else {
        this.updatedAt = new Date;
      }
      next();
    });
};

mongooseTypes.loadTypes(mongoose);
//useTimestamps = mongooseTypes.useTimestamps;

var User = mongoose.model('User', new mongoose.Schema({
    name: String,
    email: {type: mongoose.SchemaTypes.Email, required: true}
}));

User.schema.plugin(useTimestamps);

var Job = mongoose.model('Job', new mongoose.Schema({
    createdAt: Date,
    updatedAt: Date,
    type: {type: String, enum: [config.JobType.IMPORT, config.JobType.REDUCE], required: true},
    status: {type: String, enum: [config.JobStatus.ACTIVE, config.JobStatus.IDLE], required: true},
}));

Job.schema.plugin(useTimestamps);

/*
var Point = mongoose.model('Point', new mongoose.Schema({
    pointCollection: { type: mongoose.Schema.ObjectId, ref: 'PointCollection', required: true, index: 1 },
    importJob: { type: mongoose.Schema.ObjectId, ref: 'Job', required: false, index: 1 },
    loc: {type: [Number], index: '2d', required: true },
    val: {type: Number, index: 1},
    label: String,
    description: String,
    url: String,
    datetime: {type: Date, index: 1},
    sourceId: {type: mongoose.Schema.Types.Mixed, index: 1},
    incField: {type: mongoose.Schema.Types.Mixed, index: 1},
    createdAt: Date,
    updatedAt: Date
}));

Point.schema.plugin(useTimestamps);
Point.schema.index({loc: '2d', pointCollection: 1})
*/

var colorMatch = /^#([a-fA-F0-9]{2}){3}$/;

var LayerOptions = mongoose.model('LayerOptions', new mongoose.Schema({
    htmlRenderer: {type: String, required: false, default: "", enum: ["", "Canvas", "SVG"]},
    enabled: {type: Boolean, required: true, default: true},
    public: {type: Boolean, required: true, default: true},
    featureType: {type: String, required: true, enum: [
        config.FeatureType.POINTS, config.FeatureType.SQUARE_TILES, config.FeatureType.BUBBLES, config.FeatureType.SHAPES]},
    colorType: {type: String, required: true, default: config.ColorType.LINEAR_GRADIENT},
    //colorPalettes: {type: [ColorPalette.schema], index: 1},
    colors: [{
        color: {type: String, required: true, match: colorMatch},
        position: {type: String, match: /^[0-9]+(\.[0-9]+)?%?$/},
        interpolation: {type: String, enum: ['lerpRGB', 'threshold', ''], required: false},
        label: String,
        description: String
    }],
    colorLabelColor: {type: String, match: colorMatch},
    strokeColor: {type: String, match: colorMatch},
    reduction: String,
    opacity: {type: Number, required: true, min: 0, max: 1},
    strokeOpacity: {type: Number, min: 0, max: 1},
    strokeWidth: {type: Number, min: config.MIN_STROKE_WIDTH, max: config.MAX_STROKE_WIDTH},
    strokeDashstyle: {type: String},
    strokeLinecap: {type: String},
    featureSizeAttr: {type: String},
    featureColorAttr: {type: String},
    numericAttr: {type: String},
    datetimeAttr: {type: String},
    featureSize: {type: Number, min: config.MIN_FEATURE_SIZE, max: config.MAX_FEATURE_SIZE},
    minFeatureSize: {type: Number, min: config.MIN_FEATURE_SIZE, max: config.MAX_FEATURE_SIZE},
    datetimeFormat: String,
    valFormat: [{
        unit: {type: String, required: true},
        eq: {type: String, required: true},
        formatString: String
    }],
    filterQuery: mongoose.Schema.Types.Mixed,
    queryOptions: mongoose.Schema.Types.Mixed,
    title: String,
    unit: String,
    description: String,
    histogram: {type: Boolean, default: true},
    itemTitle: String,
    itemTitlePlural: String,
    cropDistribution: Boolean
}));

LayerOptions.schema.path('colors').validate(function (value) {
    return value.length > 0;
}, 'At least one color is required.');

/*
PointCollection = mongoose.model('PointCollection', new mongoose.Schema({
    title: String,
    description: String,
    source: String,
    unit: String,
    isNumeric: {type: Boolean, default: true},
    maxVal: Number,
    minVal: Number,
    maxIncField: { type: mongoose.Schema.Types.Mixed, index: 1 },
    importParams: mongoose.Schema.Types.Mixed,
    timebased: Boolean,
    gridSize: Number,
    defaults: { type: mongoose.Schema.ObjectId, ref: 'LayerOptions', index: 1 },
    active: Boolean,
    status: String,
    progress: Number,
    numBusy: Number,
    reduce: Boolean,
    maxReduceZoom: Number,
    sync: Boolean,
    cropDistribution: Boolean,
    createdBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    modifiedBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    createdAt: Date,
    updatedAt: Date,
    lastReducedAt: Date,
    linkedPointCollection: { type: mongoose.Schema.ObjectId, ref: 'PointCollection', required: false, index: 1 },
}));

PointCollection.schema.plugin(useTimestamps);
*/

var MapLayerSchema = new mongoose.Schema({
 //   _id: { type: mongoose.Schema.ObjectId },
    featureCollection: { type: mongoose.Schema.ObjectId, ref: 'GeoFeatureCollection', index: 1 },
    layerOptions: { type: mongoose.Schema.ObjectId, ref: 'LayerOptions', index: 1 },
    position: Number
});

var Map = mongoose.model('Map', new mongoose.Schema({
    active: {type: Boolean, default: true},
    title: {type: String, required: true},
    description: String,
    author: String,
    linkURL: String,
    linkTitle: String,
    twitter: String,
    displayInfo: Boolean,
    adminslug: {type: String, required: true, index: {unique: true}},
    publicslug: {type: String, required: true, index: {unique: true}},
    host: {type: String, required: false, index: {unique: true, sparse: true}},
    featured: {type: Number, default: 0},
    initialArea: {
        center: [Number, Number],
        zoom: Number
    },
    viewOptions: {
        viewName: String,
        viewBase: String,
        viewStyle: String
    },
    // TODO: Enforce privacy (currently unused because no user login required)
    status: {type: String, enum: [config.MapStatus.PRIVATE, config.MapStatus.PUBLIC], required: true, default: config.MapStatus.PUBLIC},
    layers: [MapLayerSchema],
    createdBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    modifiedBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    createdAt: Date,
    updatedAt: Date,
    tour: {
        steps: [{
        layers: [Number],
        title: String,
        body: String
        }]
    }
}));

Map.schema.plugin(useTimestamps);

var GeoFeatureSchema = new geogoose.models.GeoFeatureSchema({
    geocoded: {type: Boolean, default: false, required: true},
    incrementor: mongoose.Schema.Types.Mixed,
    source: mongoose.Schema.Types.Mixed,
    count: Number
});

GeoFeatureSchema.plugin(useTimestamps);

GeoFeatureSchema.pre('save', function(next) {
    this.geocoded = this.bounds2d && this.bounds2d.length;
    next();
});

var GeoFeatureMapReducedSchema = new geogoose.models.GeoFeatureSchema({_id: String}, null, null, null, {
    value: geogoose.models.geoJSONFeatureDefinition
});

GeoFeatureMapReducedSchema.methods.toGeoJSON = function(extraAttrs)
{
    var obj = this.toJSON().value;
    obj._id = this.get('_id').toString();
    if (extraAttrs) obj = _.extend(obj, extraAttrs);
    return geogoose.util.toGeoJSON(obj);
};

var GeoFeatureCollectionSchema = new geogoose.models.GeoFeatureCollectionSchema({
    title: String,
    description: String,
    source: String,
    unit: String,
    isNumeric: {type: Boolean, default: true},
    extremes: { type: mongoose.Schema.Types.Mixed, index: 1, default: {} },
    importParams: mongoose.Schema.Types.Mixed,
    timebased: Boolean,
    gridSize: Number,
    defaults: { type: mongoose.Schema.ObjectId, ref: 'LayerOptions', index: 1 },
    active: Boolean,
    status: String,
    progress: Number,
    numBusy: Number,
    reduce: Boolean,
    maxReduceZoom: Number,
    sync: Boolean,
    cropDistribution: Boolean,
    createdBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    modifiedBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    createdAt: Date,
    updatedAt: Date,
    lastReducedAt: Date,
    datetimeAttr: String,
    numericAttr: String
}, {FeatureSchema: GeoFeatureSchema});

GeoFeatureCollectionSchema.plugin(useTimestamps);

GeoFeatureCollectionSchema.methods.getMapReducedFeatureModel = function(opts) {
    var collectionName =
        'r_'
        + this.getFeatureModel().collection.name
        + (opts.gridSize != undefined ? '_tile_rect_' + opts.gridSize : '')
        + (opts.time != undefined ? '_' + opts.time : '');
    return this.getFeatureModel({collectionName: collectionName, schema: GeoFeatureMapReducedSchema});
};

var GeoFeatureCollection = mongoose.model('GeoFeatureCollection', GeoFeatureCollectionSchema);

module.exports = {
    User: User,
    Job: Job,
    LayerOptions: LayerOptions,
    Map: Map,
    GeoFeatureCollection: GeoFeatureCollection,
    adHocModel: geogoose.util.adHocModel
    //GeoFeature: GeoFeature,
};

<commit_msg>Further cleanup and removal of obsolete code<commit_after>var config = require('./config'),
    geogoose = require('./geogoose'),
    console = require('./ext-console'),
    mongoose = require('mongoose'),
    mongooseTypes = require("mongoose-types"),
    _ = require('cloneextend'),
    util = require('util');

var useTimestamps = function (schema, options) {
    schema.add({
        createdAt: Date
      , updatedAt: Date
    });
    schema.pre('save', function (next) {
      if (!this.createdAt) {
        this.createdAt = this.updatedAt = new Date;
      } else {
        this.updatedAt = new Date;
      }
      next();
    });
};

mongooseTypes.loadTypes(mongoose);
//useTimestamps = mongooseTypes.useTimestamps;

var User = mongoose.model('User', new mongoose.Schema({
    name: String,
    email: {type: mongoose.SchemaTypes.Email, required: true}
}));

User.schema.plugin(useTimestamps);

var Job = mongoose.model('Job', new mongoose.Schema({
    createdAt: Date,
    updatedAt: Date,
    type: {type: String, enum: [config.JobType.IMPORT, config.JobType.REDUCE], required: true},
    status: {type: String, enum: [config.JobStatus.ACTIVE, config.JobStatus.IDLE], required: true},
}));

Job.schema.plugin(useTimestamps);

var colorMatch = /^#([a-fA-F0-9]{2}){3}$/;

var LayerOptions = mongoose.model('LayerOptions', new mongoose.Schema({
    htmlRenderer: {type: String, required: false, default: "", enum: ["", "Canvas", "SVG"]},
    enabled: {type: Boolean, required: true, default: true},
    public: {type: Boolean, required: true, default: true},
    featureType: {type: String, required: true, enum: [
        config.FeatureType.POINTS, config.FeatureType.SQUARE_TILES, config.FeatureType.BUBBLES, config.FeatureType.SHAPES]},
    colorType: {type: String, required: true, default: config.ColorType.LINEAR_GRADIENT},
    //colorPalettes: {type: [ColorPalette.schema], index: 1},
    colors: [{
        color: {type: String, required: true, match: colorMatch},
        position: {type: String, match: /^[0-9]+(\.[0-9]+)?%?$/},
        interpolation: {type: String, enum: ['lerpRGB', 'threshold', ''], required: false},
        label: String,
        description: String
    }],
    colorLabelColor: {type: String, match: colorMatch},
    strokeColor: {type: String, match: colorMatch},
    reduction: String,
    opacity: {type: Number, required: true, min: 0, max: 1},
    strokeOpacity: {type: Number, min: 0, max: 1},
    strokeWidth: {type: Number, min: config.MIN_STROKE_WIDTH, max: config.MAX_STROKE_WIDTH},
    strokeDashstyle: {type: String},
    strokeLinecap: {type: String},
    featureSizeAttr: {type: String},
    featureColorAttr: {type: String},
    numericAttr: {type: String},
    datetimeAttr: {type: String},
    featureSize: {type: Number, min: config.MIN_FEATURE_SIZE, max: config.MAX_FEATURE_SIZE},
    minFeatureSize: {type: Number, min: config.MIN_FEATURE_SIZE, max: config.MAX_FEATURE_SIZE},
    datetimeFormat: String,
    valFormat: [{
        unit: {type: String, required: true},
        eq: {type: String, required: true},
        formatString: String
    }],
    filterQuery: mongoose.Schema.Types.Mixed,
    queryOptions: mongoose.Schema.Types.Mixed,
    title: String,
    unit: String,
    description: String,
    histogram: {type: Boolean, default: true},
    itemTitle: String,
    itemTitlePlural: String,
    cropDistribution: Boolean
}));

LayerOptions.schema.path('colors').validate(function (value) {
    return value.length > 0;
}, 'At least one color is required.');

var MapLayerSchema = new mongoose.Schema({
    featureCollection: { type: mongoose.Schema.ObjectId, ref: 'GeoFeatureCollection', index: 1 },
    layerOptions: { type: mongoose.Schema.ObjectId, ref: 'LayerOptions', index: 1 },
    position: Number
});

var Map = mongoose.model('Map', new mongoose.Schema({
    active: {type: Boolean, default: true},
    title: {type: String, required: true},
    description: String,
    author: String,
    linkURL: String,
    linkTitle: String,
    twitter: String,
    displayInfo: Boolean,
    adminslug: {type: String, required: true, index: {unique: true}},
    publicslug: {type: String, required: true, index: {unique: true}},
    host: {type: String, required: false, index: {unique: true, sparse: true}},
    featured: {type: Number, default: 0},
    initialArea: {
        center: [Number, Number],
        zoom: Number
    },
    viewOptions: {
        viewName: String,
        viewBase: String,
        viewStyle: String
    },
    // TODO: Enforce privacy (currently unused because no user login required)
    status: {type: String, enum: [config.MapStatus.PRIVATE, config.MapStatus.PUBLIC], required: true, default: config.MapStatus.PUBLIC},
    layers: [MapLayerSchema],
    createdBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    modifiedBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    createdAt: Date,
    updatedAt: Date,
    tour: {
        steps: [{
        layers: [Number],
        title: String,
        body: String
        }]
    }
}));

Map.schema.plugin(useTimestamps);

var GeoFeatureSchema = new geogoose.models.GeoFeatureSchema({
    geocoded: {type: Boolean, default: false, required: true},
    incrementor: mongoose.Schema.Types.Mixed,
    source: mongoose.Schema.Types.Mixed,
    count: Number
});

GeoFeatureSchema.plugin(useTimestamps);

GeoFeatureSchema.pre('save', function(next) {
    this.geocoded = this.bounds2d && this.bounds2d.length;
    next();
});

var GeoFeatureMapReducedSchema = new geogoose.models.GeoFeatureSchema({_id: String}, null, null, null, {
    value: geogoose.models.geoJSONFeatureDefinition
});

GeoFeatureMapReducedSchema.methods.toGeoJSON = function(extraAttrs)
{
    var obj = this.toJSON().value;
    obj._id = this.get('_id').toString();
    if (extraAttrs) obj = _.extend(obj, extraAttrs);
    return geogoose.util.toGeoJSON(obj);
};

var GeoFeatureCollectionSchema = new geogoose.models.GeoFeatureCollectionSchema({
    title: String,
    description: String,
    source: String,
    unit: String,
    isNumeric: {type: Boolean, default: true},
    extremes: { type: mongoose.Schema.Types.Mixed, index: 1, default: {} },
    importParams: mongoose.Schema.Types.Mixed,
    timebased: Boolean,
    gridSize: Number,
    defaults: { type: mongoose.Schema.ObjectId, ref: 'LayerOptions', index: 1 },
    active: Boolean,
    status: String,
    progress: Number,
    numBusy: Number,
    reduce: Boolean,
    maxReduceZoom: Number,
    sync: Boolean,
    cropDistribution: Boolean,
    createdBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    modifiedBy: { type: mongoose.Schema.ObjectId, ref: 'User', index: 1 },
    createdAt: Date,
    updatedAt: Date,
    lastReducedAt: Date,
    datetimeAttr: String,
    numericAttr: String
}, {FeatureSchema: GeoFeatureSchema});

GeoFeatureCollectionSchema.plugin(useTimestamps);

GeoFeatureCollectionSchema.methods.getMapReducedFeatureModel = function(opts) {
    var collectionName =
        'r_'
        + this.getFeatureModel().collection.name
        + (opts.gridSize != undefined ? '_tile_rect_' + opts.gridSize : '')
        + (opts.time != undefined ? '_' + opts.time : '');
    return this.getFeatureModel({collectionName: collectionName, schema: GeoFeatureMapReducedSchema});
};

var GeoFeatureCollection = mongoose.model('GeoFeatureCollection', GeoFeatureCollectionSchema);

module.exports = {
    User: User,
    Job: Job,
    LayerOptions: LayerOptions,
    Map: Map,
    GeoFeatureCollection: GeoFeatureCollection,
    adHocModel: geogoose.util.adHocModel
};

<|endoftext|>


<commit_before>exports.definition = function (modelName) {
	var Model = global[_.str.capitalize(modelName)];
	return {
		// Call findAll, unless this is a basic request, then render the index view
		index: function (req,res) {
			if (req.isAjax || req.isSocket) {
				this.read(req,res);
			}
			else {
				res.view();
			}
		},

		// Fetch paginated list of models from testtable
		read: function (req,res) {
			var options = {
				limit	: req.param('limit') || undefined,
				offset	: req.param('skip') || req.param('offset') || undefined,
				order	: req.param('order') || undefined
			};

			// Respond to search or where queries
			if (_.isObject(req.param('search'))) {
				// Create stub where query
				options.where = [" FALSE "];
				_.each(req.param('search'),function(queryString,queryField) {

					// Verify the field name actually exists in the model (since it won't be escaped)
					var modelFields = _.keys(Model.rawAttributes);
					if (_.include(modelFields,queryField)) {
						// Append SQL and escaped variable to the WHERE query string
						options.where[0] += " OR " + queryField+" LIKE ?";
						options.where.push("%"+queryString+"%");
					}
				});
			}
			else {
				options.where = req.param('where') || (req.param('id') && {id:req.param('id')}) || undefined;
			}

			// TODO: remove this
			console.log(options);

			Model.findAll(options).success(function(models) {
				Model.subscribe(req,res);
				res.json(models);
			});
		},

		// Store a new model
		create: function (req,res) {
			Model.create(Model.trimParams(req.params)).success(function(model) {
				Model.publish(req,res,{
					uri: Model.getModelName()+'/create',
					data: model.values
				});
				res.json({
					id: model.id,
					success:true
				});
			});
		},

		// Edit an existing model
		update: function (req,res) {
			Model.findAndUpdate({
				id: req.param('id')
			}, Model.trimParams(req.params), function (err,model) {
				if (_.isArray(model)) {
					model = model[0];
				}
				try {
					if (!model || !model.values) throw new Error('Model cannot be found.');

					// Trim updatedAt from values to avoid having to use a null binding on the client
					var changes = _.objReject(model.values,function(v,k) {
						return k=='updatedAt';
					});

					Model.publish(req,res,{
						uri: Model.getModelName()+'/'+req.param('id')+'/update',
						data: changes
					});
					res.json({
						id: +req.param('id'),
						success:true
					});
				}
				catch(e) {
					res.json({
						success: false,
						error: e
					});
				}
			});
		},

		// Destroy a model
		destroy: function (req,res) {
			Model.findAndDelete(req.param('id'),function(err) {
				Model.publish(req,res,{
					uri: Model.getModelName()+'/'+req.param('id')+'/destroy'
				});
				res.json({
					id: +req.param('id'),
					success:true
				});
			});
		}
	};
};<commit_msg>If an id is specified, only return a single item in a call to a scaffolded 'find'<commit_after>exports.definition = function (modelName) {
	var Model = global[_.str.capitalize(modelName)];
	return {
		// Call findAll, unless this is a basic request, then render the index view
		index: function (req,res) {
			if (req.isAjax || req.isSocket) {
				this.read(req,res);
			}
			else {
				res.view();
			}
		},

		// Fetch paginated list of models from testtable
		read: function (req,res) {
			var options = {
				limit	: req.param('limit') || undefined,
				offset	: req.param('skip') || req.param('offset') || undefined,
				order	: req.param('order') || undefined
			};

			// Respond to search or where queries
			if (_.isObject(req.param('search'))) {
				// Create stub where query
				options.where = [" FALSE "];
				_.each(req.param('search'),function(queryString,queryField) {

					// Verify the field name actually exists in the model (since it won't be escaped)
					var modelFields = _.keys(Model.rawAttributes);
					if (_.include(modelFields,queryField)) {
						// Append SQL and escaped variable to the WHERE query string
						options.where[0] += " OR " + queryField+" LIKE ?";
						options.where.push("%"+queryString+"%");
					}
				});
			}
			else {
				options.where = req.param('where') || (req.param('id') && {id:req.param('id')}) || undefined;
			}

			Model.findAll(options).success(function(models) {
				Model.subscribe(req,res);

				// If id was set, only return one model
				if (models && models.length===1 && req.param('id')) {
					models = models[0];
				}
				res.json(models);
			});
		},

		// Store a new model
		create: function (req,res) {
			Model.create(Model.trimParams(req.params)).success(function(model) {
				Model.publish(req,res,{
					uri: Model.getModelName()+'/create',
					data: model.values
				});
				res.json({
					id: model.id,
					success:true
				});
			});
		},

		// Edit an existing model
		update: function (req,res) {
			Model.findAndUpdate({
				id: req.param('id')
			}, Model.trimParams(req.params), function (err,model) {
				if (_.isArray(model)) {
					model = model[0];
				}
				try {
					if (!model || !model.values) throw new Error('Model cannot be found.');

					// Trim updatedAt from values to avoid having to use a null binding on the client
					var changes = _.objReject(model.values,function(v,k) {
						return k=='updatedAt';
					});

					Model.publish(req,res,{
						uri: Model.getModelName()+'/'+req.param('id')+'/update',
						data: changes
					});
					res.json({
						id: +req.param('id'),
						success:true
					});
				}
				catch(e) {
					res.json({
						success: false,
						error: e
					});
				}
			});
		},

		// Destroy a model
		destroy: function (req,res) {
			Model.findAndDelete(req.param('id'),function(err) {
				Model.publish(req,res,{
					uri: Model.getModelName()+'/'+req.param('id')+'/destroy'
				});
				res.json({
					id: +req.param('id'),
					success:true
				});
			});
		}
	};
};<|endoftext|>


<commit_before>'use strict';

var createClass = require('uptown').createClass;
var Namespace = require('../namespace');
var KeyValuePair = require('../key-value-pair');

module.exports = createClass({
  constructor: function(namespace) {
    this.namespace = namespace || new Namespace();
  },

  serialise: function(element) {
    var payload = {
      element: element.element,
    };

    if (element.meta.length > 0) {
      payload['meta'] = this.serialiseObject(element.meta);
    }

    if (element.attributes.length > 0) {
      payload['attributes'] = this.serialiseObject(element.attributes);
    }

    payload['content'] = this.serialiseContent(element.content);

    return payload;
  },

  deserialise: function(value) {
    if (typeof value === 'string') {
      return new this.namespace.elements.String(value);
    } else if (typeof value === 'number') {
      return new this.namespace.elements.Number(value);
    } else if (typeof value === 'boolean') {
      return new this.namespace.elements.Boolean(value);
    } else if (value === null) {
      return new this.namespace.elements.Null();
    } else if (value.map) {
      return value.map(this.deserialise, this);
    }

    var ElementClass = this.namespace.getElementClass(value.element);
    var element = new ElementClass();

    if (element.element !== value.element) {
      element.element = value.element;
    }

    if (value.meta) {
      this.deserialiseObject(value.meta, element.meta);
    }

    if (value.attributes) {
      this.deserialiseObject(value.attributes, element.attributes);
    }

    element.content = this.deserialiseContent(value.content);

    return element;
  },

  // Private API

  serialiseContent: function(content) {
    if (content instanceof this.namespace.Element) {
      return this.serialise(content);
    } else if (content instanceof KeyValuePair) {
      return {
        'key': this.serialise(content.key),
        'value': this.serialise(content.value)
      };
    } else if (content && content.map) {
      return content.map(this.serialise, this);
    }

    return content;
  },

  deserialiseContent: function(content) {
    if (content) {
      if (content.element) {
        return this.deserialise(content);
      } else if (content.key) {
        var pair = new KeyValuePair(this.deserialise(content.key));

        if (content.value) {
          pair.value = this.deserialise(content.value);
        }

        return pair;
      } else if (content.map) {
        return content.map(this.deserialise, this);
      }
    }

    return content;
  },

  shouldRefract: function (element) {
    if (element.attributes.keys().length || element.meta.keys().length) {
      return true;
    }

    if (element.element === 'enum') {
      // enum elements are treated like primitives (array)
      return false;
    }

    if (element.element !== element.primitive() || element.element === 'member') {
      return true;
    }

    return false;
  },

  convertKeyToRefract: function (key, item) {
    if (this.shouldRefract(item)) {
      return this.serialise(item);
    }

    if (item.element === 'enum') {
      return this.serialiseEnum(item);
    }

    if (item.element === 'array') {
      // This is a plain array, but maybe it contains elements with
      // additional information? Let's see!
      var values = [];

      for (var index = 0; index < item.length; index++) {
        var subItem = item.get(index);

        if (this.shouldRefract(subItem) || key === 'default') {
          values.push(this.serialise(subItem));
        } else if (subItem.element === 'array' || subItem.element === 'enum') {
          // items for array or enum inside array are always serialised
          var self = this;
          var value = subItem.children.map(function(subSubItem) {
            return self.serialise(subSubItem);
          });
          values.push(value);
        } else {
          values.push(subItem.toValue());
        }
      }

      return values;
    }

    if (item.element === 'object') {
      // This is an object, so we need to check if it's members contain
      // additional information
      // TODO
    }

    return item.toValue();
  },

  serialiseEnum: function(element) {
    var self = this;

    return element.children.map(function(item) {
      return self.serialise(item);
    });
  },

  serialiseObject: function(obj) {
    var result = {};

    obj.keys().forEach(function (key) {
      result[key] = this.convertKeyToRefract(key, obj.get(key));
    }, this);

    return result;
  },

  deserialiseObject: function(from, to) {
    Object.keys(from).forEach(function (key) {
      to.set(key, this.deserialise(from[key]));
    }, this);
  }
});
<commit_msg>refactor(JSON 0.6): Allow overriding content serialisation per element<commit_after>'use strict';

var createClass = require('uptown').createClass;
var Namespace = require('../namespace');
var KeyValuePair = require('../key-value-pair');

module.exports = createClass({
  constructor: function(namespace) {
    this.namespace = namespace || new Namespace();
  },

  serialise: function(element) {
    var payload = {
      element: element.element,
    };

    if (element.meta.length > 0) {
      payload['meta'] = this.serialiseObject(element.meta);
    }

    if (element.attributes.length > 0) {
      payload['attributes'] = this.serialiseObject(element.attributes);
    }

    if (this[element.element + 'SerialiseContent']) {
      payload['content'] = this[element.element + 'SerialiseContent'](element, payload);
    } else if (element.content !== undefined) {
      payload['content'] = this.serialiseContent(element.content);
    }

    return payload;
  },

  deserialise: function(value) {
    if (typeof value === 'string') {
      return new this.namespace.elements.String(value);
    } else if (typeof value === 'number') {
      return new this.namespace.elements.Number(value);
    } else if (typeof value === 'boolean') {
      return new this.namespace.elements.Boolean(value);
    } else if (value === null) {
      return new this.namespace.elements.Null();
    } else if (value.map) {
      return value.map(this.deserialise, this);
    }

    var ElementClass = this.namespace.getElementClass(value.element);
    var element = new ElementClass();

    if (element.element !== value.element) {
      element.element = value.element;
    }

    if (value.meta) {
      this.deserialiseObject(value.meta, element.meta);
    }

    if (value.attributes) {
      this.deserialiseObject(value.attributes, element.attributes);
    }

    element.content = this.deserialiseContent(value.content);

    return element;
  },

  // Private API

  serialiseContent: function(content) {
    if (content instanceof this.namespace.Element) {
      return this.serialise(content);
    } else if (content instanceof KeyValuePair) {
      return {
        'key': this.serialise(content.key),
        'value': this.serialise(content.value)
      };
    } else if (content && content.map) {
      return content.map(this.serialise, this);
    }

    return content;
  },

  deserialiseContent: function(content) {
    if (content) {
      if (content.element) {
        return this.deserialise(content);
      } else if (content.key) {
        var pair = new KeyValuePair(this.deserialise(content.key));

        if (content.value) {
          pair.value = this.deserialise(content.value);
        }

        return pair;
      } else if (content.map) {
        return content.map(this.deserialise, this);
      }
    }

    return content;
  },

  shouldRefract: function (element) {
    if (element.attributes.keys().length || element.meta.keys().length) {
      return true;
    }

    if (element.element === 'enum') {
      // enum elements are treated like primitives (array)
      return false;
    }

    if (element.element !== element.primitive() || element.element === 'member') {
      return true;
    }

    return false;
  },

  convertKeyToRefract: function (key, item) {
    if (this.shouldRefract(item)) {
      return this.serialise(item);
    }

    if (item.element === 'enum') {
      return this.serialiseEnum(item);
    }

    if (item.element === 'array') {
      // This is a plain array, but maybe it contains elements with
      // additional information? Let's see!
      var values = [];

      for (var index = 0; index < item.length; index++) {
        var subItem = item.get(index);

        if (this.shouldRefract(subItem) || key === 'default') {
          values.push(this.serialise(subItem));
        } else if (subItem.element === 'array' || subItem.element === 'enum') {
          // items for array or enum inside array are always serialised
          var self = this;
          var value = subItem.children.map(function(subSubItem) {
            return self.serialise(subSubItem);
          });
          values.push(value);
        } else {
          values.push(subItem.toValue());
        }
      }

      return values;
    }

    if (item.element === 'object') {
      // This is an object, so we need to check if it's members contain
      // additional information
      // TODO
    }

    return item.toValue();
  },

  serialiseEnum: function(element) {
    var self = this;

    return element.children.map(function(item) {
      return self.serialise(item);
    });
  },

  serialiseObject: function(obj) {
    var result = {};

    obj.keys().forEach(function (key) {
      result[key] = this.convertKeyToRefract(key, obj.get(key));
    }, this);

    return result;
  },

  deserialiseObject: function(from, to) {
    Object.keys(from).forEach(function (key) {
      to.set(key, this.deserialise(from[key]));
    }, this);
  }
});
<|endoftext|>