12

Lets say we have

Post = DS.Model.extend({
  comments: DS.hasMany({async: true})
})

Comment = DS.Model.extend({
  post: DS.belongsTo()
})

and I have to use the links feature because otherwise I get a 414 error due to too much comments on a post.

Since this commit https://github.com/emberjs/data/commit/4d717bff53f8c93bedbe74e7965a4f439882e259

It seems like it's impossible to trigger a reload of the post.get('comments'), ie sending a GET request on for example post/42/comments.

Is there any solution for that ?

Marcio Junior
  • 19,078
  • 4
  • 44
  • 47
sly7_7
  • 11,961
  • 3
  • 40
  • 54
  • I think the better way to solve this is to override the `update` method from `RecordArray`, in the `ManyArray` class. And give some context, like the `links`. At the moment using `comments.update()` will fetch all comments, insead of just 'post/1/comments'. But to do this, some pull request will be needed. – Marcio Junior Nov 27 '13 at 17:09
  • Yeah, as a first attempt, I tried to use the update method ;). I'd love to hear @wycats on this PR suggestion. – sly7_7 Nov 27 '13 at 17:19
  • Was this ever submitted as a PR? – claptimes Jan 15 '14 at 17:31
  • Thanks to @igorT, this is just pushed into master now: https://github.com/emberjs/data/pull/2297 Please try, feedback much appreciated :) – sly7_7 Sep 19 '14 at 13:33

6 Answers6

15

UPDATE

Like @sly7_7 said in your answer below, this feature is now avaliable in ember-data. So if you are using the 1.0.0-beta.11 version or greater, this code isn't needed

You can reopen the ManyArray, and create a new method called reloadLinks(). With the following:

var get = Ember.get;

DS.ManyArray.reopen({
    reloadLinks: function() {
        var records = get(this, 'content'),
        store = get(this, 'store'),
        owner = get(this, 'owner'),
        type = get(this, 'type'),
        name = get(this, 'name'),
        resolver = Ember.RSVP.defer();

        var meta = owner.constructor.metaForProperty(name);
        meta.type = type;
        var link = owner._data.links[meta.key];
        store.findHasMany(owner, link, meta, resolver);
    }
});

The usage is the following:

comments.reloadLinks();

Give a look in that fiddle http://jsfiddle.net/H6Gqf/

Marcio Junior
  • 19,078
  • 4
  • 44
  • 47
  • This is really interesting, and I think it could do the job for now :) One comment though, I find it weird to have to give the attributeName, as the function itself is on the corresponding manyarray. I think it's possible to retrieve it with some reflection on the many array right ? – sly7_7 Nov 27 '13 at 17:14
  • I don't found a way to do it. When the many array is created [here](https://github.com/emberjs/data/blob/e7996c4dc5248360466c1f330fde6f5455bb3ca4/packages/ember-data/lib/system/store.js#L576), we have the type and owner, but not the property name, and we need the property name. In your case bacause post have just one relationship with comment, maybe it's possible to identify the relationship. In other situation where a model has two relationships of the same type, how to know what is the current? Give a look in that commented code http://jsfiddle.net/marciojunior/E2Auc/ – Marcio Junior Nov 27 '13 at 18:01
  • Seems like a ManyArray has a property 'name', which is exactly what you're looking for. Or perhaps I'm missing something. see http://jsfiddle.net/mVxzn/2/ – sly7_7 Nov 28 '13 at 08:21
  • Amaze! You are right, this property is setup [here](https://github.com/emberjs/data/blob/6a2c661e66acc175370507b9ac5bdc1c782b2c59/packages/ember-data/lib/system/relationships/has_many.js#L43) but isn't present in the [class declaration](https://github.com/emberjs/data/blob/6a2c661e66acc175370507b9ac5bdc1c782b2c59/packages/ember-data/lib/system/record_arrays/record_array.js#L20) so I thought that doesn't exist :) I will update the answer. – Marcio Junior Nov 28 '13 at 11:17
  • Yes I will try to send tonight, there is some things to work, like the `isUpdating` flag, and update the `ManyArray` instead of create a new one. Thanks for the property name advice, helped a lot. – Marcio Junior Nov 28 '13 at 13:24
  • would this be a good place to do pagination? add `&offset=10&limit=100` or something similar to the `link` – David Nov 29 '13 at 03:37
  • 4
    Nice fix, but I needed to add one additional line of code to this fix. Just one line above the `store.findHasMany`, I added: `meta.type = type;`. I am working with Ember Data 1.0.0-beta.8 at the moment. – Jacob van Lingen Jul 02 '14 at 14:26
7

Thanks to @igorT, this is implemented. https://github.com/emberjs/data/pull/2297

Now the ManyArray has a reload() method, which you can even call if the relation has not been loaded yet.

You can see some use cases by reading the tests

sly7_7
  • 11,961
  • 3
  • 40
  • 54
2

Reloading a hasMany isn't directly built into Ember Data yet, but it's on its way. The solution I'm using requires Ember Data 1.0b9+canary, which includes opt-in coalesced finds.

Step 1: Opt In.

I've opted in at the ApplicationAdapter level, but you may want to do it on a per-model adapter basis. Here's what I have:

MyApp.ApplicationAdapter = DS.ActiveModelAdapter.extend({
  coalesceFindRequests: true
});

To give you an idea of why this is important and good, one page on our app was seeing 50+ requests prior to this change. We're now only seeing 11. It's still not great, but that's one hell of an improvement!

Step 2: Reloading Records in the Association

I simply wrap up reloading in a function on my model as so:

MyApp.SomeModel = DS.Model.extend({
  fooMany: DS.hasMany('foo', { async: true }),
  barMany: DS.hasMany('bar', { async: true }),
  bazOne: DS.belongsTo('baz', { async: true }),

  reloadAssociated: function () {
    var foosPromises = this.get('foos').invoke('reload'),
        barsPromises = this.get('bars').invoke('reload'),
        bazPromise = this.get('baz').reload();

    return Em.RSVP.hash({
      foos: foosPromises,
      bars: barsPromises,
      baz: bazPromise
    });
  }
});

I admit that it isn't terribly pretty, but it's the best solution I've come up with until reloading is built directly into associations.

jherdman
  • 161
  • 1
  • 5
1

i had the same problem and i cheated to find a "solution"

i created a component "CommentLoader". This component takes one argument ( post_id ) and before "inserting" (willInsertElement) i grad all the comments and render them. it works but ... i am not proud of it :(

hkairi
  • 385
  • 2
  • 9
  • What do you mean by "grad all the comments" ? You make a custom ajax request in the willInsertElement, like $.ajax('/comments?post_id=42') ? – sly7_7 Nov 27 '13 at 16:45
  • i meant grab : i used queryparmas ( this.store.find('comment', { post_id: params.post_id }) – hkairi Nov 28 '13 at 11:41
  • Ok, that should work, but unfortunately it does not care about the 'links' things. Thanks for giving some time about it. I think I will accept @marcio-rodrigues-correa-junior answer. – sly7_7 Nov 28 '13 at 13:12
1

With ember-data-1.0.0-beta.10 I'm using the following model extension.

Just call model.reloadRelationship(name) where name is the name of the model attribute representing the relationship.

This works for both normal and link belongsTo/hasMany relationships.

DS.Model.reopen({
    reloadRelationship: function(name) {
        var meta = this.constructor.metaForProperty(name),
            link = this._data.links ? this._data.links[meta.key] : null;
        if (!link) {
            if (meta.kind === 'belongsTo') {
                this.get(name).then(function(model) { model.reload(); });
            } else {
                this.get(name).invoke('reload');
            }
        } else {
            meta.type = this.constructor.typeForRelationship(name);
            if (meta.kind === 'belongsTo') {
                this.store.findBelongsTo(this, link, meta);
            } else {
                this.store.findHasMany(this, link, meta);
            }
        }
    }
});

The only thing missing here are some checks, for example a check if the model is already reloading when the model is reloaded with a link or a check to see if the property name exists within the current model.

EDIT ember-data-1.0.0-beta.14:

DS.Model.reopen({
    reloadRelationship: function(key) {
        var record = this._relationships[key];
        if (record.relationshipMeta.kind === 'belongsTo') {
            return this.reloadBelongsTo(key);
        } else {
            return this.reloadHasMany(key);
        }
    },
    reloadHasMany: function(key) {
        var record = this._relationships[key];
        return record.reload();
    },
    reloadBelongsTo: function(key) {
        var record = this._relationships[key];
        if (record.link) {
            return record.fetchLink();
        } else {
            record = this.store.getById(record.relationshipMeta.type, this._data[key]);
            return record.get('isEmpty') ? this.get(key) : record.reload();
        }
    }
});

HasMany relationship will fallback to native reload method.

For BelongsTo relationship, it will first check if record needs to be reloaded (if it is not loaded before yet, it will only call get to retrieve the record, otherwise it will call reload).

jcbvm
  • 1,640
  • 1
  • 15
  • 22
0

Another option is to add a method to the model. Should also work for hasMany:

search: DS.belongsTo('search', {async: true})

updateSearch: ->
  resolver = Ember.RSVP.defer()
  resolver.resolve = =>
    # do something after promise resolves
  relationship = @constructor.metaForProperty('search')
  link = @_data.links[relationship.key]
  @store.findBelongsTo this, link, relationship, resolver
andorov
  • 4,197
  • 3
  • 39
  • 52