BackboneJS: collection.pluck() in model validation - javascript

I want my model to validate for duplicate entry, so I need to access the collection while in the model.
I'm using Backbone.JS together with Require.JS and this makes it a little more confusing.
I can't seem to load my collection as a dependency for the module.
I tried doing validation in the view, but I guess the best practice would be to keep validation in the model where it should be.
Any advice?

From Backbone Collection docs:
If you're adding models to the collection that are already in the collection, they'll be ignored, unless you pass {merge: true} ...
Which in the add -method translates to this:
if (existing = this.get(model)) {
if (options.merge) {
existing.set(attrs === model ? model.attributes : attrs, options);
if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
}
continue;
}
Basically that will trump adding any model with duplicate id, cid or idAttribute.
If that isn't enough, then I suggest you do the validation in the view, because bringing a model's collection as a dependency to that exact model would introduce a circular dependency, eg. you need the collection to be able to compile the model, but you also need the model to compile the collection.
So if you need to make sure you're not adding a model to a collection with exactly the same attributes as a model already in that collection, do it in the view. It would look something like this:
if (collection.where(modelToBeAdded.toJSON()).length == 0) {
collection.add(modelToBeAdded);
}

Related

javascript mongoose dynamic model assignment

Ive looked related posts and couldn't quite find what I was looking for.
So I am build a backend rest api and I have certain tests I am collecting data on. The tests have their own models, and these models are associated with collections (obviously).
So I have a separate controller for each model. Now I have a "job" controller which queries data from each separate test. Now I have a separate script where I store these model objects in an JSON object. I am wondering how I can access these models properly (I am close but cant quite assign properly). Here is the block:
const testMappings = {
'aprobe':aprobe,
'status':status,
//'rxserial':rxserial,
}
Now when I try assignment as follows, where testMappings is the imported script variable:
const testMappings = activeTests.testMappings;
console.log(testMappings['aprobe']);
I get the following output:
Model {aprobe}
I would like to access the actual aprobe object. Also if anyone knows a better way of dynamically assigning these (instead of having bunch of if statements ie if(name == 'aprobe').... do something), it would be much appreciated.
You are probably looking for something like below :
const name = 'aprobe';
Object.keys(testMappings).indexOf(name) > -1 ? testMappings[name] : null
the above should give you: Model {aprobe}
So basically if the key exists in your object then you'd like to fetch the value of that key which would give you your model dynamically.

Backbone ignore posting specific attributes on save? [duplicate]

I'm using Backbone with Rails. I have a model that I can create and destroy just fine. When I edit, though, I get this error:
Can't mass-assign protected attributes: created_at, id, updated_at
That makes sense. Those attributes are protected and they should be protected. Backbone shouldn't be trying to update these attributes, but Backbone doesn't know better.
One option, of course, would be to remove params[:created_at], etc. in my Rails controller, but I can imagine that getting really un-DRY pretty quick, and plus it just seems wrong to have to do that.
Is there a way for me to tell Backbone not to include these attributes in its forms?
Either don't send them to the client so that your Backbone model never knows about them or override toJSON in your model to exclude them.
The default toJSON implementation is very simple:
toJSON: function() {
return _.clone(this.attributes);
}
so you can replace it with this:
toJSON: function() {
var attrs = _(this.attributes).clone();
delete attrs.created_at;
delete attrs.updated_at;
return attrs;
}
You could even monkey patch that right into Backbone.Model.prototype if that made sense to you.
The downside of altering toJSON is that toJSON tends to do double duty in Backbone:
toJSON is used to serialize models and collections for the server.
toJSON is used to serialize models and collections for views.
If you still want to use updated_at and created_at in views then I'd recommend adding another method, say serialize_for_view, that does what the standard toJSON does:
serialize_for_view: function() {
return _(this.attributes).clone();
}
and then use things like var html = this.template({m: this.model.serialize_for_view()}) to build your view's HTML. You could also monkey patch serialize_for_view into Backbone.Model.prototype if you wanted to use it everywhere.
I found that putting
model.unset("created_at");
model.unset("updated_at");
model.save()
fixed the problem. This won't work if you need those attributes, but if they are not needed, this works.

Backbone.js What is the purpose of specifying a model in collection

Here is what I am trying to understand.
Often times I find myself writing backbone like this:
var CallModel = Backbone.Model.extend({
});
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
It is a very basic example but as you can see, there is nothing really in the model all the data is coming into the Collection via an external url call to a json file that is build from a database.
So whats the purpose of the model? I am sure that I am probably not using backbone.js to its fullest extent which is why I am here asking you guys.
First of all, "there is nothing really in the model all the data is coming into the Collection via an external url call" - this is not true.
Let's assume you've the following:
//Model
var CallModel = Backbone.Model.extend({
defaults: {
cost:0,
duration:0
}
});
(without custom attributes or methods, there is no point in extending the original Backbone.Model)
//Collection
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
And the json data returned from service, probably something like:
//Response
{
callSummary: {
missed: 2,
received: 3,
totalCalls:5
totalDuration: 20
}
calls: [{
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
},{
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
},{
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}],
//and more additional information from your db
}
Now you populate your collection with data by calling it's fetch method:
CallsCollection.fetch();
Your collection should look something like:
{
models: [{
attributes: {
callSummary: {},
calls: [{},{},{}],
...
},
...
}],
length:1,
url: "url/to/external/json",
...
}
The data will be added to a model's attribute hash. If you don't specify a particular model, as Bart mentioned in his answer, backbone will populate the collection with a Backbone.Model instance: Which is still not much useful - Wew... A collection with single model having entire response data inside it's attributes as it is...
At this point, you're wondering why did I even bother creating a model, and then a collection..?
The problem here is Collections are derived from Arrays, while Models are derived from Objects. In this case, our root data structure is an Object (not an Array), so our collection tried to parse the returned data directly into a single model.
What we really want is for our collection to populate its models from the "calls" property of the service response. To address this, we simply add a parse method onto our collection:
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json',
parse: function(response){
/*save the rest of data to corresponding attributes here*/
return response.calls; // this will be used to populate models array
}
});
Now your collection will be something like the following:
{
models: [{
...
attributes: {
...
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
}
},{
...
attributes: {
...
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
}
},{
...
attributes: {
...
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}
}],
length:3,
url: "url/to/external/json",
...
}
This - is what we want! : Now it is very easy to handle the data: You can make use of the add, remove, find, reset and handful of other collection methods effectively.
You can pass this models array into your templating library of choice, probably with two way bindings: When the respective view for one of the call model changes, the particular model will be updated, events will propagate from your models to the collection, and the particular model will be passed into the handler functions.
You can now call fetch, save, destroy, clear and a lot of other methods with ease on single unit's of data (each model), rather than hurdle with the entire data saved in a single model - which is pretty much useless, you've to iterate through the response data manually and perform CRUD and similar operations by your own, and in most cases: re-render the entire collection view. which is very, very bad and totally unmaintainable.
To conclude: If your data source doesn't return an array of objects, or you don't parse the response and return an array of objects from which n number of models are to be populated - Then defining a collection is pretty much useless.
Hopefully, now you get the idea.
Very helpful source of info:
Backbone, The Primer: Models and Collections
Developing Backbone.js Applications
backbonejs.org
You don't need to specify a model. A Backbone collection will default to using Backbone.Model if you don't specify this option. The following would work equally well if you don't need the models of the collection to be of a particular instance.
var CallsCollection = Backbone.Collection.extend({
url: 'url/to/external/json'
});
Reference
EDIT
In essence, specifying the model option within a collection is just a way to ensure that objects added to this collection will be instances of that particular model class. If the models being added to your collection don't have any custom behaviour outside of what is available to Backbone.Model, you don't need to create and specify a model as Backbone collections will default to using an instance of Backbone.Model as I have already mentioned. If, however, you wanted to ensure that models added to a particular collection were of a particular type and shared customized behaviour (e.g. validations, defaults, etc.), you would create your own model class by extending Backbone.Model and specifying this in the collection. I hope this clears things up for you.
Sounds Weird but this is the way.
Every collection in backbone, must represent a model, so basically a collections is a list of models.
Even if your model has no data, you need to indicate it when you create a Collection.
This is how backbone works for collections.

backbone.js and states that are not in models

In the data-driven paradigm of Backbone, the backbone views/routers should subscribe to model changes and act based on the model change events. Following this principle, the application's views/routers can be isolated from each other, which is great.
However, there are a lot of changes in the states of the applications that are not persisted in the models. For example, in a to-do app, there could be buttons that lets you look at tasks that are "completed", "not completed", or "all". This is an application state not persisted in the model. Note that the completion state of any task is persisted, but the current filter in the view is a transient state.
What is a good way to deal with such application state? Using a plain, non-backboned state means that the views/routers cannot listen to the changes in this state, and hence become difficult to code in the data-driven paradigm.
Your buttons filter example can be properly solved using Model events.
I suppose your buttons handlers have access to the tasks Collection. Then filter the collection and trigger events over the selected Models like:
model.trigger( "filter:selected" )
or
model.trigger( "filter:un-selected" )
The ModelView can be listening to these events on its Model and acts accordingly.
This is following your requirements of respecting the not use or "attributes that are not persistent" like selected but I don't have any trauma to use special attributes even if they shouldn't be persistent. So I also suggest to modify the selected attribute of your Models to represent volatile states.
So in your buttons handlers filter the collection and modify the selected attribute in your Models is my preferred solution:
model.set( "selected", true )
You can always override Model.toJSON() to clean up before sync or just leave this special attributes to travel to your server and being ignored there.
Comment got long so I'll produce a second answer to compare.
First, I feel like "completed", "not completed" are totally item model attributes that would be persisted. Or maybe if your items are owned by many users, each with their own "completed" "not completed" states, then the item would have a completedState submodel or something. Point being, while #fguillen produced two possible solutions for you, I also prefer to do it his second way, having models contain the attributes and the button / view doing most of the work.
To me it doesn't make sense for the model to have its own custom event for this. Sounds to me like a filter button would only have to deal with providing the appropriate views. (Which items to show) Thus, I would just make the button element call a function that runs a filter on the collection more or less directly.
event: {
'click filterBtnCompleted':'showCompleted'
},
showCompleted: function(event) {
var completedAry = this.itemCollection.filter(function(item) {
return item.get('completed');
});
// Code empties your current item views and re-renders them with just filtered models
}
I tend to tuck away these kind of convenience filter functions within the collection themselves so I can just call:
this.ItemCollection.getCompleted(); // etc.
Leaving these attributes in your model and ignoring them on your server is fine. Although again, it does sound to me like they would be attributes you want to persist.
One more thing, you said that using plain non-backboned states sacrifices events. (Grin :-) Not so! You can easily extend any object to have Backbone.Event capabilities.
var flamingo = {};
_.extend(flamingo, Backbone.Events);
Now you can have flamingo trigger and listen for events like anything else!
EDIT: to address Router > View data dealings -------------------//
What I do with my router might not be what you do, but I pass my appView into the router as an options. I have a function in appView called showView() that creates subviews. Thus my router has access to the views I'm dealing with pretty much directly.
// Router
initialize: function(options) {
this.appView = options.appView;
}
In our case, it may be the itemsView that will need to be filtered to present the completed items. Note: I also have a showView() function that manages subviews. You might just be working directly with appView in your scenario.
So when a route like /items/#completed is called, I might do something like this.
routes: {
'completed':'completed'
},
completed: {
var itemsView = ItemCollectionView.create({
'collection': // Your collection however you do it
});
this.appView.showView(itemsView);
itemsView.showCompleted(); // Calls the showCompleted() from View example way above
}
Does this help?

Implementing Backbone.Subset.js in Backbone.js to filter Models from a parent Collection

In this stackoverflow post i read about filtering backbone collections and using subsets.
One answer (by sled) recommends using backbone.subset.js (usage example).
I could not find any further resources on backbone.subset.js and I failed implementing it into my project.
It seems like backbone.subset.js is the perfect solution for what i'm trying to achieve.
(Having one "parent" collection that holds all models at all times, and depending on user input filtering the relevant models from the parent collection into a backbone.subset collection.)
My "parent" collection, holding all tasks:
var TasksAll = Backbone.Collection.extend({
url: '/tasks', // the REST url to retrieve collection data
model: Task // the models of which the collection consists of
});
var allTasks = new TasksAll();
Now i want to create a subset collection for e.g. tasks where task.status = 0:
var TasksTrash = new Backbone.Subset({
superset: allTasks,
filter: function(Task) {
return Task.isTrash();
}
});
var trashTasks = new TasksTrash();
Whereas inside the Task model, the method "isTrash" returns true if:
this.get('status') == 0
a) Are there any more resources on backbone.subset.js?
b) How do I implement above scenario?
c) Can I pass 'superset' and 'filter' options as params to the Backbone.Subset init function?
d) I looked into the backbone.subset.js code, when I 'reset' my parent Collection my subset Collections should be updated straight away, right?
PS: I'm fairly new to Backbone. Thanks for your help.
Looking at the source for backbone-subset, it looks as though there is a pre-initialization hook which you could utilize in order to make the 'sieve' or filter available as an option or argument:
https://github.com/masylum/Backbone.Subset/blob/master/backbone.subset.js#L50
As for providing parent as an argument, there is an outstanding patch to add that exact functionality:
https://github.com/masylum/Backbone.Subset/pull/5
With it, you can pass in parent as an option, if it is not an option the library will fall back to looking for it on the object Prototype

Categories

Resources