I would like to extend sailsjs to have something similar to rails strong params
i.e.
req.params.limit(["email", "password", "password_confirmation"])
I have created this already as an add on, but I would like it to automatically attach to the request.params
Addon:
limit = function(limiters){
params = this.all();
self = Object();
limiters.forEach(function(limiter){
if(params[limiter]){
self[limiter] = params[limiter]
}
return self;
}
request:
req.params.limit = limit;
req.params.limit(["email", "password"]);
How would I go about adding this to the framework as a module?
i think you could just create a policy
// policies/limit.js
limit = function(limiters){
params = this.all();
self = Object();
limiters.forEach(function(limiter){
if(params[limiter]){
self[limiter] = params[limiter]
}
return self;
}
module.exports = function limit (req, res, next) {
req.params.limit = limit;
req.params.limit(["email", "password"]);
next();
};
then you can add the policy in your ./config/policies.js file. the example is for all controllers/actions. in the link above is the documentation on how to add it to specific actions.
// config/policies.js
module.exports.policies = {
'*': 'limit'
};
EDIT: of course you can do the call to req.params.limit(...); in your controller if you don't want it static in your policy. policies are in general nothing more than express middlewares
I see the use cases for this on controller actions, limiting possible searches and so on.
On the other hand: When you just want to your model not to take up undefined attributes found in a create request you could simply set schema to true for the corresponding model:
// models/example.js
module.exports = {
schema: true,
attributes: {...}
}
Setting it globally:
// config/models.js
module.exports.models = {
schema: true,
...
}
Related
I have not found an easy way to extend Mongoose Schema/Model methods because of the way that mongoose handles them, and because of the fact that mongoose=require('mongoose') is a singelton.
So, I am 'faking' class inheritance here:
'use strict';
var _ = require('lodash');
module.exports = function(MongooseModel, options) {
var Collection = {};
_.assign(Collection, _.toPlainObject(MongooseModel));
Collection.pluralName = Collection.modelName + 's';
Collection.foo = Collection.bar;
return Collection
};
Does anyone have a more elegant solution?
EDIT:
Turns out the above solution doesn't work. For instance, using Collection.find({}, function(err, docs) {...}) will error when Mongo tries to create "docs" from a model that has not been registered with Mongoose.
So, what I've done is now completely inelegant:
'use strict';
var _ = require('lodash');
module.exports = function(MongooseModel, options) {
var Collection = MongooseModel;
...
return Collection
};
There are some ways to try and do this, though not sure exactly what your trying to extend.
You can add instance methods <schema>.methods.<mymethod> = function(){}
// define a schema
var animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function (cb) {
return this.model('Animal').find({ type: this.type }, cb);
}
And you can add static methods <schema>.statics.<mymethod> = function(){}
// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function (name, cb) {
return this.find({ name: new RegExp(name, 'i') }, cb);
}
var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function (err, animals) {
console.log(animals);
});
Examples are from the mongoose docs - just search for "statics".
The statics functions you can call on a model. The methods are usually functions that work with an instance of a document returned from a query or created with new.
The question:
As I understand in sails.js during initialization process Services are initialized before Models.
Is there any possibility to change this behavior? To make Models load before Services.
If it's not, then how can I load particular settings from the database to use them to build instance of my class described in some Service during this Service initialization?
A little bit code for solidity:
api/models/Model.js
console.log("Model Identified");
module.exports = {
attributes: {
name: { type: 'string', required: true, size: 15 },
//Some extra secret fields
}
};
...
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
MyCoolService.prototype.setOptions = function(options){
//Set values for MyCoolService fields.
}
//Some other methods
var myCoolServiceWithSettingsFromDb = new MyCoolService();
//That's the place
model.findOne(sails.config.myApplication.settingsId).exec(function(err,result){
if(!err)
myCoolServiceWithSettingsFromDb.setOptions(result);
});
module.exports = myCoolServiceWithSettingsFromDb;
It's because you instantiate object in service with constructor that needs sails that not exist. Try use this at MyCoolService;
module.exports = {
someOption: null,
method: function () {
var that = this;
sails.models.model.findOne(sails.config.myApplication.settingsId)
.exec(function (err, result) {
if (!err)
that.someOption = result;
});
}
};
that method can be called by sails.services.mycoolservice.method() or simply MyCoolService.method() to give your service some option from DB.
If you want to initiate them at Sails start, call that method at config/bootstrap.js
Thanks to Andi Nugroho Dirgantara,
I ended up with this solution (I still don't like it much, but it works):
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
//All the same as in question
//The instance
var instance;
module.exports = module.exports = {
init: function(options) {
instance = new MyCoolService(options);
},
get: function() {
return instance;
},
constructor: MyCoolService
};
config/bootstrap.js
...
Model.findOrCreate({ id: 1 }, sails.config.someDefaultSettings).exec(function(err, result) {
if (err)
return sails.log.error(err);
result = result || sails.config.someDefaultSettings;
MyCoolService.init(result);
return sails.log.verbose("MyCoolService Created: ", TbcPaymentProcessorService.get());
});
...
tests/unit/service/MyCoolService.test.js
...
describe('MyCoolService', function() {
it('check MyCoolService', function(done) {
assert.notDeepEqual(MyCoolService.get(), sails.config.someDefaultSettings);
done();
});
});
...
It works: the service is instantiated once while bootstraping and it's instance is avaliable everywhere.
But to me this solution still weird... I still don't understand how to globally instantiate instance of my service (for use in a lot of controllers) and make it the best way.
I'm very, very new to the whole NodeJS stack, and I'm trying to rough up a simple login system for practice.
Jumping to my question,
app.js
...
var mongoose = require( 'mongoose' );
var templates = require( './data/inc.js' ); // includes schema structures
...
user.js - included in inc.js
...
module.exports =
{
"Schema" : new exports.mongoose.Schema({
"uid": mongoose.Schema.Types.ObjectId,
"username": { type:String, unique:true },
"alias": String,
"credentials":
{
"salt": String,
"password": String,
"key": String
},
"profile":
{
"age": { type: Number, min: 18 }
},
"last_login": Date,
"updated": { type: Date, default: Date.now }
})
}
...
The 'user.js' script above will not work because it doesn't have access to the mongoose object variable instantiated in the 'app.js' script. In PHP any included/required scripts would be able to access variables from the parent script, but in NodeJS as I know it for example I have to re-require/state the mongoose variable in order to create my schema tree.
user.js
...
* var mongoose = require( 'mongoose' ); // must include in script to use mongoose object
module.exports
{
...
}
...
Is there any work-around that will allow me the same scope access as PHP?
The answer is that there are workarounds, but you really don't want to use them, ever, ever, except for things which you want to hack into the global scope of all running modules in your application up to and including all dependencies (mongoose) and all of ITS dependencies.
override.js
global.thisIsNowAvailable = true;
flaky-file.js
if (thisIsNowAvailable) { /* ... */ }
index.js
require("./override");
require("./flaky-file");
The same will work for overriding methods on global prototypes, et cetera.
Unless your library is super-awesome and is intended to intercept, parse and interpret code at require-time
require("babel/register"); // all loaded modules can now be written in ES6
doing this for other reasons leads to horrible code-bases...
broken-index.js
require("flaky-file");
require("override");
// you might have just attempted to reference a variable that doesn't exist,
// thrown an error and crashed your entire server
// (not just a single connection, like PHP... ...the entire server went down,
// for everyone, and it has to be restarted).
Think of modules as separate function scopes.
It's really simple to do something like:
needs-mongoose.js
function doSomeInitWithMongoose (db) { /* ... */ }
function doSomeRuntimeWithMongoose (db, params) { /* ... */ }
module.exports = mongoose => {
doSomeInitWithMongoose(mongoose);
return {
run: params => {
/* ... app is run here ... */
doSomeRuntimeWithMongoose(mongoose, params);
}
};
};
configures-mongoose.js
var mongoose = require("mongoose");
function configure (db, cfg) { /* ... */ return db; }
module.exports = config => {
var configuredDB = configure(mongoose, config);
return configuredDB;
};
main.js
// to support arrow functions and other awesome ES6, including ES6 modules
require("babel/register");
var config = require("./mongoose-config");
var db = require("./configures-mongoose")(config);
var app = require("./needs-mongoose")(db);
app.run({ /* ... */ });
EDIT
Updated the last few files to be a structurally-correct pseudo-program (which does absolutely nothing, of course);
Of course, if index.js or server.js were to require("babel/register"); and then load main.js (without the Babel include in it), all of the require statements south of Babel could be written as ES6 modules, without issue.
server.js
require("babel/register");
require("./es6-main");
es6-main.js
import config from "./mongoose-config";
import configureDB from "./configures-mongoose";
import loadApp from "./needs-mongoose";
const db = configureDB(config);
const app = loadApp(db);
app.run({ /* ... */ });
Note that now I'm naming the functions I was originally returning, because in JS when you return a function, you can immediately call it...
getFunc( config )( data );
...but you can't act immediately on import statements.
Rule of thumb is that if you're going to export an object to the outside world, it should have 0 external dependencies, or all external dependencies will be set up later, by setters of some kind:
var utils = require("./utils"); // doesn't need any information
utils.helperFunc(data);
or
var catsAndPorn = true;
var internets = [];
var SeriesOfTubes = require("series-of-tubes");
var internet = new SeriesOfTubes( catsAndPorn );
internets.push( internet );
or
var bigOlFramework = require("big-ol-framework");
bigOlFramework.setDBPool( myDBCluster );
http.createServer( bigOlFramework.connectionHandler ).listen( 8080 );
None require outside information for their actual init (though may require their own internal dependencies).
If you want to return something which does rely on external init, either export a factory/constructor, or export a function, which accepts your config/data, and then returns what you want, after an init sequence.
EDIT 2
The last piece of advice here is that as far as mongoose usage goes, or Gulp, to a similar extent, or several routers...
...when you want to have a single file which registers its contents to a registry, or requires a core-component, to be able to return something, the pattern in Node which makes the most sense is to return a function which then does the init
var Router = require("router");
var router = new Router( );
require("./routes/login")(router);
require("./routes/usesrs")(router);
require("./routes/articles")(router);
Where "./routes/articles.js" might look like
import ArticlesController from "./../controller/articles"; // or wherever
var articles = new ArticlesController();
module.exports = router => {
router.get("/articles", ( ) => articles.getAll( ));
router.post("/articles", ( ) => articles.create( ));
};
So if you were looking to structure ORM based on schema, you might do similar:
var mongoose = require("mongoose");
var Users = require("./schema/users")(mongoose);
where "./schema/users" looks like:
module.exports = mongoose => {
return new mongoose.Schema({ /* ... */ });
};
Hope that helps.
Why don't you just do this?
var mongoose = require( 'mongoose' );
...
"Schema" : new mongoose.Schema({
Instead of:
exports.mongoose.Schema // I'm not sure where you got `exports.mongoose` from.
Also you don't have to use the .js when requiring like:
var templates = require( './data/inc' );
Edit
I believe you can't do it like PHP. Also the requires are cached so no need to worry about re requiring.
I'd like to add a default toDisplay function to all models which will use metadata, not unlike attribute/association definitions, to perform manipulations on the instance's attributes/associations making them suitable for display in the UI.
for example:
Foo.findOne(someId)
.exec(function(err, foo) {
...
res.view({
foo: foo.toDisplay(),
});
});
So, I'd like to add this function too all models. I can imagine a
Model.prototype.toDisplay = ...
solution, but I'm not sure where to get Model from (some long require('waterline/..../model') path?), and if I had Model, where to put that snip-it.
Please advise.
Model configuration is fully documented here on SailsJS.org. #umassthrower is correct in pointing out that adding an instance method to config/models.js would add it to all of your models; he's also correct in observing that this is not the intended use of the config file.
The reason you're finding this a bit more challenging in Sails than Rails is that Ruby has real classes and inheritance, and Javascript just has objects. One fairly clean way to simulate inheritance and extend your model objects from a "base" object would be to use something like Lodash's _.merge function. For example you could save your base model in lib/BaseModel.js:
// lib/BaseModel.js
module.exports = {
attributes: {
someAttribute: 'string',
someInstanceFunction: function() {
// do some amazing (synchronous) calculation here
}
}
};
Then in your model file, require lodash and use _.extend:
// api/models/MyModel.js
var _ = require('lodash');
var BaseModel = require("../../lib/BaseModel.js");
module.exports = _.merge({}, BaseModel, {
attributes: {
someOtherAttribute: 'integer'
}
};
The attributes from your base model will be merged with MyModel, with MyModel taking precedence.
Setting the first argument to the empty model {} is important here; _.merge is destructive for the first object sent in, so if you just did _.merge(BaseModel, {...} then the base model would be modified.
Also, remember to npm install lodash!
In Sails 0.x, when the moduleloader was loaded, you could access to sails.models directly, but now in 1.x this is not ready yet, so, my solution to this was creating a custom hook that wraps the loadModels function of sails.modules, this may not be the best solution but works for me #adam-pietrasiak hope this works for you too :) I am also super lazy when it comes to repeating code.
// provide this code in api/hooks/overrides.js or use another name, who cares
const _ = require('lodash');
module.exports = function (sails) {
return {
defaults: {},
savedModelLoad: null,
configure: function () {
this.savedModelLoad = this.savedModelLoad || sails.modules.loadModels;
sails.modules.loadModels = this.loadModelsAndApplyOverrides;
},
loadModelsAndApplyOverrides: function(cb){
this.savedModelLoad(function (err, models) {
const newModels = _.map(models, applyModelOverrides);
cb(err, newModels);
});
}
};
};
function applyModelOverrides(model) {
return _.merge(model, {
// do your custom stuff here
attributes: {
someAttribute: 'string',
someInstanceFunction: function() {
// do some amazing (synchronous) calculation here
}
}
});
}
How come in sails you cannot access other controller methods from within another one?
like this.
module.exports =
findStore: ->
# do somthing
index: ->
#findStore(); # Error: undefined
Compiled
module.exports = {
findStore: function() {},
index: function() {
return this.findStore(); // Error: undefined
}
};
If you can't do this, then why not? how else should I be doing this...
You can use sails.controllers.yourControllerName.findStore()
the sails global object has references to almost everything.
One of the best ways to organize your code in Sails, at least for me and my team, has been to have all the real business logic in Services (/api/services). Those objects can be accessed globally from any controller.
Also, a good practice is working with promises in services (as Sails use them on the model methods)
Just create a Store service (StoreService.js), with your code:
module.exports = {
findStore: function(storeId) {
// here you call your models, add object security validation, etc...
return Store.findOne(storeId);
}
};
Your Controllers should handle all that is related to requests, calling services, and returning apropriate responses.
For example, in you example, the controller could have this:
module.exports = {
index: function(req, res) {
if(req.param('id')) {
StoreService.findStore(req.param('id'))
.then(res.ok)
.catch(res.serverError);
} else {
res.badRequest('Missing Store id');
}
},
findStore: function(req, res) {
if(req.param('id')) {
StoreService.findStore(req.param('id'))
.then(res.ok)
.catch(res.serverError);
} else {
res.badRequest('Missing Store id');
}
},
};
That way, you have really simple controllers, and all business logic is managed by services.
Having the same problem for last few hours. I used the api/services folder.
It may not be exactly what you need but it is an option.
A good explanation is here. What services would one add to the api/services folder in sails.js
It's slightly annoying when you're just trying to build something quickly, but in the long run it forces good code organization practice (by making it harder to shove all business logic into a controller).
I would like to suggest a solution that works but not the best possible way to do it. We can use bind function to bind the context with the calling source as shown below :
generateUrl is present in the Controller A
function generateUrl(){
return 'www.google.com';
}
get URL is another method in Controller A
getURL(){
A.generateURL.bind(A.generateURL) //func call with optional arg
}
I hope this helps!
A more elegant way to solve this problem is using the keyword this before the function name.
Example:
one: function() {
console.log('First Function');
},
two: function() {
// call the function one in the same controller
this.one();
}
You can do something like this:
//ArticleController
module.exports = {
findStore: async () => {
return await findStoreFunc(req.param('id'));
},
index: async () => {
...
return await findStoreFunc(id);
}
};
const findStoreFunc = async (id) => {...}
And to use the function from another controller:
const ArticleController = require('./ArticleController');
//CustomerController
module.exports = {
index: async () => {
...
let article = await ArticleController.findStore(id);
...
}
};