New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
model method without prototype on Backbone.Collection causes crash #3408
Comments
I think the bigger issue here is that when model is a function that returns an instance modelId: function(model, attrs) {
return attrs[model.idAttribute || 'id'];
} Since all the uses of it have a reference to model handy and since it's not released it would be easy to make this change. |
Actually the only issue would be with the |
I was always a little curious why As for setting |
@jridgewell |
@johnfn But why are you trying to bind the |
@thejameskyle I wanted to use a property on |
Hmm, does it make sense to potentially change the constructor in var model;
if (this._isModel(this.model.prototype) || this.model === Model) {
model = new this.model(attrs, options);
} else {
model = this.model(attrs, options);
} This way it doesn't |
I read the code and jumped into the discussion. Sorry, I should have finished reading. Changing function modelId(attrs) {
return _.result(this.model.prototype, 'idAttribute', 'id');
} |
@jridgewell I don't see how that helps? |
If |
That means it'd still be a problem if you have a custom |
That was deemed acceptable in #2836. I was just trying to get rid of the error in the base I think it's going to end up being left to you to define a custom |
Since |
I realize now I forgot the function modelId(attrs) {
return attrs[_.result(this.model.prototype, 'idAttribute', 'id')];
} Also, after thinking about it, it does make sense that |
@jridgewell That's essentially what exists today, this update was so that Collections could determine uniqueness (ids) instead of models (See: 4e2d209). |
Moral of the story, avoid Do we need to rethink the way modelId works with |
@akre54 He wasn't binding |
Ok so what's the issue? If @johnfn's collection looked like this instead, you'd also expect it not to work, right? var myCollection = Backbone.Collection.extend({
initialize: function() {
this.model.prototype = null;
},
model: Backbone.Model
}); |
@akre54 Shrug, like I mentioned above I think the bigger issue is specifying model as a method that returns an instance. var Library = Backbone.Collection.extend({
model: function(attrs, options) {
if (condition) {
return new PublicDocument(attrs, options);
} else {
return new PrivateDocument(attrs, options);
}
}
}); This will make |
Only if you're using an idAttribute other than |
👍 I wasn't aware of this prior to this issue, so that'd be good.
I think so, |
We usually add documentation when we cut a release. I've had a 1.2.0 pull open for a while now (#3285), which may or may not get used for docs, but I'll add this in just in case.
Can you explain? How does it make sense to pass both the model and its attributes? Of the 6 instances |
I agree
|
We want to be able to call
Is #2920 a real issue though? I think the argument time and again has been that you shouldn't use arbitrary strings for your ids. It should be given to you by your persistence layer (as an integer or uuid, etc). |
Yeah, I don't want to get rid of this either. I think this is once again an issue of multiple model types, and wanting to know what type you're dealing with. Although I don't know how common having multiple types in a collection with different |
Damn, I forgot to search for places using
What are the collection attributeMethods?
No, but we need to be able to quickly grab the model's old id (eg. |
Actually, we could instantiate the model in function get(obj) {
if (obj == null) return void 0;
return this._byId[obj] || this._byId[this.modelId(this._isModel(obj) ? obj : new this.model(obj)) || this._byId[obj.cid];
} |
Strongly disagree. |
I don't view instantiating an object as a side effect? We're not adding the model to the collection, just creating it so that it can be passed to |
Scratch my diff --git backbone.js backbone.js
index 963fb15..14b6ce4 100644
--- backbone.js
+++ backbone.js
@@ -674,7 +674,7 @@
for (var i = 0, length = models.length; i < length; i++) {
var model = models[i] = this.get(models[i]);
if (!model) continue;
- var id = this.modelId(model.attributes);
+ var id = this.modelId(model);
if (id != null) delete this._byId[id];
delete this._byId[model.cid];
var index = this.indexOf(model);
@@ -736,7 +736,7 @@
// Do not add multiple models with the same `id`.
model = existing || model;
if (!model) continue;
- id = this.modelId(model.attributes);
+ id = this.modelId(model);
if (order && (model.isNew() || !modelMap[id])) {
order.push(model);
@@ -837,8 +837,7 @@
// Get a model from the set by id.
get: function(obj) {
if (obj == null) return void 0;
- var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
- return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
+ return this._byId[obj] || this._byId[obj.cid] || this._byId[this.modelId(obj)];
},
// Get the model at the given index.
@@ -935,8 +934,9 @@
},
// Define how to uniquely identify models in the collection.
- modelId: function (attrs) {
- return attrs[this.model.prototype.idAttribute || 'id'];
+ modelId: function (model) {
+ var idAttribute = _.result(this.model.prototype, 'idAttribute') || 'id';
+ return this._isModel(model) ? model.id : model[idAttribute];
},
// Private method to reset all internal state. Called when the collection
@@ -971,7 +971,7 @@
// Internal method to create a model's ties to a collection.
_addReference: function(model, options) {
this._byId[model.cid] = model;
- var id = this.modelId(model.attributes);
+ var id = this.modelId(model);
if (id != null) this._byId[id] = model;
model.on('all', this._onModelEvent, this);
},
@@ -991,7 +991,7 @@
if (event === 'destroy') this.remove(model, options);
if (event === 'change') {
var prevId = this.modelId(model.previousAttributes());
- var id = this.modelId(model.attributes);
+ var id = this.modelId(model);
if (prevId !== id) {
if (prevId != null) delete this._byId[prevId];
if (id != null) this._byId[id] = model;
diff --git test/collection.js test/collection.js
index 27c87b0..38cfa57 100644
--- test/collection.js
+++ test/collection.js
@@ -1422,6 +1422,9 @@
},
modelId: function (attrs) {
+ if (this._isModel(attrs)) {
+ attrs = attrs.attributes;
+ }
return attrs.type + '-' + attrs.id;
}
}); |
Agreed. Though as a counter anecdote to this I recently worked on a project where tracks were keyed in the db by
Nope. We've gone down that path before and it's horrible. We built the class creator at Skillshare using an older version of Backbone that had this behavior. We had to hack around correctly parsing ids (and even temporarily persisting cids) to get models to show up correctly. Not the behavior you want at all and I'm grateful that it was eventually fixed. Your change seems natural, but I'm not a fan of polluting |
I was finding that out. I'm 👍 for leaving function modelId(attrs) {
return attrs[_.result(this.model.prototype, 'idAttribute') || 'id'];
}
I forgot, @danapplegate mentioned you worked at Skillshare. |
Why would idAttribute ever be a function? What's the goal of using _.result there? If it's just for the guard we can do that in plain js. |
Just a guard. |
Ok how about modelId: function (attrs) {
var idAttr = (this.model.prototype && this.model.prototype.idAttribute) || 'id';
return attrs[idAttr];
} or modelId: function (attrs) {
var modelProto = this.model.prototype;
return attrs[(modelProto && modelProto.idAttribute) || 'id'];
} To be sure, it looks like we're back where we started. Which is to say it looks like this case is only useful when you're overriding |
I prefer the second option, personally.
I think this that's appropriate. 👍 |
This was discussed previously in #2080; however it was discussed in the context of
_.bindAll(this)
and I think that confused the discussion, because the issue is not about_.bindAll(this)
at all. It actually happens whenever themodel
function lacks aprototype
property. For example:Backbone errors on this line https://github.com/jashkenas/backbone/blob/master/backbone.js#L939 because
prototype
doesn't exist.Obviously, few people would write
this.model.prototype = null
, but a lot of people might writethis.model = _.bind(this.model)
, which also removes the prototype. Backbone should be able to handle this case.It seems like it'd be pretty straightforward to have line 939 also check for the existence of
prototype
. What do you think?The text was updated successfully, but these errors were encountered: