-
-
Save lancejpollard/d3aa5a9ceb52dd478205 to your computer and use it in GitHub Desktop.
Tower Validation API Ideas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
// 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
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);
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)
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)
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
// 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