1
0
Fork 0
arangodb/js/server/modules/org/arangodb/foxx.js

1059 lines
32 KiB
JavaScript

/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true */
/*global module, require, exports */
////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx application
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Lucas Dohmen
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var Application,
RequestContext,
BaseMiddleware,
FormatMiddleware,
Model,
Repository,
_ = require("underscore"),
backbone_helpers = require("backbone"),
db = require("org/arangodb").db,
fs = require("fs"),
console = require("console"),
INTERNAL = require("internal"),
foxxManager = require("org/arangodb/foxx-manager"),
internal = {};
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoAPI
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_createUrlObject
/// @brief create a new url object
///
/// This creates a new `UrlObject`.
/// ArangoDB uses a certain structure we refer to as `UrlObject`.
/// With the following function (which is only internal, and not
/// exported) you can create an UrlObject with a given URL,
/// a constraint and a method. For example:
///
/// @EXAMPLES
///
/// @code
/// internal.createUrlObject('/lecker/gans', null, 'get');
/// @endcode
////////////////////////////////////////////////////////////////////////////////
internal.createUrlObject = function (url, constraint, method) {
'use strict';
var urlObject = {};
if (!_.isString(url)) {
throw "URL has to be a String";
}
urlObject.match = url;
urlObject.methods = [method];
if (!_.isUndefined(constraint)) {
urlObject.constraint = constraint;
}
return urlObject;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_initializer
/// @brief Create a new Application
///
/// This creates a new Application. It takes two optional arguments as displayed above:
/// * **The URL Prefix:** All routes you define within will be prefixed with it
/// * **The Template Collection:** More information in the template section
///
/// @EXAMPLES
///
/// @code
/// app = new Application({
/// urlPrefix: "/wiese",
/// templateCollection: "my_templates"
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
Application = function (options) {
'use strict';
var urlPrefix, templateCollection, myMiddleware;
options = options || {};
urlPrefix = options.urlPrefix || "";
templateCollection = options.templateCollection;
this.routingInfo = {
routes: []
};
this.requires = {};
this.helperCollection = {};
this.routingInfo.urlPrefix = urlPrefix;
if (_.isString(templateCollection)) {
this.templateCollection = db._collection(templateCollection) ||
db._create(templateCollection);
myMiddleware = new BaseMiddleware(templateCollection, this.helperCollection);
} else {
myMiddleware = new BaseMiddleware();
}
this.routingInfo.middleware = [
{
url: {match: "/*"},
action: {callback: myMiddleware.stringRepresentation}
}
];
this.routingInfo.repositories = {};
};
_.extend(Application.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_start
/// @brief Start the application
///
/// Sometimes it is a good idea to actually start the application
/// you wrote. If this precious moment has arrived, you should
/// use this function.
/// You have to provide the start function with the `applicationContext`
/// variable.
////////////////////////////////////////////////////////////////////////////////
start: function (context) {
'use strict';
var prefix = context.prefix;
this.routingInfo.urlPrefix = prefix + "/" + this.routingInfo.urlPrefix;
context.requires = this.requires;
context.routingInfo = this.routingInfo;
},
registerRepository: function (name, opts) {
this.routingInfo.repositories[name] = opts || {};
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_handleRequest
/// @brief Handle a request
///
/// The `handleRequest` method is the raw way to create a new
/// route. You probably wont call it directly, but it is used
/// in the other request methods:
///
/// When defining a route you can also define a so called 'parameterized'
/// route like `/gaense/:stable`. In this case you can later get the value
/// the user provided for `stable` via the `params` function (see the Request
/// object).
///
/// @EXAMPLES
///
/// @code
/// app.handleRequest("get", "/gaense", function (req, res) {
/// //handle the request
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
handleRequest: function (method, route, callback) {
'use strict';
var newRoute = {
url: internal.createUrlObject(route, undefined, method),
action: {
callback: String(callback)
},
docs: {
parameters: [],
errorResponses: [],
httpMethod: method.toUpperCase()
}
};
this.routingInfo.routes.push(newRoute);
return new RequestContext(newRoute);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_head
/// @brief Handle a `head` request
///
/// This handles requests from the HTTP verb `head`.
/// As with all other requests you can give an option as the third
/// argument, or leave it blank. You have to give a function as
/// the last argument however. It will get a request and response
/// object as its arguments
////////////////////////////////////////////////////////////////////////////////
head: function (route, callback) {
'use strict';
return this.handleRequest("head", route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_get
/// @brief Manage a `get` request
///
/// This handles requests from the HTTP verb `get`.
/// See above for the arguments you can give. An example:
///
/// @EXAMPLES
///
/// @code
/// app.get('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
get: function (route, callback) {
'use strict';
return this.handleRequest("get", route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_post
/// @brief Tackle a `post` request
///
/// This handles requests from the HTTP verb `post`.
/// See above for the arguments you can give. An example:
///
/// @EXAMPLES
///
/// @code
/// app.post('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
post: function (route, callback) {
'use strict';
return this.handleRequest("post", route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_put
/// @brief Sort out a `put` request
///
/// This handles requests from the HTTP verb `put`.
/// See above for the arguments you can give. An example:
///
/// @EXAMPLES
///
/// @code
/// app.put('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
put: function (route, callback) {
'use strict';
return this.handleRequest("put", route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_patch
/// @brief Take charge of a `patch` request
///
/// This handles requests from the HTTP verb `patch`.
/// See above for the arguments you can give. An example:
///
/// @EXAMPLES
///
/// @code
/// app.patch('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
patch: function (route, callback) {
'use strict';
return this.handleRequest("patch", route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_delete
/// @brief Respond to a `delete` request
///
/// This handles requests from the HTTP verb `delete`.
/// See above for the arguments you can give.
/// **A word of warning:** Do not forget that `delete` is
/// a reserved word in JavaScript and therefore needs to be
/// called as app['delete']. There is also an alias `del`
/// for this very reason.
///
/// @EXAMPLES
///
/// @code
/// app['delete']('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
///
/// app.del('/gaense/stall', function (req, res) {
/// // Take this request and deal with it!
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
'delete': function (route, callback) {
'use strict';
return this.handleRequest("delete", route, callback);
},
del: function (route, callback) {
'use strict';
return this['delete'](route, callback);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_before
/// @brief Before
///
/// The before function takes a path on which it should watch
/// and a function that it should execute before the routing
/// takes place. If you do omit the path, the function will
/// be executed before each request, no matter the path.
/// Your function gets a Request and a Response object.
/// An example:
///
/// @EXAMPLES
///
/// @code
/// app.before('/high/way', function(req, res) {
/// //Do some crazy request logging
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
before: function (path, func) {
'use strict';
if (_.isUndefined(func)) {
func = path;
path = "/*";
}
this.routingInfo.middleware.push({
url: {match: path},
action: {
callback: "function (req, res, opts, next) { " + String(func) + "(req, res); next(); })"
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_after
/// @brief After
///
/// This works pretty similar to the before function.
/// But it acts after the execution of the handlers
/// (Big surprise, I suppose).
///
/// An example:
///
/// @EXAMPLES
///
/// @code
/// app.after('/high/way', function(req, res) {
/// //Do some crazy response logging
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
after: function (path, func) {
'use strict';
if (_.isUndefined(func)) {
func = path;
path = "/*";
}
this.routingInfo.middleware.push({
url: {match: path},
action: {
callback: "function (req, res, opts, next) { next(); " + String(func) + "(req, res); })"
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_helper
/// @brief The ViewHelper concept
///
/// If you want to use a function inside your templates, the ViewHelpers
/// will come to rescue you. Define them on your app like in the example.
///
/// Then you can just call this function in your template's JavaScript
/// blocks.
///
/// @EXAMPLES
///
/// @code
/// app.helper("link_to", function (identifier) {
/// return urlRepository[identifier];
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
helper: function (name, func) {
'use strict';
this.helperCollection[name] = func;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_application_accepts
/// @brief Shortform for using the FormatMiddleware
///
/// Shortform for using the FormatMiddleware
/// More information about the FormatMiddleware in the corresponding section.
/// This is a shortcut to add the middleware to your application:
///
/// @EXAMPLES
///
/// @code
/// app.accepts(["json"], "json");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
accepts: function (allowedFormats, defaultFormat) {
'use strict';
this.routingInfo.middleware.push({
url: { match: "/*" },
action: {
callback: (new FormatMiddleware(allowedFormats, defaultFormat)).stringRepresentation
}
});
}
});
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_initializer
/// @brief Context of a Request Definition
///
/// Used for documenting and constraining the routes.
////////////////////////////////////////////////////////////////////////////////
RequestContext = function (route) {
'use strict';
this.route = route;
this.typeToRegex = {
"int": "/[0-9]+/",
"string": "/.+/"
};
};
_.extend(RequestContext.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_pathParam
/// @brief Describe a Path Parameter
///
/// Describe a Path Paramter:
/// If you defined a route "/foxx/:id", you can constrain which format the id
/// can have by giving a type. We currently support the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter.
///
/// @EXAMPLES
///
/// @code
/// app.get("/foxx/:id", function {
/// // Do something
/// }).pathParam("id", {
/// description: "Id of the Foxx",
/// dataType: "int"
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
pathParam: function (paramName, attributes) {
'use strict';
var url = this.route.url,
docs = this.route.docs,
constraint = url.constraint || {};
constraint[paramName] = this.typeToRegex[attributes.dataType];
this.route.url = internal.createUrlObject(url.match, constraint, url.methods[0]);
this.route.docs.parameters.push({
paramType: "path",
name: paramName,
description: attributes.description,
dataType: attributes.dataType,
required: true
});
return this;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_queryParam
/// @brief Describe a Query Parameter
///
/// Describe a Query Parameter:
/// If you defined a route "/foxx", you can constrain which format a query
/// parameter (`/foxx?a=12`) can have by giving it a type.
/// We currently support the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter, if it is required and
/// if you can provide the parameter multiple times.
///
/// @EXAMPLES
///
/// @code
/// app.get("/foxx", function {
/// // Do something
/// }).queryParam("id", {
/// description: "Id of the Foxx",
/// dataType: "int",
/// required: true,
/// allowMultiple: false
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
queryParam: function (paramName, attributes) {
'use strict';
this.route.docs.parameters.push({
paramType: "query",
name: paramName,
description: attributes.description,
dataType: attributes.dataType,
required: attributes.required,
allowMultiple: attributes.allowMultiple
});
return this;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_nickname
/// Set the nickname for this route in the documentation
////////////////////////////////////////////////////////////////////////////////
nickname: function (nickname) {
'use strict';
if (!nickname.match(/^[a-z]+$/)) {
throw "Nickname may only contain [a-z], not '" + nickname + "'";
}
this.route.docs.nickname = nickname;
return this;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_summary
/// @brief Set the summary for this route in the documentation
///
/// Set the summary for this route in the documentation
/// Can't be longer than 60 characters
////////////////////////////////////////////////////////////////////////////////
summary: function (summary) {
'use strict';
if (summary.length > 60) {
throw "Summary can't be longer than 60 characters";
}
this.route.docs.summary = summary;
return this;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_notes
/// @brief Set the notes for this route in the documentation
///
/// Set the notes for this route in the documentation
////////////////////////////////////////////////////////////////////////////////
notes: function (notes) {
'use strict';
this.route.docs.notes = notes;
return this;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_errorResponse
/// @brief Document an error response
///
/// Document the error response for a given error code with a reason for the
/// occurrence.
////////////////////////////////////////////////////////////////////////////////
errorResponse: function (code, reason) {
'use strict';
this.route.docs.errorResponses.push({
code: code,
reason: reason
});
return this;
}
});
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_initializer
/// @brief The Base Middleware
///
/// The `BaseMiddleware` manipulates the request and response
/// objects to give you a nicer API.
////////////////////////////////////////////////////////////////////////////////
BaseMiddleware = function (templateCollection, helperCollection) {
'use strict';
var middleware = function (request, response, options, next) {
var responseFunctions,
requestFunctions,
_ = require("underscore");
requestFunctions = {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_request_body
/// @brief The superfluous `body` function
///
/// Get the body of the request
////////////////////////////////////////////////////////////////////////////////
body: function () {
return this.requestBody;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_request_params
/// @brief The jinxed `params` function
///
/// Get the parameters of the request. This process is two-fold:
///
/// 1. If you have defined an URL like `/test/:id` and the user
/// requested `/test/1`, the call `params("id")` will return `1`.
/// 2. If you have defined an URL like `/test` and the user gives a
/// query component, the query parameters will also be returned.
/// So for example if the user requested `/test?a=2`, the call
/// `params("a")` will return `2`.
////////////////////////////////////////////////////////////////////////////////
params: function (key) {
var ps = {};
_.extend(ps, this.urlParameters);
_.extend(ps, this.parameters);
return ps[key];
}
};
responseFunctions = {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_response_status
/// @brief The straightforward `status` function
///
/// Set the status code of your response, for example:
///
/// @EXAMPLES
///
/// @code
/// response.status(404);
/// @endcode
////////////////////////////////////////////////////////////////////////////////
status: function (code) {
this.responseCode = code;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_response_set
/// @brief The radical `set` function
///
/// Set a header attribute, for example:
///
/// @EXAMPLES
///
/// @code
/// response.set("Content-Length", 123);
/// response.set("Content-Type", "text/plain");
///
/// // or alternatively:
///
/// response.set({
/// "Content-Length": "123",
/// "Content-Type": "text/plain"
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
set: function (key, value) {
var attributes = {};
if (_.isUndefined(value)) {
attributes = key;
} else {
attributes[key] = value;
}
_.each(attributes, function (value, key) {
key = key.toLowerCase();
this.headers = this.headers || {};
this.headers[key] = value;
if (key === "content-type") {
this.contentType = value;
}
}, this);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_response_json
/// @brief The magical `json` function
///
/// Set the content type to JSON and the body to the
/// JSON encoded object you provided.
///
/// @EXAMPLES
///
/// @code
/// response.json({'born': 'December 12, 1915'});
/// @endcode
////////////////////////////////////////////////////////////////////////////////
json: function (obj) {
this.contentType = "application/json";
this.body = JSON.stringify(obj);
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_BaseMiddleware_response_render
/// @brief The mysterious `render` function
///
/// If you initialize your Application with a `templateCollection`,
/// you're in luck now.
/// It expects documents in the following form in this collection:
///
/// {
/// path: "high/way",
/// content: "hallo <%= username %>",
/// contentType: "text/plain",
/// templateLanguage: "underscore"
/// }
///
/// The `content` is the string that will be rendered by the template
/// processor. The `contentType` is the type of content that results
/// from this call. And with the `templateLanguage` you can choose
/// your template processor. There is only one choice now: `underscore`.
///
/// If you call render, Application will look
/// into the this collection and search by the path attribute.
/// It will then render the template with the given data:
///
///
/// Which would set the body of the response to `hello Application` with the
/// template defined above. It will also set the `contentType` to
/// `text/plain` in this case.
///
/// In addition to the attributes you provided, you also have access to
/// all your view helpers. How to define them? Read above in the
/// ViewHelper section.
///
/// @EXAMPLES
///
/// @code
/// response.render("high/way", {username: 'Application'})
/// @endcode
////////////////////////////////////////////////////////////////////////////////
render: function (templatePath, data) {
var template;
if (_.isUndefined(templateCollection)) {
throw "No template collection has been provided when creating a new FoxxApplication";
}
template = templateCollection.firstExample({path: templatePath });
if (_.isNull(template)) {
throw "Template '" + templatePath + "' does not exist";
}
if (template.templateLanguage !== "underscore") {
throw "Unknown template language '" + template.templateLanguage + "'";
}
this.body = _.template(template.content, _.extend(data, helperCollection));
this.contentType = template.contentType;
}
};
request = _.extend(request, requestFunctions);
response = _.extend(response, responseFunctions);
next();
};
return {
stringRepresentation: String(middleware),
functionRepresentation: middleware
};
};
FormatMiddleware = function (allowedFormats, defaultFormat) {
'use strict';
var stringRepresentation, middleware = function (request, response, options, next) {
var parsed, determinePathAndFormat;
determinePathAndFormat = function (path, headers) {
var mimeTypes = require("org/arangodb/mimetypes").mimeTypes,
extensions = require("org/arangodb/mimetypes").extensions,
urlFormatToMime = function (urlFormat) {
var mimeType;
if (mimeTypes[urlFormat]) {
mimeType = mimeTypes[urlFormat][0];
} else {
mimeType = undefined;
}
return mimeType;
},
mimeToUrlFormat = function (mimeType) {
var urlFormat;
if (extensions[mimeType]) {
urlFormat = extensions[mimeType][0];
} else {
urlFormat = undefined;
}
return urlFormat;
},
parsed = {
contentType: headers.accept
};
path = path.split('.');
if (path.length === 1) {
parsed.path = path.join();
} else {
parsed.format = path.pop();
parsed.path = path.join('.');
}
if (parsed.contentType === undefined && parsed.format === undefined) {
parsed.format = defaultFormat;
parsed.contentType = urlFormatToMime(defaultFormat);
} else if (parsed.contentType === undefined) {
parsed.contentType = urlFormatToMime(parsed.format);
} else if (parsed.format === undefined) {
parsed.format = mimeToUrlFormat(parsed.contentType);
}
if (parsed.format !== mimeToUrlFormat(parsed.contentType)) {
throw "Contradiction between Accept Header and URL.";
}
if (allowedFormats.indexOf(parsed.format) < 0) {
throw "Format '" + parsed.format + "' is not allowed.";
}
return parsed;
};
try {
parsed = determinePathAndFormat(request.path, request.headers);
request.path = parsed.path;
request.format = parsed.format;
response.contentType = parsed.contentType;
next();
} catch (e) {
response.responseCode = 406;
response.body = e;
}
};
stringRepresentation = String(middleware)
.replace("allowedFormats", JSON.stringify(allowedFormats))
.replace("defaultFormat", JSON.stringify(defaultFormat));
return {
functionRepresentation: middleware,
stringRepresentation: stringRepresentation
};
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_initializer
/// @brief Create a new instance of Model
///
/// If you initialize a model, you can give it initial data
/// as an object.
///
/// @EXAMPLES
///
/// @code
/// instance = new Model({
/// a: 1
/// });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
Model = function (attributes) {
'use strict';
this.attributes = attributes || {};
};
_.extend(Model.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_get
/// @brief Get the value of an attribute
///
/// Get the value of an attribute
/// @EXAMPLES
///
/// @code
/// instance = new Model({
/// a: 1
/// });
///
/// instance.get("a");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
get: function (attributeName) {
return this.attributes[attributeName];
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_set
/// @brief Set the value of an attribute
///
/// Set the value of an attribute
/// @EXAMPLES
///
/// @code
/// instance = new Model({
/// a: 1
/// });
///
/// instance.set("a", 2);
/// @endcode
////////////////////////////////////////////////////////////////////////////////
set: function (attributeName, value) {
this.attributes[attributeName] = value;
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_has
/// @brief Returns true if the attribute is set to a non-null or non-undefined value.
///
/// Returns true if the attribute is set to a non-null or non-undefined value.
/// @EXAMPLES
///
/// @code
/// instance = new Model({
/// a: 1
/// });
///
/// instance.has("a"); //=> true
/// instance.has("b"); //=> false
/// @endcode
////////////////////////////////////////////////////////////////////////////////
has: function (attributeName) {
return !(_.isUndefined(this.attributes[attributeName]) ||
_.isNull(this.attributes[attributeName]));
},
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_toJSON
/// @brief Return a copy of the model which can be saved into ArangoDB
///
/// Return a copy of the model which can be saved into ArangoDB
/// (or send to the client).
////////////////////////////////////////////////////////////////////////////////
toJSON: function () {
return this.attributes;
}
});
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_extend
/// @brief Extend the Model prototype to add or overwrite methods.
///
/// Extend the Model prototype to add or overwrite methods.
////////////////////////////////////////////////////////////////////////////////
Model.extend = backbone_helpers.extend;
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_repository_initializer
/// @brief Create a new instance of Repository
///
/// Create a new instance of Repository
/// A Foxx Repository is always initialized with the prefix, the collection and the modelPrototype.
/// If you initialize a model, you can give it initial data as an object.
///
/// @EXAMPLES
///
/// @code
/// instance = new Repository(prefix, collection, modelPrototype);
/// @endcode
////////////////////////////////////////////////////////////////////////////////
Repository = function (prefix, collection, modelPrototype) {
'use strict';
this.prefix = prefix;
this.collection = collection;
this.modelPrototype = modelPrototype;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_repository_extend
/// @brief Extend the Repository prototype to add or overwrite methods.
///
/// Extend the Repository prototype to add or overwrite methods.
////////////////////////////////////////////////////////////////////////////////
Repository.extend = backbone_helpers.extend;
exports.Application = Application;
exports.Model = Model;
exports.Repository = Repository;
exports.BaseMiddleware = BaseMiddleware;
exports.FormatMiddleware = FormatMiddleware;
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
/// -----------------------------------------------------------------------------
/// --SECTION-- END-OF-FILE
/// -----------------------------------------------------------------------------
/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End: