Is this a best practice to cache request data in Backbone.js? - javascript

I have a requestCache: {} object in my router declaration. I have a reviews method mapped to a route with the same name (#reviews). I want to cache the results generated inside this method.
router.js
var AppRouter = Backbone.Router.extend({
currentView: null,
requestCache: {},
reviews: function() {
var self = this;
var reviewCollection = new ReviewCollection();
reviewCollection.url = '/profile/' + this.userid + '/reviews';
if('reviews' in self.requestCache) {
reviewCollection = self.requestCache['reviews'];
self.changeView(new ReviewsView({collection:reviewCollection}), 'reviews');
} else {
reviewCollection.fetch().done(function() {
self.requestCache['reviews'] = reviewCollection;
self.changeView(new ReviewsView({collection:reviewCollection}), 'reviews');
});
}
},
changeView just renders the view using the results.
This works fine. What I want to know is whether this is a good way of caching data?

Take a look at backbone-fetch-cache. It does what you want.

As suggested by SoundCloud team, they've craeted a store object to share models and collections through the code.
I've been using Backbone SingletonModel (https://github.com/reconbot/backbone-singleton)
It works just fine and you can make the same for your collections, defining a getInstance method and a _instance on its static part.
var MyCollection = Backbone.Collection.extend({}, {
_instance: null,
count: 0,
getInstance: function () {
if (!this._instance)
this._instance = new MyCollection();
this.count++;
return this._instance;
}
});

Related

Knockout: Change observable value

I use a requirejs/crossroads setup.
This is my bootstrapping with some global properties:
ko.applyBindings({
route: router.currentRoute,
user: {
...
},
...
loading: ko.observable(false),
setLoadingState: function(newState) {
this.loading(newState);
}
});
When calling the setLoadingState function from components (passed via params), it tells me that loading is not a function/undefined.
What's the correct way of implementing such mechanisms?
Note that in your (simplified?) example, you don't need an additional method since it only forwards to loading, which can be called directly.
Either use a class like pattern to make sure this refers to your view model like so:
var MyApp = function(router) {
this.route = router.currentRoute,
this.loading = ko.observable(false);
};
MyApp.prototype.setLoadingState = function(newState) {
this.loading(newState);
};
ko.applyBindings(new MyApp(router));
(you can also use the more modern class syntax)
or, use plain objects via a "factory" function:
var MyApp = function(router) {
var route = router.currentRoute,
var loading = ko.observable(false);
var setLoadingState = function(newState) {
loading(newState);
};
// Expose what you want to expose:
return {
loading: loading,
setLoadingState: setLoadingState
};
};
ko.applyBindings(MyApp(router));

store.push not reflecting on template with ember-cli-pagination

I'm new to Ember.js and I'm trying to add an object to the store after an ajax request.
The problem is that it does not reflect on template if I use ember-cli-pagination.
If I use this.store.findAll in model, it works, but when I use this.findPaged it does not.
I'm using ember-inspector and the object appears in the store, just don't in the browser.
My code:
import Ember from 'ember';
import RouteMixin from 'ember-cli-pagination/remote/route-mixin';
export default Ember.Route.extend(RouteMixin, {
actions: {
create: function(email) {
let adapter = Ember.getOwner(this).lookup('adapter:application');
let url = adapter.buildURL('billing/delivery-files');
let store = this.get('store');
let modelName = 'billing/delivery-file';
return adapter.ajax(url, 'POST', {
email: email
}).then(function(data) {
var normalized = store.normalize(modelName, data.object);
store.push(normalized);
});
}
},
model(params) {
return this.findPaged('billing/delivery-file',params); //does not work
// return this.store.findAll('billing/delivery-file'); //works
}
});
Tried the solutions from this issue, and did not work at all.
What am I missing?
Figured out!
After pushing to the store, I needed to push to the paginated array a reference to the store object. Don't know exactly why but it worked like a charm!
model.content.pushObject(pushedRecord._internalModel);
In my case I wanted it at the first position of the array, so I did:
model.content.insertAt(0, pushedRecord._internalModel);

Is there a more elegant way to "fake" class inheritance?

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.

Set a Backbone collection model with circular dependencies in requirejs

The thing is that I have a circular dependecy between some Backbone modules so I have to use "exports" as Requirejs scpecifies in its documentation http://requirejs.org/docs/api.html#circular. So the model 'A' will look like this:
define(function(require, exports) {
var B = require('B');
var A = Backbone.Model.extend({
});
exports.model = A;
});
And the collection 'B' like this:
define(function(require, exports) {
var A = require('A');
var B = Backbone.Model.extend({
model: A.model
});
exports.model = B;
});
The problem here is that by the time I have to specify the collection 'B' model property, the model 'A' isn't yet defined. This is the error I'm getting when I try to set the collection with models like this:
B.collection.set([{id: 1}, {id: 2}]);
Uncaught TypeError: 'undefined' is not an object (evaluating 'targetModel.prototype') (http://127.0.0.1:9999/bower_components/backbone/backbone.js:689)
Any ideas on how should I solve this problem?
From the example, it's not clear that B actually depends on A. If it's just a model:collection relationship, it might make sense to remove the dependency of the model on its collection. If it's at all possible to break the circular dependency, I would strongly encourage you to do so.
If the back-reference is truly required, though, one option might be to move the resources into the same module and do a sort of lazy export:
define(function() {
var lazyThings = {
A: null,
B: null
};
lazyThings.A = Backbone.Model.extend({
collection: things.B
});
lazyThings.B = Backbone.Collection.extend({
model: A
});
return lazyThings;
});
Alternatively, you could return lazyThings.B and later access the model from its prototype:
require('b', function (B) {
var A = B.prototype.model; // A
});
Finally, requirejs could be made to work by calling the respective dependencies lazily (i.e., after the modules are resolved):
// B
define(['a'], function (A) {
return function () {
return Backbone.Collection.extend({
model: A()
});
}
});
// A
define(['b'], function (B) {
return function () {
return Backbone.Model.extend({
model: B()
});
}
});
The following works for me, try to make it clear as possible.
You have a model, you have a collection. In order for them to both depend on each other + avoid a circular dependency, you need a 3rd "mediator" dependency. It's convenient in Backbone to have a model and easily lookup what collection it belongs to, and vice versa, but the problem of course is they have a circular dependency.
So before we had:
+model
+collection
__________
= circular
and after:
+model
+collection
+mediator
________
= OK
//collection
define([
'#allModels',
'#BaseCollection',
'#AppDispatcher',
'#allFluxConstants',
'app/js/flux/flux-helpers/collectionUpdater'
],
function (allModels, BaseCollection, AppDispatcher, allFluxConstants, collUpdater) {
var dispatchCallback = function (payload) {
return true;
};
var BaymaxComponentCollection = BaseCollection.extend({
model: allModels['BaymaxComponent'],
collectionName:'baymax-component',
url: '/baymax_component',
batchURL: '/batch/baymax_component',
initialize: function (models, opts) {
this.dispatchToken = AppDispatcher.register(dispatchCallback);
},
// collection is sorted by original insertion order.
comparator: 'order'
});
return new BaymaxComponentCollection();
});
//model
define([
'#BaseModel',
'#ModelCollectionMediator',
'#AppDispatcher'
],
function ( BaseModel, MCM) {
var BaymaxComponent = BaseModel.extend({
idAttribute: 'id',
urlRoot: '/baymax_component',
collectionName: 'baymax-component',
defaults: function () { //prevents copying default attributes to all instances of UserModel
return {}
},
initialize: function (attributes, opts) {
//*** the following line is crucial ***
this.collection = MCM.findCollectionByName(this.collectionName);
},
validate: function (attr) {
return undefined;
}
},
{ //class properties
});
return BaymaxComponent;
});
//mediator
define(function (require) {
return {
findCollectionByName: function (name) {
var allCollections = require('#allCollections');
return allCollections[name];
}
};
});

Sails.js Access Model on Service initialization

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.

Categories

Resources