docs: function(hash) {
  	return function(row) {
  		var testValue = function(value, rowValue) {
  			if (value === '*' || value === '' || !value) {
  				return true;
  			}
  			if (_.isString(rowValue)) {
  				return (rowValue.toLowerCase() === value.toLowerCase());
  			}
  			if (_.isArray(rowValue)) {
  				return (_.contains(_.map(rowValue, function(tag) {
  					return _.isString(tag) && tag.toLowerCase();
  				}), value.toLowerCase()) && true);
  			}
  			return false;				
  		};
  		
  		// _.all makes sure all members of the hash pass
  		return _.all(hash, function(value, key) {
  			var rowValue = row.getAlias(key);
  			
  			if (rowValue) {
  				
  				// if the filter value is an array, then the rowValue must also be an array
  				if (_.isArray(value) && _.isArray(rowValue)) {
  					
  					// expression is true if rowValue intersects completely with requested value.
  					return _.intersection(value, rowValue).length === value.length;
  				}
  
  				// if the value is an array, but not the rowValue, only test the first index;
  				return testValue(_.isArray(value) ? value[0] : value, rowValue)
  			}
  			return false;
  		});
  	};
  }