Resources/Models

Everything about Resources/Models

What is a Resource?

In JSData, a Resource (Model) is the data and meta data associated with a particular RESTful entity.

Why RESTful? It's the easiest to generalize. But what RESTful means is relative, so JSData makes some fairly general assumptions about your RESTful resources and then allows for customization.

Default assumptions:

  • GET /<resource>/:id returns an object, { id: 1 } for example
  • GET /<resource> returns an array of objects, [{ id: 1 }, { id: 2 }] for example
  • POST /<resource> { some: 'field' } creates a single item in your database and returns the updated item, { id: 1, some: 'field' } for example
  • PUT /<resource>/:id { updated: 'field' } updates a single item in your database and returns the updated item, { id: 1, updated: 'field' } for example
  • PUT /<resource> { updated: 'field' } updates a collection of items in your database and returns the updated items, [{ id: 1, updated: 'field' }, { id: 2, updated: 'field' }] for example
    -DELETE /<resource>/:id deletes a single item from your database
  • DELETE /<resource> deletes a collection of items from your database

There are many variations of this pattern: you might use PATCH instead of PUT, your server might return some custom response object with the actual resource payload nested inside somewhere, the name of your resource might differ from the endpoint your server uses, you might use nested resource urls for resources that have relations, etc. This can all
be configured when you define a resource with JSData.

How do I define resources?

You define resources and register them with the data store via DS#defineResource, which returns a resource definition. You can think of a resource definition as a static Model class. It holds meta information about the resource and provides a number of static methods for operating on instances of the Model class. While the resource definition itself isn't a constructor function, it can be used to create instances of the Model via DS#createInstance. You can decorate resource definitions with your own static methods that hold domain logic related to the resource so you can stay DRY.

Instances of Resources have shorthand proxies of some of the Resource methods, similar to how Resources proxy all of the DS methods. For example, the following are equivalent:

  • DS#hasChanges(resourceName, id)
  • Resource#hasChanges(id)
  • Instance#DSHasChanges() - The methods are namespaced here so as not to collide with any instance methods you might add

Example of shorthands

var store = new JSData.DS();
var User = store.defineResource('user');

var user = store.inject('user', { id: 1 });
var user2 = User.inject({ id: 1 }); // resource shorthand method

user === user2; // true

user.name = 'John';

// ...

// these are all the same
store.hasChanges('user', 1); // true
User.hasChanges(1); // true
user.DSHasChanges(); // true

Instance Shorthands

Here is the list of instance shorthands:

  • DSCompute()
  • DSRefresh([options])
  • DSSave([options])
  • DSUpdate([options])
  • DSDestroy([options])
  • DSCreate([options])
  • DSLoadRelations([relations][, options])
  • DSChangeHistory()
  • DSChanges()
  • DSHasChanges()
  • DSLastModified()
  • DSLastSaved()
  • DSPrevious()
  • DSRevert()

Simplest resource definition example ever:

var User = store.defineResource('user');

With that simple definition the data store can now interact with the User resource according to the general assumptions described above. See DS#defineResource for detailed API information.

Create instances of User:

var user = User.createInstance({ name: 'John' });

Customize Store & Resource Methods

The static methods on Stores and Resources can be configured using Aspect Oriented Programming. This is useful if you always want a certain option set to a certain value just for a certain method of a resource. For example:

var Foo = store.defineResource('foo');
// default is false
Foo.bypassCache; // false

// so, when you call "find", it will use the cached item if one is available
Foo.inject({ id: 1 });
 // no request is made
Foo.find(1).then(...);
                 
// but, let's say we want "findAll" calls to always make a request
Foo.findAll.before(function (params, options) {
  options = options || {};
  options.bypassCache = true;
  // these args are passed to the original findAll method,
  // which is being invoked next
  return [params, options];
});

Another example:

var Foo = store.defineResource('foo');

// original Foo.createInstance method
var orig = Foo.createInstance;

// pass a callback into ".before"
// the callback will receive the arguments passed
// to the method you're configuring
Foo.createInstance.before(function (attrs) {
  this === Foo; // callback is invoked in the context of the Resource
  
  if (attrs && !('name' in attrs)) {
    // you can either modify the arguments that were passed in
    attrs.name = 'hi';
  } else if (arguments.length === 0) {
    // or you can return an array,
    // the contents will be the arguments passed to
    // the original method when it's invoked
    return [{ id: 'anonymous' }];
  }
});

// Foo has a new createInstance method now
orig !== Foo.createInstance; // true

var foo = Foo.createInstance({ id: 1 });
foo; // { id: 1, name: 'hi' }

var foo2 = Foo.createInstance();
foo2; // { id: 'anonymous' }

Resource definitions are cached

When creating a resource, those resources are cached at `store.definitions.. If you attempt to create a resource with the same name a second time, it will error. Instead, the developer needs to return the previously created definition.

var User = store.defineResource('user')

var User2 = store.defineResource('user') // will error

// instead

var User2 = store.definitions.user

Additional reading:

📘

Need help?

Want more examples or have a question? Post on the Slack channel or mailing list then we'll get your question answered and probably update this wiki.