{"id":17188235,"url":"https://github.com/jclem/rest-model","last_synced_at":"2025-04-13T19:14:09.246Z","repository":{"id":16270352,"uuid":"19018601","full_name":"jclem/rest-model","owner":"jclem","description":"a simple library for working with APIs in Ember","archived":false,"fork":false,"pushed_at":"2016-10-27T21:51:45.000Z","size":1164,"stargazers_count":18,"open_issues_count":2,"forks_count":8,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-05T16:57:20.062Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jclem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-22T05:46:37.000Z","updated_at":"2020-02-10T20:46:18.000Z","dependencies_parsed_at":"2022-08-04T08:45:17.619Z","dependency_job_id":null,"html_url":"https://github.com/jclem/rest-model","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jclem%2Frest-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jclem%2Frest-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jclem%2Frest-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jclem%2Frest-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jclem","download_url":"https://codeload.github.com/jclem/rest-model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766751,"owners_count":21158301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T01:08:28.156Z","updated_at":"2025-04-13T19:14:09.218Z","avatar_url":"https://github.com/jclem.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RestModel\n\n[![Build Status](https://travis-ci.org/jclem/rest-model.svg?branch=master)](https://travis-ci.org/jclem/rest-model)\n\nRestModel is a library for interacting with a REST API in Ember. Its goal is to\nprovide a library with predictable behavior and which can be easily extended to\nmeet custom needs.\n\n## Documentation\n\nThis readme, and more detailed work-in-progress\n[here, on GitHub pages](http://dingus.io/rest-model/).\n\n## Install\n\n```bash\nbower install rest-model --save\n```\n\n## Use\n\n### Basic Use\n\nUsing RestModel is as simple as extending it to create a custom model class, and\nproviding a URL for that model:\n\n```javascript\nvar App = RestModel.extend().reopenClass({\n  url: '/apps'\n});\n```\n\n`App` can now be used to fetch resources from the `/apps` endpoint:\n\n```javascript\n// Fetch all apps (GET /apps):\nApp.all().then(function(apps) {\n  apps[0].constructor === App;\n});\n\n// Fetch a single app (GET /apps/:id):\nApp.find(1).then(function(app) {\n  app.constructor === App;\n});\n```\n\n`App`s can also be created, fetched, updated, and destroyed:\n\n```javascript\nvar existingApp = App.create({ id: params.id });\nexistingApp.fetch().then(function(existingApp) {\n  existingApp.get('name') === 'existing-app-name';\n});\n\nvar newApp = App.create({ name: 'new-app-name' });\nnewApp.save().then(function(newApp) {\n  newApp.get('id') === 2;\n  return newApp.delete();\n}).then(function() {\n  // newApp has been deleted\n});\n```\n\n### Working with Nested Resources\n\nWorking with nested resources is as simple as providing a nested URL with\nplaceholder segments, and providing parent objects and keys for each record:\n\n```javascript\nvar Post = RestModel.extend().reopenClass({\n  url: '/posts'\n});\n\nvar Comment = RestModel.extend().reopenClass({\n  url: '/posts/:post/comments'\n});\n\nvar post = Post.create({ id: 1 });\n\nComment.all(post).then(function(comments) { // GET /posts/1/comments\n  comments[0].constructor === Comment;\n});\n\nComment.find(post, 2).then(function(comment) { // GET /posts/1/comments/2\n  comment.constructor === Comment;\n});\n\nvar comment = Comment.create({ parents: [post], body: 'hello' });\ncomment.save().then(function(comment) { // POST /posts/1/comments\n  comment.constructor === Comment;\n});\n```\n\nThere is no concept of belongs-to/has-many in RestModel. All models are managed\nindividually, and `parents` can be used on any record\u0026mdash;their primary keys\nwill be interpolated as is appropriate into the URL.\n\n### Custom Namespaces\n\nIf resources are behind a custom namespace, one can be provided via the\n`namespace` property on the class:\n\n```javascript\nvar Model = RestModel.extend().reopenClass({\n  namespace: 'api'\n});\n\nvar App = Model.extend().reopenClass({\n  url: '/apps'\n});\n\nApp.all(); // GET `/api/apps`\n```\n\n### Custom Primary Keys\n\nYour API may allow you to find resources by, for example, both `id` and `name`.\nRestModel supports this via the `primaryKeys` array property on the class, which\ndefaults to `['id']`. Any time RestModel needs a primary key to fetch or save a\nrecord, it will iterate over these keys in order until it finds one for which\nthe model has a value.\n\n```javascript\nvar App = RestModel.extend().reopenClass({\n  url: '/apps',\n  primaryKeys: ['id', 'name']\n});\n```\n\nNow, say a user visits `/apps/my-app` in your Ember app. Your route will want\nto fetch the `App` model for them, and RestModel can fetch it by `name`, since\nthat value is provided in `primaryKeys`:\n\n```javascript\nvar AppsRoute = Ember.Route.extend({\n  model: function(params) {\n    return App.create({ name: params.name }).fetch();\n  }\n});\n```\n\nAll subsequent API requests for that `App` instance will be made using `id`,\nassuming your API returns this property.\n\n### Determining If a Record Has Changed\n\nRestModel provides an `isDirty` property on each instance that returns `true` if\nattributes on the record have been changed from their original values. When a\nrecord is successfully `save()`d, the new values are considered the \"original\nvalues\".\n\nIn order to determine `isDirty`, RestModel requires that you provide an array\ncalled `attrs` that contains the attributes to dirty check against:\n\n```javascript\nvar App = RestModel.extend({\n  attrs: ['name']\n}).reopenClass({\n  url: '/apps'\n});\n\nvar app = App.create({ name: 'foo' });\napp.get('isDirty'); // false\napp.set('name', 'bar');\napp.get('isDirty'); // true\n```\n\n### Reverting a Changed Record\n\nAssuming that a record has an `attrs` array defined, it can be reverted to its\noriginal values if it has changed. Remember that changing a record and then\nsaving it will cause the record to consider its new properties its \"original\nproperties\", and it won't revert.\n\n```javascript\nvar app = App.create({ name: 'foo' });\napp.get('name'); // 'foo'\napp.set('name', 'bar');\napp.get('name'); // 'bar'\napp.revert();\napp.get('name'); // 'foo'\n```\n\n### Serializing a Record for Saving/Updating\n\nWhen a record is saved or updated, `#serialize` is called on it, which is a\nmethod that returns a JSON string. By default, this method will either serialize\nany properties in a `serializedProperties` property array on the record, or it\nwill simply call `JSON.stringify(this)` if no such property exists.\n\nThis method can be overridden easily, as long as it returns a JSON string.\n\n### Deserializing Records\n\nWhen a record is returned from the API, `::deserialize` is called on its class,\nwith the API response object as the argument. By default, this method simply\ncalls `return this.create(object)`, returning an instance of the class.\n\nWhen an array is returned, `::deserializeArray` is called on its class, with the\nAPI response array as the argument. By default, this simply returns a map of\ncalling `::deserialize` with each member of the array.\n\nThese methods can be overridden for custom API response deserialization.\n\n### Setting Custom Request Headers\n\nEach class can choose to implement a `getBeforeSend` function. This function\nshould return a function whose single argument is a jQuery XMLHttpRequest\nobject (`jqXHR`). Custom request headers can be added and removed here, as it\nwill be called on every AJAX request for this class.\n\nThe `getBeforeSend` method itself receives an object of options, including\nthings like the request method (e.g. `'GET'`) to be used by the impending AJAX\nrequest.\n\n```javascript\nvar App = RestModel.extend().reopenClass({\n  url: '/apps',\n\n  getBeforeSend: function(options) {\n    return function(jqXHR) {\n      jqXHR.setRequestHeader('foo', 'bar');\n    }\n  }\n})\n```\n\n### Caching\n\nAlthough caching is in an early state in RestModel, there is basic caching\nfunctionality, which uses `localStorage`. In order to activate it, set `cache`\nto `true` on the class:\n\n```javascript\nvar App = RestModel.extend().reopenClass({\n  cache: true,\n  url: '/apps'\n});\n```\n\nThe cache will keep a single representation of every record of that class it has\nfetched, and for every separate URL, either an array of record primary keys or\na single record primary key for that URL.\n\nOnce a request to an endpoint has been made once (and then cached), subsequent\nrequest promises will immediately resolve with the cached value. An API request\nwill be triggered in the background, and the cached value (both in the cache and\nin the record or array of records the promise was resolved with) will be\nupdated.\n\nOn endpoints that return arrays, the cache will add, update, and remove* the\nappropriate records.\n\nOn endpoints that return single objects, the cache will only update the cached\nobject.\n\nAs long as what's rendered by your Ember app is the same array or object\nreturned by a RestModel method (e.g. `::all`, `#find`), your view should render\nimmediately, and then update once the background API request completes.\n\n_*This can currently break for paginated APIs, as it is impossible to\ndetermine whether a record has been removed or simply relocated to a different\npage or range._\n\n## Building\n\nBefore a release, RestModel should be built with a non-uglified and an uglified\nversion into its `dist` directory:\n\n```sh\nnpm install\nnpm run build\n```\n\n## Testing\n\n```sh\n$ npm install\n$ bower install\n$ npm test\n```\n\n## Thanks, Heroku\n\nWhile I created and maintain this project, it was done while I was an employee\nof [Heroku][heroku] on the Human Interfaces Team, and they were kind enough to\nallow me to open source the work. Heroku is awesome.\n\n[heroku]: https://www.heroku.com/home\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjclem%2Frest-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjclem%2Frest-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjclem%2Frest-model/lists"}