Backbone-relational
                                
                                 Backbone-relational copied to clipboard
                                
                                    Backbone-relational copied to clipboard
                            
                            
                            
                        Change events not being fired on collection change
When a nested collection changes (EDIT: item removal, specifically), the model doesn't get notified.
This or similar should do the trick (it's worked for me outside of backbone-relational):
    collection.bind("all", function() {
      this.change();
    }, this);
thoughts?
I made this work for item adds/removes on a related collection using this.get('features-has-many-collection').bind("all", function() { this.change() }, but I'm not seeing how to iterate the collections on a model, as this._relations doesn't have meta-data for actually getting to the associated collections.
Recommendations?
I'm getting the same behavior on Backbone 0.5.3 and Backbone-Relational 0.4.0. Changes to models in nested collections fire events on the nested collection, but do not fire on the parent-relation model.
E.g. (in Coffeescript)
class Event extends Backbone.RelationalModel
  relations: [{
          type: Backbone.HasMany,
          key: 'rsvps'
          relatedModel: 'Rsvp'
          collectionType: 'RsvpsCollection'
          createModels: true
          reverseRelation:
            key: 'event'
            includeInJSON: 'id'
         }]
  ...
At run time (in Firebug/Acebug)
e.bind 'all', () ->
  console.log 'event fired on Event object with arguments: ', arguments
r = e.get('rsvps').first()
r.bind 'all', () ->
  console.log 'event fired on Rsvp object with arguments: ', arguments
r.set {response: 'yes'}
# => event fired on Rsvp object with arguments: ["change", Rsvp ... ]
r.destroy()
# => event fired on Rsvp object with arguments: ["remove", Rsvp ... ]
# => event fired on Rsvp object with arguments: ["update:event", Rsvp ... ]
# => event fired on Event object with arguments: ["remove:rsvps", Rsvp ... ]
# => event fired on Rsvp object with arguments: ["destroy", Rsvp ... ]
If you want to handle events for a nested collection, you can bind to that collection, just like in plain Backbone. This should work (does work as far as I can tell) for change, add and remove.
As per your example:
e.get( 'rsvps' ).bind( 'all', function() {
        console.log( 'event fired on Event.rsvps; arguments=%o', arguments );
    });
Hmm, sorry, this is something that should be working I guess (binding on rsvps:add and rsvps:remove from event).
Checked again, this seems fine for me? Action sequence:
console.log( 'set {response:yes} on r' );
r.set( { 'response': 'yes' } );
console.log( 'remove r from e' );
e.get( 'rsvps' ).remove( r );
console.log( 'adding another rsvp' );
e.get( 'rsvps').add( { name: "Jacob" } );
Events logged:
set {response:yes} on r
event fired on Event.rsvps; arguments=["change:response", ...
event fired on Rsvp; arguments=["change:response", ...
event fired on Event.rsvps; arguments=["change", ...
event fired on Rsvp; arguments=["change", ...
remove r from e
event fired on Event.rsvps; arguments=["remove", ...
event fired on Rsvp; arguments=["remove", ...
event fired on Rsvp; arguments=["update:event", ...
event fired on Event; arguments=["remove:rsvps", ...
event fired on Event.rsvps; arguments=["relational:remove", ...
adding another rsvp
event fired on Event.rsvps; arguments=["add", ...
event fired on Event.rsvps; arguments=["update:event", ...
event fired on Event; arguments=["add:rsvps", ...
event fired on Event.rsvps; arguments=["relational:add", ...
I opened this ticket because I was dissatisfied with the need to call bind('all') on all of my nested collections, in order to get notified of their changes. Calling those bind()'s myself works fine, but I wished for a single convenience method so that I'd receive a change() notification on my model when any of its nested collections change. It's pretty specific to some pretty common cases.
this.notifyOnRelationsChanged();
this.notifyOnRelationsChanged(function() { this.change() });
this.notifyOnRelationsChanged({only:['items', 'books']})
this.notifyOnRelationsChanged({except: ['interestedUsers']});
@PaulUithol Strange, I just double checked this again in Firebug/Acebug and I'm still not seeing change events:
#>> lang=cf
e = router.events.first()
rsvps = e.get 'rsvps'
r = rsvps.first()
e.bind 'all', () ->
  log 'Event event fired.  Args: ', arguments
r.bind 'all', () ->
  log 'RSVP event fired.  Args: ', arguments
log 'changing rsvp'
r.set response: 'no'
log 'deleting rsvp'
r.destroy()
Output:
changing rsvp
deleting rsvp
DELETE http://localhost:3000/rsvps/29
200 OK
    2.63s   
RSVP event fired. Args: ["remove", Rsvp ...
RSVP event fired. Args: ["remove", Rsvp ...
RSVP event fired. Args: ["update:event", Rsvp ...
Event event fired. Args: ["remove:rsvps", Rsvp ...
RSVP event fired. Args: ["destroy", Rsvp ...
I defined the relations array only in Backbone.Models.Events, but it has the reverseRelation attribute defined.  Could this be causing the issue?
(Btw, if this is too tangential to the original post I can create a new issue)
@rjharmon: ahh, okay. The idea I got from your post was just that you had issues with remove events not getting fired.
I don't think firing a change event for every modification on any relation by defaults would be a good idea, but something like that is pretty easy to do by adding a helper method to Backbone.RelationalModel, since this._relations actually does give you the related model or collection. Quick first attempt that you could build from:
Backbone.RelationalModel.prototype.bindRelationEvents = function() {
    var dit = this;
    _.each( this.getRelations(), function( relation ) {
        relation.related && relation.related.bind( 'all', function() {
            dit.trigger.apply( dit, arguments );
            // Or even simply `dit.render()`, if the number of events is reasonable...
        } );
    });
};
then call this.bindRelationEvents for objects you'd like to receive all events from their relations as well.
Are there any news on this? We have a Backgrid table in our application that renders related models in a special cell and also offers an editor for that cell to change the related models. Changing the related models is no problem, but the application has to perform further actions when that relation changes. At the moment our custom cell editor just handles this too, but that is not a satisfying solution because it does not follow the principal of separation of concerns.
The problem is that, as mentioned above, the change event is not triggered for the relation's attribute.
I. e., we have a parent model ParentModel that has a HasMany relationship called 'related_items'.
var ParentModel = Backbone.RelationalModel.extend({
    defaults: function() {
        return {
            related_items: new ItemCollection();
        };
    },
    relations: [
        {
            key: 'related_items',
            type: Backbone.HasMany,
            relatedModel: ItemCollection.prototype.model
            // ...
        }
    ]
});
Listening to the 'change:related_items' on the ParentModel or a ParentModelCollection is pretty useless because that event is never triggered. Not even if you switch out the 'related_items' collection entirely: my_parent_model.set('related_items', new_collection); does not trigger the event.
Following code would seem totally natural to me
// this refers to some view which handles displaying a ParentModelCollection
this.listenTo(this.collection, 'change:related_items', this.changeCallback);
but as I understand this conversation, that is deliberately not supported. Instead I would have to iterate over each item in the collection and add a listener directly on each related collection, something like this
this.collection.each(function(model) {
    this.listenTo(model.get('related_items'), 'change', this.changeCallback);
}, this);
But that is totally impractical, because whenever new models are added or removed from my ParentModelCollection or whenever the referenced 'related_items' collection changes, I have to remove and re-bind my changeCallback for that particular model.
This is not the only time that we stumble on this problem. Our application needs to react to changed HasMany relations quite often. So I don't understand your statement:
I don't think firing a change event for every modification on any relation by defaults would be a good idea,
Why don't you think 'bubbling up' the change event to the parent model was a good idea? Can't be that much of an impact on performance I think and it would definitely make reacting to changed HasMany relations a lot more concise and straight forward.
That would cause a lot of events being fired when updating large collections.
@floriandammeyer Can you check out this jsfiddle and fork it to show a failing test for your use case?
I will take a look at it on the weekend
Finally got the time now, I forked your fiddle here: http://jsfiddle.net/ee19fo4r/1/
In the first test case, I changed the expected behavior so that changing a model inside the child collection would also trigger a change event on the parent model. That behavior however does not make sense at all, because it would conflict with the expected behavior of the following test. You would not be able to determine what actually changed: a model inside the collection or the collection itself.
The following test expects a change event to be fired on the parent model if the related collection has been substituted entirely. I think this test case should be implemented, because it is the behavior that I expected from Backbone.Relational. If you switch out the collection entirely by calling parent.set('children', new Backbone.Collection());, it should trigger a change event for the children attribute just like chaning a normal attribute (like name) would do. However, at this moment that does not happen. Switching out the related collection happens silently, and this is problematic for my initial use case. Example:
// ...
Backbone.View.extend({
  initialize: function()
  {
    // This view should react to changes that happen to child models 
    // of the given parent models, so we iterate over all models in the
    // given collection to add an event handler
    this.collection.each(function(model) {
      this.listenTo(model.get('children'), 'change', this.handleChangedChild);
    }, this);
    // Obviously, if the related 'children' collection changes on any of the
    // parent models, we have to re-bind our listener to the new collection!
    this.listenTo(this.collection, 'change:children', function(model) {
      this.listenTo(model.get('children'), 'change', this.handleChangedChild);
      // Note: I did not implement any garbage-collection here 
      // that removes the old listeners for brevity
    });
  }
  // ...
});
// ...
In our application a related collection is being substituted quite frequently. Unfortunately, because no change event is triggered in that case, above code does not work as expected.