-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom types #180
Custom types #180
Changes from all commits
0c5db2a
6b04336
48385ba
e3e5211
c20c03a
dbb8d7e
900f642
9224bd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -189,7 +189,7 @@ Fortune.prototype.resources = function(req){ | |
var _this = this; | ||
|
||
function filterHooks(hooks, time, type, resource, name){ | ||
var ary = hooks[time][type]; | ||
var ary = (hooks[time] || [])[type] || []; | ||
return _.reduce(_this._hookFilters, function(memo, filter){ | ||
return filter(memo, name, time.replace('_', ''), type, resource) | ||
}, ary); | ||
|
@@ -198,18 +198,19 @@ Fortune.prototype.resources = function(req){ | |
var resources = _.map(_this._resources, function(md, name){ | ||
var schema = _.clone(md.schema); | ||
|
||
_.each(schema, function(v,k){ | ||
var vIsFunction = _.isFunction(v), | ||
typeFn = vIsFunction ? v : v.type; | ||
|
||
if(typeFn){ | ||
typeFn = typeFn.toString(); | ||
typeFn = typeFn.substr('function '.length); | ||
typeFn = typeFn.substr(0, typeFn.indexOf('(')); | ||
var jsonFriendify = function(schema) { | ||
_.each(schema, function(v,k){ | ||
if(_.isFunction(v)){ | ||
schema[k] = v.name; | ||
} | ||
else if(_.isObject(v)) { | ||
schema[k] = jsonFriendify(v); | ||
} | ||
}); | ||
return schema; | ||
} | ||
|
||
schema[k] = vIsFunction ? typeFn : _.extend({}, v, {type: typeFn}); | ||
} | ||
}); | ||
schema = jsonFriendify(schema); | ||
|
||
var hooks = { | ||
beforeRead: _.map(filterHooks(md.hooks, '_before', 'read', {}, name), function(h){ return h.name}), | ||
|
@@ -252,6 +253,14 @@ Fortune.prototype._exposeResourceDefinitions = function() { | |
}); | ||
}; | ||
|
||
Fortune.prototype.customType = function(name, schema, options, schemaCallback) { | ||
this._customTypes = this._customTypes || {} | ||
this._customTypes[name] = _.extend({}, options, { name: name, schema: schema }) | ||
this._resource = name; | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Define a resource and setup routes simultaneously. A schema field may be either a native type, a plain object, or a string that refers to a related resource. | ||
* | ||
|
@@ -303,6 +312,20 @@ Fortune.prototype.resource = function(name, schema, options, schemaCallback) { | |
return this; | ||
} | ||
|
||
var customTypes = {}; | ||
schema = _.mapValues(schema, function(value, key) { | ||
if((typeof value).toLowerCase() === "string") { | ||
|
||
// Verify there is a custom type with the name | ||
var customType = _this._customTypes[value]; | ||
customTypes[key] = customType; | ||
|
||
// Custom type has Mixed schema instead | ||
return customType.schema; | ||
} | ||
return value; | ||
}); | ||
|
||
this._resources = this._resources || {}; | ||
this._resources[name] = { | ||
actions: actionsObj, | ||
|
@@ -316,8 +339,47 @@ Fortune.prototype.resource = function(name, schema, options, schemaCallback) { | |
authMethods: authMethods, | ||
validation: validation | ||
}; | ||
|
||
plugins.init(this, this._resources[name]); | ||
|
||
hooks.initGlobalHooks(_this._resources[name], _this.options); | ||
|
||
var customHooks = _.flatten(_.map(_.keys(customTypes), function(key) { | ||
// Modify a names of the hooks to include field name they applied to | ||
_.each(customTypes[key].hooks, function(whenHooks, when) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it expect a structure like this? It kind of doesn't follow semantics of hooks?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it does. But not sure what you meant by "It kind of doesn't follow semantics of hooks?". Can you pls extend the statement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usage example, just in case: https://bitbucket.org/flyvictor/user-service/pull-requests/853/sup-661-custom-type-usage-example-do-not/diff There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, think i got it. In design doc these hooks were passed through as properties of options, but chaining is probably even better. |
||
_.each(whenHooks, function(actionHooks, action) { | ||
_.each(actionHooks, function(actionHook) { | ||
if(actionHook.name) { | ||
actionHook.name = [key, actionHook.name].join('-'); | ||
} | ||
// Set highest priority | ||
actionHook.priority = 1000; | ||
|
||
// Bind the handler to particular data type | ||
var handler = actionHook.fn | ||
if(handler) { | ||
actionHook.fn = function(req, res) { | ||
if(this[key]) { | ||
this[key] = handler.call(this[key], req, res); | ||
} | ||
return this; | ||
} | ||
} | ||
}); | ||
}); | ||
}) | ||
return customTypes[key].hooks; | ||
})); | ||
|
||
function mergeArray(to, from) { | ||
if(_.isArray(to) && _.isArray(from)) { | ||
return (to || []).concat(from); | ||
} | ||
} | ||
|
||
var mergedHooks = _.merge(_.merge.apply(this, customHooks, mergeArray) || {}, _this._resources[name].hooks, mergeArray); | ||
_this._resources[name].hooks = mergedHooks; | ||
|
||
//Register updates in queryParser. Should be called here to make sure that all resources are registered | ||
_this._exposeResourceDefinitions(); | ||
_this._querytree = querytree.init(this); | ||
|
@@ -338,15 +400,15 @@ Fortune.prototype.resource = function(name, schema, options, schemaCallback) { | |
function () { | ||
schema = _this._preprocessSchema(schema); | ||
|
||
// Store a copy of the input. | ||
_this._schema[name] = _.clone(schema); | ||
try { | ||
// Store a copy of the input. | ||
_this._schema[name] = _.clone(schema); | ||
schema = _this.adapter.schema(name, schema, options, schemaCallback); | ||
|
||
// Pass any upsertKeys to the schema | ||
schema.upsertKeys = upsertKeys || []; | ||
|
||
_this._route(name, _this.adapter.model(name, schema,modelOptions), _this._resources, inflect, _this._querytree, _this._metadataProviders); | ||
_this._route(name, _this.adapter.model(name, schema, modelOptions), _this._resources, inflect, _this._querytree, _this._metadataProviders); | ||
_this._resourceInitialized(); | ||
} catch(error) { | ||
console.trace('There was an error loading the "' + name + '" resource. ' + error.stack || err); | ||
|
@@ -436,7 +498,6 @@ function GlobalHook(time, type){ | |
} | ||
|
||
function Hook(time, type){ | ||
|
||
return function(name, hooksArray, inlineConfig){ | ||
var that = this; | ||
if (this.options.throwOnLegacyTransforms && (_.isFunction(name) || _.isFunction(hooksArray))){ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ var hooks = {}; | |
if (req.query['fail' + type]) { | ||
console.log('Failing hook',type); | ||
_.defer(function() { | ||
res.send(321); | ||
res.sendStatus(321); | ||
}); | ||
if (req.query['fail' + type] === 'boolean') | ||
return false; | ||
|
@@ -66,14 +66,32 @@ module.exports = function(options, port, ioPort) { | |
} | ||
}]); | ||
|
||
|
||
app.beforeAll(hooks.beforeAll) | ||
.beforeAllRead(hooks.beforeAllRead) | ||
.beforeAllWrite(hooks.beforeAllWrite) | ||
.afterAll(hooks.afterAll) | ||
.afterAllRead(hooks.afterAllRead) | ||
.afterAllWrite(hooks.afterAllWrite) | ||
|
||
.customType("location", { | ||
lat: Number, | ||
lon: Number | ||
}) | ||
.beforeWrite([{ | ||
name: 'todb', | ||
init: function() { | ||
return function(req, res){ | ||
return this; | ||
} | ||
} | ||
}]) | ||
.afterRead([{ | ||
name: 'fromdb', | ||
init: function(){ | ||
return function(req, res){ | ||
return this; | ||
} | ||
} | ||
}]) | ||
.resource('person', { | ||
name: String, | ||
official: String, | ||
|
@@ -97,6 +115,7 @@ module.exports = function(options, port, ioPort) { | |
nestedField1: String, | ||
nestedField2: Number | ||
}], | ||
location: 'location', | ||
upsertTest : String, | ||
_tenantId: String | ||
}, { | ||
|
@@ -195,7 +214,7 @@ module.exports = function(options, port, ioPort) { | |
|
||
.before('person pet', function(req, res){ | ||
if (this.email === '[email protected]'){ | ||
res.send(321); | ||
res.sendStatus(321); | ||
return false; | ||
} | ||
return this; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The schema here is the thing passed into mongoose, right? It should also accept userland schema to expose in
/resources
output.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Pls take a look. Result looks like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like i broke something, hold on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected. Right now, it only accepts user-level schemas and make validations against them. On data level any complex objects are converted to
mongoose.Schema.Mixed
data type on adapter level (see here: https://github.com/flyvictor/fortune/blob/master/lib/adapters/mongodb.js#L68).Also, simpler one-scheme syntax follows the same look and feel of an ordinal fortune resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if Mixed would really work here. If mongoose doesn't cast types for us (and it doesn't for Mixed type) we could get inconsistent data types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created an issue for this: #182