Count async hasMany without loading


#1

How can I count related objects associated via hasMany without loading all them? Naive post.get('comments.length') would first load all comments. I used to do post.get('data.comments.length') but now data is deprecated =(

I know about post._data but now I’m afarid to use it (after they added that underscore :smiley: )


#3

Have another problem… How to check if there is associated model (belongsTo)?!

{{#if comment.post}}
  comment.parent will be loaded to display this =(
{{/if}}

{{#if comment.post.id}}
  comment.parent still will be loaded to display this  =(
{{/if}}

This also loads post model: hasPost: Ember.computed.notEmpty('post')

The only workaround is to use _data =(


#4

Here is my dirty workaround. I tweaked serializer:

ApplicationSerializer = DS.RESTSerializer.extend
  normalize: (type, hash, prop)->
    # itarate over properties came from payload
    for key, value of hash
      try
        meta = type.metaForProperty(key)
      catch e
        # some keys absent in type class
        continue
      injectProperty = (key, value, attr_type)->
        if not hash[key]
          try
            # absent property will throw Error
            meta = type.metaForProperty(key)
            # this type already extended with that property
            if meta.type is attr_type
              hash[key] = value
          catch e
            hash[key] = value
            tmp = {}
            tmp[key] = DS.attr(attr_type)
            type.reopen tmp
      switch meta.kind
        when 'hasMany'
          # create an array for 'hasMany'
          injectProperty "#{key}_ids", value.slice(), 'array'
          injectProperty "has_#{key}", !!value.length, 'boolean'
        when 'belongsTo'
          # create and XXX_id and has_XXX for 'belongsTo'
          injectProperty "#{key}_id", value, 'string'
          injectProperty "has_#{key}", !!value, 'boolean'
    return @_super.apply @, arguments 

This tweaked serialized let’s you check if relationship on a model empty or not without loading associated models, as a bonus you can get ids for associated models.

models

App.Post = DS.model.extend
  text: DS.attr 'string'
  comments: DS.hasMany 'comment', async: true

App.Comment = DS.model.extend
  text: DS.attr 'string'
  post: DS.belongsTo 'post', async: true

usage

{{#if post.has_comments}}
  This post has {{post.comments_ids.length}} comments
{{/if}}
{{#if comment.has_post}}
  This comment associated with post #{{comment.post_id}}
{{/if}}

#5

Really interesting solution. So, your backend is always returning all the ids and you’re dealing with them on client side while still having the metadata for the general type available.

Have you considered possible decrease in performance when dealing with large set of models? (I know ember-data is not the best solution in this scenario anyway…)

I’m going to try it as soon as possible, thanks for sharing!


#6

In cases when ids list may be large (for example comments) we introduce proxy model:

App.Step = DS.Model.extend
  comment_proxy: DS.belongsTo 'comment-proxy', async: true
  comments_count: DS.attr 'number'

App.CommentProxy = DS.Model.extend
  comments: DS.hasMany 'comment', async: true

App.Comment = DS.Model.extend
  text: DS.attr 'string'
  target: DS.belongsTo 'step', async: true

So we load comment_proxy only when user want to interact with comments. Yes It can be slow. We have item with >3200 comments check out numbers:

  • comment_proxy payload is about 14kb
  • it takes about 4 seconds to show comments after user pressed “show comments button”

Of course we do not load all 3200 comments, only first 17 comments will be loaded.

Anyway we need all ids on client-side to implements permalinks. Here is why: imagine someone linked 3200th comment – you dont want to load ALL comments, so our solution is to extract subarray with linked ID and few siblings load only them and display.

BTW discourse have similar solution! Here are 40k+ comments thread – http://what.thedailywtf.com/t/the-thread-of/1000/10465 you can open devtools and then try to load this page


#7

You can check the new references api http://emberjs.com/blog/2016/03/13/ember-data-2-4-released.html#toc_code-ds-references-code