Ognjen Regoje bio photo

Ognjen Regoje
But you can call me Oggy


I make things that run on the web (mostly).
More ABOUT me and my PROJECTS.

me@ognjen.io LinkedIn

My favorite part of Backbone.js is the collections

#technical

I mostly use Vue.js due to it’s very intuitive templating system and excellent reactivity. But I still very much miss one specific feature of Backbone.js: collections.

In particular I miss the abilty to make scopes in a collection that return an instance of that collection – essentially making it function like an ActiveRecord::Collection.

Here’s an example:

OrdersCollection = Backbone.Collection.extend(
  model: Order
  by_user_id: (user_id) ->
    return new OrdersCollection(this.toArray().filter((x) ->
      return x.get("user_id") == user_id
    ))

  by_status: (status) ->
    return new OrdersCollection(this.toArray().filter((x) ->
      return x.get("status") == status
    ))
)

Which allows me to chain them then:

  orders_collection.by_user_id(1).by_status("completed")

And by memoizing these functions it becomes even more efficient:

OrdersCollection = Backbone.Collection.extend(
  model: Order
  initialize: ->
    this.orders_by_status = {}

  by_user_id: (user_id) ->
    return new OrdersCollection(this.toArray().filter((x) ->
      return x.get("user_id") == user_id
    ))

  filter_by_status: (status) ->
    return new OrdersCollection(this.toArray().filter((x) ->
      return x.get("status") == status
    ))

  by_status: (status) ->
    this.orders_by_status[status] = this.orders_by_status[status] || this.filter_by_status(status)
)

That way repeatedly calling by_status(status) doesn’t trigger filtering repeatedly.

And the nuclear version that allows any filter and also caches it:

OrdersCollection = Backbone.Collection.extend(
  model: Order
  initialize: ->
    this.orders_by_status = {}

  parse_scope: (scope) ->
    if typeof(scope) == "function"
      new Stats.Collections.OrdersCollection(_.filter(this.toArray(), (current) ->
        return scope.apply(current)
      ))
    else
      new Stats.Collections.OrdersCollection(_.filter(this.toArray(), (current) ->
        if typeof(current[scope]) == "function"
          return current[scope]()
        else
          return current.scoped(scope)
      ))

  w: (scope) ->
    this["parsed_" + scope] = this["parsed_" + scope] || this.parse_scope(scope)
)

It accepts name of a function defined on the collection and then caches the results.

It also accepts name of a function defined on the model that returns boolean and also caches the results.

And finally it also accepts a function that’d be executed on the model, for example:

  collection.w(() -> return this.get("status") == "paid")

This brings it very close to ActiveRecord::Collection and makes things much easier (and faster). It’s particular useful when calculating statistics and collections are repeatedly filtered.