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 exampleGET /<resource>
returns an array of objects,[{ id: 1 }, { id: 2 }]
for examplePOST /<resource> { some: 'field' }
creates a single item in your database and returns the updated item,{ id: 1, some: 'field' }
for examplePUT /<resource>/:id { updated: 'field' }
updates a single item in your database and returns the updated item,{ id: 1, updated: 'field' }
for examplePUT /<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 databaseDELETE /<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.
Updated less than a minute ago