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);
...
}
};
Related
I have some problem here, I'm using a mix of Angular2 and Nativescript's native JS to use HTTP because I was having problems with Angular2 HTTP before this implementation, but I'm having frustration on how to put the JSON response from the http request to the monthList variable so it can be read all over the place. Please help, Thank you.
Here's my code:
public monthList = [];
constructor(location: Location, private page: Page, private router: Router, private testing: any) {
this.router = router;
this.testing = testing;
let http = require("http");
http.getJSON("link")
.then(function (r) {
// Argument (r) is JSON!
}, function (e) {
// Argument (e) is Error!
console.log(e);
});
}
Inside your .then you set your return object to your monthList
this.monthList = r;
Try doing a console.dump(r) and see what is being returned. You may have to do something like r.xxx or however it is structured.
Then you can probably do something like
this.monthList.month
and access the data of the month and so forth. Or you maybe have to do something like
this.monthList[0].month.
For example in my project I have a JSON object coming back that is nested and I need to set it as something like r.value.data.
Is there a specific issue you're running into? I would think its as simple as:
class SomeAngularClass {
constructor() {
http.getJSON("link")
.then(function(r) {
this.monthList.push(r);
}, function (e) {
console.log(e);
});
}
}
And then you could access it via:
SomeAngularClass().monthList;
I know how to use stub to replace one function.
sandbox.stub(Cars, "findOne",
() => {return car1 });
But now I have a line in my function I want to test that I need to stub that looks like this
Cars.find().fetch()
So there is a chain of function here and I'm unsure what I need to do. How do I stub "find" to return something that I can use to stub "fetch"?
IMHO, we can just use returns to do this. We don't need to use callsFake or mock it as function.
// Cars.find().fetch()
sinon.stub(Cars, 'find').returns({
fetch: sinon.stub().returns(anything)
});
in case, if there is another method after fetch(), we can use returnsThis()
// Cars.find().fetch().where()
sinon.stub(Cars, 'find').returns({
fetch: sinon.stub().returnsThis(),
where: sinon.stub().returns(anything)
});
Ref:
https://sinonjs.org/releases/v6.3.3/
Hope it helps
Try this:
sandbox.stub(Cars, "find", () => {
return {
fetch: sinon.stub().returns(anything);
};
});
The form of attaching a function to a stub shown here:
sandbox.stub(Cars, "find", () => {
return {
fetch: sinon.stub().returns(anything);
};
});
is deprecated.
It's now, as of version 6.3
sandbox.stub(Cars, "find").callsFake(() => {
return {
fetch: sinon.stub().returns(anything);
};
});
This is another approach that also allows spying on chains of jQuery methods - which took me a long time to figure out.
In the example, I am trying to test that an email field is cleared out
//set up stub and spy
const valSpy = sandbox.spy();
const jQueryStub = sandbox
.stub($.prototype, "find") // this prototype is important
.withArgs("input[name=email]")
.returns({ val: valSpy });
// call function under test
learnerAlreadyAccepted(inviteDoc);
// check expectations
expect(jQueryStub).to.have.been.called; // not really necessary
expect(valSpy).to.have.been.calledWith("");
and the function under test is (roughly):
learnerAlreadyAccepted = function(doc) {
$("form").find("input[name=email]").val("");
}
I ran into this problem and, though I liked the solution for a single test, wanted something more dynamic that would allow for reuse across tests. I also preferred the sandbox approach, as it made restoring much easier for larger suites. End result:
export function setupChainedMethodStub(sandbox: sinon.SinonSandbox, obj: any, methodName: string, methodChain: string[], value: any) {
return sandbox.stub(obj, methodName).returns(generateReturns(sandbox, methodChain, value));
}
function generateReturns(sandbox: sinon.SinonSandbox, methodChain: string[], value: any): any {
if (methodChain.length === 1) {
return {
[methodChain[0]]: sandbox.stub().returns(value),
};
} else {
return {
[methodChain[0]]: sandbox.stub().returns(generateReturns(sandbox, methodChain.slice(1), value)),
};
}
}
Wherever I want to set up a stub on the fly, I pass in the created sandbox and the other parameters:
setupChainedMethodStub(sandbox, MyMongooseModel, 'findOne', ['sort', 'exec'], { foo: 'bar' })
Then I just have a sandbox.restore() in my highest scoped afterEach()
There are a few changes from v2.0.
More details here
One of them is:
stub(obj, 'meth', fn) has been removed, see documentation
You can downgrade but I would not recommend it, instead you can do something like this:
let stub = sinon.stub(obj, "meth").callsFake(() => {
return {
meth2: sinon.stub().callsFake(() => {
return {
meth3: sinon.stub().returns(yourFixture),
};
}),
};
});
I have a simple solution that hopefully works for others.
Presuming that fetch is also a method on Cars, and fetch and find support method chaining, Cars may look something like this:
class Cars {
fetch() {
// do stuff
return this;
}
find() {
// do stuff
return this;
}
}
[ANSWER] We should be able to support method chaining with the stub like this:
sandbox.stub(Cars, 'fetch').callsFake(function () { return this; }); // optional
sandbox.stub(Cars, 'findOne').callsFake(function () { return this; });
I'm running into an issue with an Ember CLI project where I can't get an injected ember service from a controller action function.
The really strange thing is that this totally works on my models and custom adapters:
the controller:
export default Ember.Controller.extend({
node: Ember.inject.service(),
azureStorage: Ember.computed.alias('node.azureStorage'),
actions: {
myAction: function () {
// this returns null
var x = this.get('azureStorage');
}
}
});
// The service code (azureStorage and fs are NOT null)
if (window.requireNode) {
azureStorage = window.requireNode('azure-storage');
fs = window.requireNode('fs');
}
export default Ember.Service.extend({
azureStorage: azureStorage,
fs: fs,
getActiveAccount: function (store) {
return new Ember.RSVP.Promise(function (resolve, reject) {
var accounts = store.all('account'),
length = accounts.get('length'),
i = 0;
accounts.forEach(function (account) {
if (account.get('active') === true) {
return Ember.run(null, resolve, account);
}
i += 1;
if (i >= length) {
return Ember.run(null, reject, 'could not find any active accounts');
}
});
});
}
});
// the controller test code
var controller = this.subject();
controller.send('myAction');
I would have expected this to return the service and the azureStorage object. On my models & adapters the same pattern works just fine:
export default DS.Adapter.extend({
serializer: serializer.create(),
node: Ember.inject.service(),
azureStorage: Ember.computed.alias('node.azureStorage'),
findQuery: function () {
// this returns the value correctly
var x = this.get('azureStorage');
}
});
Any reason this would work on models & adapters but NOT on a controller?
I'm not familiar with the Ember.inject.service() pattern, but is there a reason you're not using the pattern outlined in http://guides.emberjs.com/v1.10.0/understanding-ember/dependency-injection-and-service-lookup/ ?
Also, why are you injecting node and azureStorage into the controller if you've already abstracted them into an adapter? You should just be using this.store.find('whatever', 123) from the controller to get your data. If your azureStore is different than your normal Ember Data store, then you should create a new store and register it with the application's container. If you inject it in your controller, you can access it with this.azureStore, or alternatively this.container.lookup('store:azure').
Also, not a good practice to start injecting stuff into your models. I would really take a look at the Ember-friendly ways of doing service/dependency injection, because this doesn't look very elegant and you're duplicating a lot of code to get access to something you already have.
I have an Angular factory, Session.
It exposes a method join that does some authentication stuff then off the back of that exposes an arbitrary resource through the same factory (code snippet below should illustrate).
Is there a best practice here in terms of exposing that property?
Solutions I see:
Exposing getter methods
Using a service rather than a factory and using this
Doing something like this from a factory:
// Public API
var exports = {
join: function(params, callback) {
authenticate(params, function(err, data) {
if (!err) {
exports.arbitraryResource = createArbitraryResource(data.resource);
}
callback(err, data.sessionKey);
});
}
};
return exports;
Really interested to hear how other developers are exposing mutable properties from factories. I'd like to avoid getter methods if only because they're clunky to write and it seems like an anti pattern here.
I would write a method getResource() which would make the authenticate call if resource was null else return the resource that had been setup by the previous call. This way the calling code does not care as to how the resource was fetched.
app.factory('myService', function() {
var resource = null;
return {
getResource: function(callback) {
if (resource) {
callback(null, resource);
} else {
authenticate(params, function(err, data) {
resource = data
callback(err, data);
});
}
}
}
})
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
}
}
});
}