I'm building a node.js app and I'm evaluating Sequelize.js for persistent objects. One thing I need to do is publish new values when objects are modified. The most sensible place to do this would seem to be using the afterUpdate hook.
It almost works perfectly, but when I save an object the hook is passed ALL the values of the saved object. Normally this is desirable, but to keep the publish/subscribe chatter down, I would rather not republish fields that weren't saved.
So for instance, running the following
tasks[0].updateAttributes({assignee: 10}, ['assignee']);
Would automagically publish the new value for the assignee for that task on the appropriate channel, but not republish any of the other fields, which didn't change.
The closest I've come is with an afterUpdate hook:
Task.hook('afterUpdate', function(task, fn) {
Object.keys(task).forEach(function publishValue(key) {
pubSub.publish('Task:'+task.id+'#'+key, task[key]);
});
return fn();
});
which is pretty straightforward, but since the 'task' object has all the fields, I'm being unnecessarily noisy. (The pubSub system is ignorant of previous values and I'd like to keep it that way.)
I could override the setters in the task object (and all my other objects), but I would prefer not to publish until the object is saved. The object to be saved doesn't seem to have the old values (that I can find), so I can't base my publish on that.
So far the best answer I've come up with from a design standpoint is to tweak one line of dao.js to add the saved values to the returned object, and use that in the hook:
self.__factory.runHooks('after' + hook, _.extend({}, result.values, {savedVals: args[2]} ), function(err, newValues) {
Task.hook('afterUpdate', function(task, fn) {
Object.keys(task.savedVals).forEach(function publishValue(key) {
pubSub.publish('Task:'+task.id+'#'+key, task[key]);
});
return fn();
});
Obviously changing the Sequelize library is not ideal from a maintenance standpoint.
So my question is twofold: is there a better way to get the needed information to my hook without modifying dao.js, or is there a better way to attack my fundamental requirement?
Thanks in advance!
There is not currently. In the implementation for exactly what you describe we simply had to implement logic to compare old and new values, and if they differed, assume that they have changed.
Related
I'm currently facing a problem with Meteor and React, where i know some partly solutions but they don't work and imo none of them is pointing in the true direction.
The situation:
All is about an fitness app: I have a structure that represents exercises for customers, while each exercise can have a defined number of sets (a set is how often a exercise should be done). Each set has some properties (all the user can manipulate within the font-end).
Now i have the following component structure with some map-functions (state properties are in {}):
Training {customers,exercises,datetime,otherinfos}
- Overview {customers,exercises}
exercises.map():
- Exercise {exercise,customers}
customers.map():
- Customer {exercise,customer}
exercise.sets.map()
Set {exercise, customer, set, valuesofset}
From a UI-perspective (react) this all works without problems.
Now the idea is to have a button "Save" within the Training component. When the button is pressed, I want to save the state of all Set-Components in a "sets" collection (if it has other values than the default placeholder ones) and at the same time save the Training-Component in a "trainings" collection. But the training should also include information about what Sets are integrated (so at least the Set._id should be in the Training-Component state at time of Saving.
Here now my ideas so far:
Create refs from Training all the way down to all Sets and then, when pressing "Save" iterate over all refs and call a "Mongo.insert" from all Sets. Here i have the problem that i cannot return the inserted _id. Of course i could call a different function in each Component from Set all the way back to Training, but imo this is an overflow.
Try to manage the state of all sets within the Training state by calling a nested function. As i have onChangeHandler on the Inputs, this would always call a method in Training and check which one of the Sets was changed and then changes it. I have tried it this way, but it led to a very bad performance.
Create a temp-ID for Training, forward it to to the Sets (using the componentWillReceiveProps method) and when in Set, insert the Set in the database with the temp-ID. Then receive all Sets with temp-ID and use it to add the Training in the database. --> imo very complicated and I don't really want to do a database call if it is not necessary.
So currently i don't know how to solve this problem. The reason i try to separate "sets" and "trainings" is given through the fact, that later on i would like to give information about the last Set right next to the new empty Set whenever one is on the database. Any tips are welcome!
EDIT:
As suggested, there is also the possibility to solve the problem with Session. So therefor i have added the following code to Set:
componentDidMount() {
Tracker.autorun(() => {
Session.set(`set_${this.state.id}`, {
...this.state
});
});
}
My idea was then to iterate over all Session-Keys from Training which start with "set_" - unfortunately there is no function to that holds all Keys.
Second idea was to to use an array as value for a Session-pair. However, it's quite a procedure to handle the update of the reactive Set component (copy array from session, check whether an element is available or not, create a new one or update the existing one).
EDIT2:
I think i got a solution with Session:
Object.getOwnPropertyNames(Session.keys)
did the trick to get all SessionKeys! Thank you for your help!
If you do not want to use Redux or pass parent bound callbacks in the child component, you can try Session to store data at app level which can be accessed(set/get) in any component
https://docs.meteor.com/api/session.html
In your case, you may set values of "Set" in Session and access it in Training. You may also need https://guide.meteor.com/react.html#using-withTracker. Using withTracker will help in doing reactive update of the database on change of any Session variable.
I have a small, home-brewed implementation of the Flux pattern that I'm working with, just to get a better understanding of the pattern. Its working quite well, and I'm learning a ton! But I've run into an issue I can't wrap my head around at the moment. Apologies in advance if this has an obvious solution.
Imagine my simple Store called ExampleStore, and my simple Component ExampleComponent. In it there are:
_exampleState
getExampleState()
setExampleState()
in ExampleComponent, to stay updated:
_onChange: function() {
setState({exampleState: ExampleStore.getExampleState()})
}
in ExampleStore, after a dispatched action I use the setter:
setExampleState(newStateFromDispatchedAction);
This works perfectly. The data is flowing as it should. But I have a question, because it seems easy to break the pattern because there is no privacy enforced on my _exampleState within my Store. Since I have a getter and private setter method for _exampleState, it seems natural to me that somehow I want to enforce more privacy on the variable. Is there a nifty way to do this, that I am overlooking?
As it is now, if, for example, inside ExampleComponent I do (which I wouldn't, but I could):
this.state.exampleState = {field:'example'}
//doing this
this.state.exampleState.field = 'example2';
//changes the store _exampleState to {field:'example2'}
I have changed the value of _exampleState within ExampleStore directly, without making use of the setter. This seems dangerous (and makes me question why I'd have a private setter/public getter to begin with). This question comes after dealing with a pesky bug where a library I was using modified the state variable directly, and thereby within the Store.
Is there some good way I'm overlooking to enforce privacy on the state variables in my Store, so that they may not be changed directly through their references in ExampleComponent? Sorry if this is a dumb question and I'm overlooking something simple, thanks for the help!
Be aware that one of the basic principles of the Flux philosophy is that stores should have no (public) setters. That means you should not be able to modify the store's state if not inside the store itself.
One way of enforcing the privacy could be by keeping state variables as private, only letting the store managing them.
EDIT: to "enforce" privacy, you could also return a deep copy of your state, as it is shown in the code.
The following code, based on the official flux GitHub repository's flux-todomvc example, highlights the idea:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
// This is a private state variable that can only be accessed in this file
var _exampleState = {/*...*/};
var ExampleStore = assign({}, EventEmitter.prototype, {
EXAMPLE_STATE_CHANGED: 'EXAMPLE_STATE_CHANGED',
// return a deep copy of your state so there is no way
// to modify the store's state by reference
getExampleState: function() {
return deepCopy(_exampleState);
}
/*...*/
};
// this is a private method (setter)
var _setExampleState = function(newExampleState) {
_exampleState = newExampleState;
};
ExampleStore.dispatchToken = AppDispatcher.register(function(action) {
switch(action.actionType) {
case AppConstants.CHANGE_EXAMPLE_STATE:
_setExampleState(action.newExampleState);
ExampleStore.emit(ExampleStore.EXAMPLE_STATE_CHANGED);
break;
}
});
// the implementation of deepCopy is a developer's choice
// this version of it is very inefficient
var deepCopy = function(obj) {
return JSON.parse(JSON.stringify(obj));
}
module.exports = ExampleStore;
Facebook official examples are a good way to understand how to implement the core Flux concepts.
EDIT: this is a way of "enforcing" privacy of a state variable, but it is discouraged due to the clear loss of efficiency. I guess that the main idea here is that, even though you are able to do so in some situations, changing the store's state through reference is just against Flux. It is important to notice that this enforcement is not a reality in many big libraries. In React, for instance, it is possible to modify the state of a component directly, even though that is completely not recommended.
you can wrap your store in a closure, and provide getters and setters, to prevent accidental modification of your state.
https://facebook.github.io/immutable-js (or Mori or seamless-immutable) provides the means to prevent modifications to nested data, while avoiding the need to make defensive deep clones in your getExampleState method. However, it has a huge impact on your coding style and code base. It possibly works best with a functional coding style, as is encouraged by some flux implementations, like https://github.com/rackt/redux.
Another option is to make it clearer that you don't want the state to be modified by ensuring that non-library code only sees the store state in React 'props', rather than React 'state' - not modifying props should be second nature to a React developer anyway (other bugs will occur if they modify it). This can be done using a generic flux wrapper component, such as Facebook's 'Container' - https://facebook.github.io/flux/docs/flux-utils.html
Can't find any docs or posts for this, which may indicate I'm trying to do something incorrect.
Is it possible to use a Mongoose schema that is entirely virtual, i.e. not persisted to the db?
I have a number of models, most of which are persisted to db, but would like to consistently include models that are only retained in memory, not persisted?
The closest I can come up with is along these lines, but it will still persist objects with only an id attribute in the database. Simplified here:
// access_token.js
var schema = mongoose.Schema({});
schema.virtual('token').get(function() {
return 'abcde12345';
});
module.exports = mongoose.model('AccessToken', schema);
The idea in doing this is to abstract models so that the consuming part of the app does not need to be aware of whether a model is persisted to the database or only held in memory. Of course this could be achieved by creating the same object and methods as a plain object, but that approach would quickly become repetitive.
You could override (monkey patch) the Mongoose methods which save data (e.g. .save) but I suspect what you are trying to do is difficult/impossible.
You could take a look at sift.js, which is a query library to do in-memory querying.
https://github.com/crcn/sift.js
You can set a pre middleware for this model which always fails.
schema.pre('save', function (next) {
next(new Error('This can't be saved!');
});
So you will know when you are doing wrong.
I'm analyzing the basic todo application.
Why is it that when I delete the StatsView (from the main todos.js and from todos.handlebars) the remaining method (property) of the todoListController stops updating itself?
Todos.todoListController = SC.ArrayController.create({
...
remaining: function() {
console.log('remaining');//doesn't apear in the console
return this.filterProperty('isDone', false).get('length');
}.property('#each.isDone').cacheable(),
...
});
I can imagine, that this is because with the StatsView I deleted the binding. But shouldn't it be, that the #each keeps an eye on the changes?
SproutCore optimizes to do as little work as possible. So, when you deleted the StatsView, you deleted the thing that cares about the .remaining property. Since nothing is asking for it, SproutCore doesn't compute it. This is why you should always use the get() and set() methods when accessing properties so that they can decide whether to use the cached version or to actually compute the property.
I've run into a headache with Backbone. I have a collection of specified records, which have subrecords, for example: surgeons have scheduled procedures, procedures have equipment, some equipment has consumable needs (gasses, liquids, etc). If I have a Backbone collection surgeons, then each surgeon has a model-- but his procedures and equipment and consumables will all be plain ol' Javascript arrays and objects after being unpacked from JSON.
I suppose I could, in the SurgeonsCollection, use the parse() to make new ProcedureCollections, and in turn make new EquipmentCollections, but after a while this is turning into a hairball. To make it sensible server-side there's a single point of contact that takes one surgeon and his stuff as a POST-- so propagating the 'set' on a ConsumableModel automagically to trigger a 'save' down the hierarchy also makes the whole hierarchical approach fuzzy.
Has anyone else encountered a problem like this? How did you solve it?
This can be helpful in you case: https://github.com/PaulUithol/Backbone-relational
You specify the relations 1:1, 1:n, n:n and it will parse the JSON accordingly. It also create a global store to keep track of all records.
So, one way I solved this problem is by doing the following:
Have all models inherit from a custom BaseModel and put the following function in BaseModel:
convertToModel: function(dataType, modelType) {
if (this.get(dataType)) {
var map = { };
map[dataType] = new modelType(this.get(dataType));
this.set(map);
}
}
Override Backbone.sync and at first let the Model serialize as it normally would:
model.set(response, { silent: true });
Then check to see if the model has an onUpdate function:
if (model.onUpdate) {
model.onUpdate();
}
Then, whenever you have a model that you want to generate submodels and subcollections, implement onUpdate in the model with something like this:
onUpdate: function() {
this.convertToModel('nameOfAttribute1', SomeCustomModel1);
this.convertToModel('nameOfAttribute2', SomeCustomModel2);
}
I would separate out the different surgeons, procedures, equipment, etc. as different resources in your web service. If you only need to update the equipment for a particular procedure, you can update that one procedure.
Also, if you didn't always need all the information, I would also lazy-load data as needed, but send down fully-populated objects where needed to increase performance.