Skip to content

Instantly share code, notes, and snippets.

@lancejpollard
Created November 22, 2012 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lancejpollard/d3aa5a9ceb52dd478205 to your computer and use it in GitHub Desktop.
Save lancejpollard/d3aa5a9ceb52dd478205 to your computer and use it in GitHub Desktop.
Tower Validation API Ideas
// https://github.com/bnoguchi/mongoose-types
App.validate('presence', extractArgs, function(key, value) {
return 'string'; // message itself
return true/1/undefined; // uses default message
});
var compileValidation = function(fn, arg) {
var type = _.kind(arg)
, validation;
if (type == 'object') {
validation = arg;
type = _.kind(validation.value);
} else if (type == 'function') {
validation = {validate: arg};
}
return validation;
}
/*
* validate('format', /[a-z]/)
* validate('format', function(value) { return value.match(/[a-z]/) })
* validate('format', {value: /[a-z]/})
* validate('format', {value: /[a-z]/, message: 'format is off'})
* validate('format', {value: /[a-z]/, message: 'format is off'})
* validate('format', /[a-z]/, {if: 'somethingIsTrue'})
* validate(key, value, options)
* validate(key, fn, options)
* validate(key, options)
*/
App.validation('format', compileValidation, function(key, value) {
});
App.validation('format', {
// if you give it this, it will pass you the args so you can create the final: {validate: fn, message: 'x'}
setup: function() {
},
validate: function() {
},
message: function() {
}
});
App.validations({
format: {setup: ..., validate: ..., message: ...}
});
// https://github.com/LearnBoost/mongoose/pull/1214
App.User = App.Model.extend({
email: App.field().validate('presence').validate('uniqueness').validate('format')
email: App.field().validate('presence', 'uniqueness', 'format', {if: 'x'}) // shared options
email: App.field().validate('length', function(value) { return value.length > 3 })
});
function validate(key, value) {
var validate = App.validations[key].compile(value);
validate.call(record, key, value);
}
@lancejpollard
Copy link
Author

// transform|parse|format|serialize, which one?
App.serialize('array').to(function(string) {
return string.split(',');
}).from(function(array) {
return array.join(',');
});

App.serialize('array', {
to: function(string) {
return string.split(',');
},
from: function(array) {
return array.join(',');
}
});

App.serialize('url', {
to: function() {

},
from: function() {

}
}).serialize('phone', {
to: function(string) {
string.replace(/asdf/, ...);
}
});

App.type('phone', {
to: function() {

},
from: function() {

},
validate: function(value) {
return !!this.to(value).match(/x/);
}
});

App.param('digits', 'phone');
App.param('tags', 'array');
App.param('tags', {type: 'array'}).validate('in', ['ruby', 'javascript']);
App.param('tags', {type: 'array'}).validate(function(req) {
if (req.isAdmin) return true; // maybe be able to search by different things based on role
return ['ruby', 'javascript'];
});

App.Router = Tower.Router.extend({
route: '/',
action: function() {

},
posts: Tower.Route.extend({
route: '/posts',
index: Tower.Route.extend({
route: '/',
tags: Tower.param('tags'),
title: App.Post.field('title') // maybe?
})
})
});

router.get('params') // bindable ember object
router.get('url') // bindable ember object

// 1. serialization
// 2. param/field
// 3. validation

@lancejpollard
Copy link
Author

// transform|parse|format|serialize, which one?
App.serialize('array').to(function(string) {
  return string.split(',');
}).from(function(array) {
  return array.join(',');
});

App.serialize('array', {
  to: function(string) {
    return string.split(',');
  },
  from: function(array) {
    return array.join(',');
  }
});

App.serialize('url', {
  to: function() {

  },
  from: function() {

  }
}).serialize('phone', {
  to: function(string) {
    string.replace(/asdf/, ...);
  }
});

App.type('phone', {
  to: function() {

  },
  from: function() {

  },
  validate: function(value) {
    return !!this.to(value).match(/x/);
  }
});

App.param('digits', 'phone');
App.param('tags', 'array');
App.param('tags', {type: 'array'}).validate('in', ['ruby', 'javascript']);
App.param('tags', {type: 'array'}).validate(function(req) {
  if (req.isAdmin) return true; // maybe be able to search by different things based on role
  return ['ruby', 'javascript'];
});

App.Router = Tower.Router.extend({
  route: '/',
  action: function() {

  },
  posts: Tower.Route.extend({
    route: '/posts',
    index: Tower.Route.extend({
      route: '/',
      tags: Tower.param('tags'),
      title: App.Post.field('title') // maybe?
    })
  })
});

router.get('params') // bindable ember object
router.get('url') // bindable ember object

// 1. serialization
// 2. param/field
// 3. validation

@lancejpollard
Copy link
Author

Tower.addValidator('format', function(format) {

}, function(key, value) {
  return value.match(this.format);
});

var validate = Tower.validator('format').compile(/asdf/);
validate.call(obj, key, val);

// or

Tower.validate('format', obj, key, val);
  // which internally calls compile().call(obj, key, val);

@lancejpollard
Copy link
Author

Something like this:

Tower.validationSuccess = (callback) ->
  callback() if callback
  true

Tower.validationFailure = (object, key, message, callback) ->
  errors = Ember.get(object, 'errors')
  errors[key] ||= []
  errors[key].push(message)
  callback() if callback
  false

Tower.validator = ->
  keys  = _.args(arguments)
  definition = keys.pop()

  if typeof definition == 'function'
    definition = validate: definition
    if typeof keys[keys.length - 1] == 'function'
      definition.compile = keys.pop()

  _.each , (key) ->
    Tower.validators[key] = definition

  Tower

# https://github.com/chriso/node-validator
Tower.validationConfiguration =
  success: ->
  failure: ->

Tower.validators = {}

Tower.validator 'presence', 'required', (key, value, callback) ->
  if _.isPresent(value)
    Tower.validationSuccess(callback)
  else
    Tower.validationFailure(
      object,
      key,
      Tower.t('model.errors.presence', attribute: key),
      callback
    )

Tower.validator 'count', 'length',
  compile: (value) ->
    validate: @validate
    value:    parseInt(@value)

  validate: (obj, key, value) ->
    @value == parseInt(value)

# should handle string, number, float, date
Tower.validator 'min', 'gte', '>=',
  compile: (value) ->
    if arguments.length == 1 && typeof value == 'number'
      validate: @validate
      value: value

  validate: (key, value) ->
    value >= @value

Tower.validator 'gt', '>', (key, value) ->
Tower.validator 'max', 'lte', '<=', (key, value) ->
Tower.validator 'lt', '>', (key, value) ->
Tower.validator 'in', 'only', 'values', 'accepts', (key, value) ->
Tower.validator 'nin', 'notIn', 'except', (key, value) ->
Tower.validator 'uniqueness', 'unique', 'uniq', (key, value) ->
Tower.validator 'format', (key, value) ->
  console.log arguments

# Tower.validation('format', /[a-z]/)
Tower.validation = (type) ->
  validator = Tower.validators[type]

  throw new Error('Validator "' + type + '" is undefined') unless validator

  if validator.compile && arguments.length > 1
    validator = validator.compile()

  validator

Tower.validate = (type, object, key, callback) ->
  Tower.validation(type).validate(object, key, callback)

@lancejpollard
Copy link
Author

Tower.validator 'count', 'length', (value) ->
  @value = parseInt(value)

  validate = (obj, key, value) ->
    @value == parseInt(value)

# by doing it like this, rather than return turning a function, it
# clears out unused variables from memory.
Tower.validator 'count', 'length',
  compile: (value) ->
    validate: @validate
    value: parseInt(value)

  validate: (obj, key, value) ->
    @value == parseInt(value)

@lancejpollard
Copy link
Author

It's almost as if you can do this:

Tower.operation('gte', '>=', function(a, b) {
  return a >= b;
});

Tower.validation('gte', function(a) {
  operation = Tower.operation('gte');

  return function(b) {
    operation(a, b);
  }
});

...define all the operations first, which are simpler and more generic, and usable by themselves. Then define the validations as abstractions on top of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment