platanus/angular-restmod

BelongsToMany relations as $collections?

Closed this issue · 2 comments

I've been struggling with this one, and would really appreciate some advice or a push in the right direction if anyone can help. My models look as follows:

var Organization = restmod.model('/organizations/organization', {
  orders: {belongsToMany: 'Order'},
});

var Order = restmod.model('/orders/order', {
  ...
});

The behavior I want is for the relation of orders on an Organization to not be scoped to the parent url (hence the usage of belongsToMany as opposed to hasMany) however I still wish for Organization.orders to be a Collection. i.e. so that I can still call:

Organization.orders.$create() and it will be added Organization.orders, but it will also make a POST request to /orders/order and not /organizations/organization/<pk>/orders/order. To elaborate with some more examples:

var newOrganization = Organization.$build();

newOrganization.orders.$create({date: '2015-11-18'}); //   POST  /orders/order

newOrganization.orders[0].date === '2015-11-18'; // true

newOrganization.orders[0].date = '2016-1-1';
newOrganization.orders[0].$save(); //   PATCH  /orders/order/<pk>

newOrganization.orders[0].$destroy(); // DELETE /orders/order/<pk>
newOrganization.orders.length === 0; // true

Additionally, my API returns the orders attached to an organization as inlined data, and I wish for that inlined data to be put into a Collection that is only scoped to the /orders/order url. i.e.

// GET /organizations/organization/1

{
  "id": 1,
  "orders": [
    {"id": 1, "date": "2015-11-18"},
    {"id": 2, "date": "2015-12-18"}
  ]
}
var existingOrg = Organization.$find(1);

existingOrg.orders.length === 2; // true
existingOrg.orders.$create({date: '2017-1-1'}); // POST /orders/order
// Note the call to $create, as I want the inlined data to be unpacked into a collection

existingOrg.orders.length === 3; // true

Effectively I want the scoping of the belongsToMany relations, which use the urls of the nested model, but I want the 'orders' object itself to be a Collection so I can still call collection methods such as $create on it.

Is this at all possible, either by using a hasMany and hacking the urls to the way I want, or by using a `belongsToMany`` and forcing it to return a Collection?

You could go around the problem by configuring the model to mimic the relation behavior, like this:

// The Organization model:
restmod.model('/organizations/organization', {
  orders: {
    init: function() { 
      // create a new order collection for every new organization.
      return Order.$collection(); 
    },
    decode: function(_raw) {
      // pass inlined orders to collection on server response decoding
      this.orders.$reset().$decode(_raw);
    },
    mask: 'CU' // prevent orders attribute from being sent on create or update.
  }
});

I started doing it that way, but our entire API is structured like this, so I was getting tired of doing this in many different places. Instead, I ended up using a hasMany relation, and just altered how urls were determined. This allowed the relations to function as collections as I desired, but using the urls I wanted. For reference, it looked something like this:

.factory('NestedCollectionApi', [
  'restmod',
  function (restmod) {
    return restmod.mixin({
      $config: {
        hasMany: {
          hooks: {
            'after-has-many-init': function () {
              this.$url = _.partial(this.$urlFor, this);
            }
          }
        },
        hasOne: {
          hooks: {
            'after-has-one-init': function () {
              this.$url = _.partial(this.$urlFor, this);
            }
          }
        }
      }
    });
  }
])

This seemed to perform the behavior I wanted, by ensuring that whenever I used a model as a nested relation, the urls used were not scoped underneath the parent. I'll go ahead and mark this as closed.