@commercelayer/active-resource
Advanced tools
Comparing version 1.0.0-beta.9 to 1.0.0-beta.10
@@ -69,6 +69,5 @@ ### 0.9.0 | ||
## 1.0.0-alpha.2 | ||
## Master | ||
* Allow resources to be provided as values in `where` statements, transforming their primaryKey as value | ||
* Fix bug that left `Relation#select` field classes camelCased in requests | ||
* Use a single instance of `axios` in interfaces, so that `resourceLibrary.interface.axios.request` can be easily stubbed | ||
@@ -83,56 +82,6 @@ * Fix bug in Interfaces.JsonApi#toCamelCase that mishandles attributes with arrays of strings as the value | ||
* Add pretty print for `Base.toString()` | ||
* Fix bug that left `Relation#select` field classes camelCased in requests | ||
* Add `ResourceLibrary#createResource` that takes in class and composes it with `Base` class, calling static `define` | ||
on it to define fields directly in the class | ||
## 1.0.0-alpha.3 | ||
* Allow arrays of values/resources to be supplied as `where` values | ||
* Allow `Relation` to inherit custom class methods from the base class it starts from | ||
* Add Node.js support | ||
## 1.0.0-beta.0 | ||
* Ensure `Errors#propagate` adds full error to root resource instead of just propagating them to nested resources | ||
* Fix polymorphic relationship construction from server | ||
* Allow `null` values for where filters | ||
* Merge `includes` with `fields` in JSON:API interface so includes aren't left out when fields are used | ||
* Change README to Javascript | ||
## 1.0.0-beta.1 | ||
* Fix bug introduced by 28c8f7a | ||
## 1.0.0-beta.2 | ||
* Fix bug in `CollectionProxy#load` only loading one item when using immutable mode | ||
## 1.0.0-beta.3 | ||
* Add pagination caching to CollectionResponse | ||
## 1.0.0-beta.4 | ||
* BREAKING: Remove support for adding classes to resource library `prototype` instead of as direct object property | ||
* FIX: Allow resource library headers to be set after initialization | ||
* FEATURE: Add afterCreate callback | ||
## 1.0.0-beta.5 | ||
* Add immutable `reload` | ||
* In immutable mode, don't flag associations as loaded unless they are included in response | ||
## 1.0.0-beta.6 | ||
* Add polymorphic relationships to clone | ||
## 1.0.0-beta.7 | ||
* In immutable mode, don't flag associations as loaded if they are not defined in the response | ||
# Master | ||
* In immutable mode, fix passing down of queryParams in CollectionProxy build | ||
* Add uniq to Collection | ||
* Remove empty attributes and relationships objects from resources post/patch'd to server | ||
* Add `include` option to Reflection for adding relationship to default include queryParams | ||
* Fix bug in immutable error propagation to has many association that resulted in duplicate items on association target of clone |
{ | ||
"private": false, | ||
"name": "@commercelayer/active-resource", | ||
"description": "Persistable object relational mapping in Javascript", | ||
"main": "build/active-resource.min.js", | ||
"main": "build/active-resource.js", | ||
"author": "Nick Landgrebe", | ||
"version": "1.0.0-beta.9", | ||
"version": "1.0.0-beta.10", | ||
"homepage": "https://github.com/nicklandgrebe/activeresource.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"build": "grunt build" | ||
}, | ||
"dependencies": { | ||
@@ -14,4 +16,4 @@ "axios": "^0.21.1", | ||
"qs": "^6.10.1", | ||
"underscore": "1.13.1", | ||
"underscore.inflection": "^1.3.3", | ||
"underscore": "^1.13.1", | ||
"underscore.inflection": "latest", | ||
"underscore.string": "latest" | ||
@@ -21,16 +23,10 @@ }, | ||
"devDependencies": { | ||
"@babel/core": "^7.1.6", | ||
"@babel/preset-env": "^7.1.6", | ||
"bili": "^3.3.4", | ||
"grunt": "0.x.x", | ||
"grunt-babel": "^8.0.0", | ||
"grunt-contrib-clean": "0.5.x", | ||
"grunt-contrib-coffee": "^2.0.0", | ||
"grunt-contrib-coffee": "0.7.x", | ||
"grunt-contrib-concat": "^1.0.1", | ||
"grunt-contrib-connect": "0.4.x", | ||
"grunt-contrib-jasmine": "^2.0.0", | ||
"grunt-contrib-jasmine": "latest", | ||
"grunt-contrib-uglify": "^3.3.0", | ||
"grunt-contrib-watch": "0.5.x", | ||
"grunt-exec": "^3.0.0", | ||
"grunt-rollup": "^9.0.0", | ||
"grunt-template-jasmine-requirejs": "^0.2.3", | ||
@@ -42,9 +38,4 @@ "grunt-umd": "^2.4.0", | ||
"jquery": "^2.0.0", | ||
"moxios": "git+https://github.com/nicklandgrebe/moxios.git", | ||
"rollup-plugin-babel": "^4.0.3" | ||
}, | ||
"scripts": { | ||
"build:test": "grunt build && bili && grunt spec", | ||
"build": "grunt build" | ||
"moxios": "git+https://github.com/nicklandgrebe/moxios.git" | ||
} | ||
} |
654
README.md
@@ -1,33 +0,33 @@ | ||
# ActiveResource.js - API Resource relational mapping in Javascript | ||
Welcome to ActiveResource.js, an API resource ORM library for JavaScript. | ||
<br/> | ||
<br/> | ||
ActiveResource.js is designed to make interacting with resources stored on a RESTful server more free-flowing and holistic than simpler solutions like | ||
`Backbone` and `ngResource` or creating your own requests for each CRUD operation in your API. ActiveResource.js constructs and executes requests and formats responses into | ||
# ActiveResource.js - Object-relational mapping in Javascript | ||
Welcome to ActiveResource.js, an object relational mapping library for Javascript. ActiveResource.js is designed to make | ||
interacting with resources stored on a RESTful server more straightforward and holistic than simpler solutions like | ||
`ngResource`. ActiveResource.js constructs and executes requests and formats responses into | ||
meaningful resource representations on the client side, allowing you to perform CRUD operations, as well | ||
as interact with and modify the various relationships of resources effortlessly. | ||
<br/> | ||
<br/> | ||
as interact with and modify the various relationships (often known as associations) of resources effortlessly. | ||
ActiveResource.js is inspired heavily by [Active Record](https://github.com/rails/rails/tree/master/activerecord), the well known | ||
ORM for Ruby on Rails. In the same way that Active Record and other libraries like it make interacting with relational databases trivial in most of the | ||
use cases that might be required of a server side application, ActiveResource.js aims to make interacting with | ||
RESTful servers trivial in most of the use cases required of a client side application. | ||
<br/> | ||
<br/> | ||
ORM for Ruby on Rails. In the same way that Active Record makes interacting with relational databases trivial in most of the | ||
use cases that might be required of a server side application, ActiveResource.js hopes to make interacting with | ||
RESTful servers trivial in most of the use cases that might be required of a client side application. | ||
The library provides a base class that, when subclassed, sets up a mapping between the new class | ||
and an existing resource on the server. Resources can be connected to other resources in two ways: through client side interaction (whose behavior is defined | ||
and an existing resource scope on the server. In the context of an application, these classes are commonly referred to as | ||
models. Models can also be connected to other models in two ways: through client side interaction (whose behavior is defined | ||
by associations), and by making requests to persist the association on the server. | ||
<br/> | ||
<br/> | ||
ActiveResource.js relies heavily on naming in that it uses class and association names to establish mappings between | ||
respective resource endpoints, and nested/related resource endpoints. Although these mappings can be defined | ||
respective resource endpoints, subresource endpoints, and foreign key properties. Although these mappings can be defined | ||
explicitly, it's recommended to follow naming conventions, especially when getting started with the library. | ||
<br/> | ||
<br/> | ||
An introduction to ActiveResource.js can be found on the Toptal Engineering Blog at https://www.toptal.com/api-developers/fast-powerful-js-sdks-for-rest-apis . | ||
* * * | ||
WARNING: ActiveResource.js currently only works in browsers. A new release will soon allow it to work in Node, plus change some design decisions | ||
to use more universal idioms. | ||
* * * | ||
## Installation | ||
``` | ||
yarn add active-resource | ||
yard add active-resource | ||
``` | ||
@@ -39,123 +39,110 @@ | ||
```javascript | ||
// /resources/APILibrary.js | ||
import { createResourceLibrary } from 'active-resource'; | ||
```coffee | ||
# lib/myLibrary.coffee | ||
export default createResourceLibrary( | ||
'https://example.com/api/v1/', // base url for your server | ||
headers: { Authorization: 'Bearer ...' } | ||
); | ||
MyLibrary = ActiveResource.createResourceLibrary( | ||
'https://example.com/api/v1/', # base url for your server | ||
headers: { Authorization: 'Basic ...' } | ||
) | ||
``` | ||
Then, you create a resource class for each resource in your library using `library.createResource`: | ||
Then, you create a resource class for each resource in your library: | ||
```javascript | ||
// /resources/Product.js | ||
import APILibrary from './APILibrary'; | ||
```coffee | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
/* ... */ | ||
} | ||
} | ||
# lib/myLibrary/product.coffee | ||
export default APILibrary.createResource(Product); | ||
class MyLibrary.Product extends MyLibrary.Base | ||
this.className = 'Product' | ||
this.queryName = 'products' | ||
# **or** | ||
class MyLibrary::Product extends MyLibrary.Base | ||
this.className = 'Product' | ||
this.queryName = 'products' | ||
``` | ||
If you use a minification library that changes the names of classes, it is recommended that you set `this.className` inside `define`. See [configuration section of this page](#config) for more details. | ||
Both `className` and `queryName` are required, and you can see what they do in the [configuration section of this page](#config). | ||
## Features | ||
Some of the major features include: | ||
* * * | ||
### Automated mapping between classes and endpoints, attributes and relationships | ||
* Automated mapping between classes and endpoints, attributes and relationships | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
/* ... */ | ||
} | ||
} | ||
export default APILibrary.createResource(Product); | ||
```coffee | ||
class MyLibrary.Product extends MyLibrary.Base | ||
this.className = 'Product' | ||
this.queryName = 'products' | ||
``` | ||
The Product class is automatically mapped to the RESTful endpoints for product resources on the server with the URL: | ||
The Product class is automatically mapped to the RESTful endpoints for product resources on the server, which would all have the URL: | ||
``` | ||
https://example.com/api/v1/products/ | ||
http://example.com/api/v1/products/ | ||
``` | ||
Create an index file for all your resources so that `createResource` will be called on all of them at the same time: | ||
* * * | ||
```javascript | ||
// /resources/index.js | ||
* HTTP requests constructable through simple to use chained method calls (a `Relation`) | ||
import Product from './Product'; | ||
```coffee | ||
Product.where(title: 'A product title').includes('orders').order(createdAt: 'desc').all() | ||
Product.select('title').first(5) | ||
Product.page(2).perPage(1).all() | ||
export { | ||
Product | ||
} | ||
``` | ||
Product.limit(2).offset(2).all() | ||
If you don't call `createResource` on a resource class, your ResourceLibrary will not know it exists, and will fail to create relationships between that resource | ||
and other resources that have had `createResource` called on them. | ||
Product.find(1) | ||
Product.findBy(title: 'A product title') | ||
* * * | ||
Product.each (p) -> | ||
... | ||
### HTTP requests constructable through simple to use chained relation methods | ||
Product.includes('orders', merchant: ['currency']).all() | ||
``` | ||
```javascript | ||
await Product.where({ title: 'A product title' }).includes('orders').order({ createdAt: 'desc' }).all(); | ||
await Product.select('title').first(5); | ||
await Product.page(2).perPage(1).all(); | ||
Method calls like `all()` will return a promise, and the response in the promise will be an `ActiveResource::Collection` (see below). If the response is expected to be a single resource (`find`, `findBy`, `first`) it will just be that resource. | ||
await Product.limit(2).offset(2).all(); | ||
await Product.find(1); | ||
await Product.findBy({ title: 'A product title' }); | ||
await Product.each((p) => console.log(p)); | ||
await Product.includes('orders', { merchant: ['currency'] }).all() | ||
**Note:** Due to the current design of JSON API, if you use both `select` and `includes` in the same `Relation` chain, you should add any `includes` to `select`. For example: | ||
```coffee | ||
Product.includes('merchant').select('title').all() | ||
``` | ||
should be: | ||
```coffee | ||
Product.includes('merchant').select('title','merchant').all() | ||
``` | ||
This is because the JSON API spec defines both attributes and relationships as `fields`, which is what the `select` method constructs. So | ||
if you want to include a relationship and you also plan on `select`ing fields, make sure that you specify any includes as a field using | ||
`select`. This does not apply if you only want to use `includes` without `select`. | ||
Method calls like `all()` will return a promise, and the response to the promise will be an `ActiveResource.Collection` (see [below](#collection)). | ||
If the response is expected to be a single resource (`find`, `findBy`, `first`) it will just be that resource. | ||
* * * | ||
### Persistence methods that simplify managing of resources | ||
* Persistence methods that simplify managing of resources | ||
```javascript | ||
product = Product.build({ title: 'A product title' }) | ||
product.save(() => { | ||
if(product.valid()) { | ||
product.persisted() // true | ||
} else { | ||
product.errors().empty() // false | ||
} | ||
}) | ||
```coffee | ||
product = Product.build(title: 'A product title') | ||
product.save -> | ||
if product.valid() | ||
product.persisted() # == true | ||
else | ||
product.errors().empty() # == false | ||
Product.create({ title: 'A product title' }, (product) => { | ||
if(product.valid()) { | ||
/* ... */ | ||
} else { | ||
product.newRecord() // true | ||
} | ||
}) | ||
Product.create title: 'A product title', (product) -> | ||
if product.valid() | ||
... | ||
else | ||
product.newRecord() # == true | ||
Product.first() | ||
.then((product) => { | ||
product.update({ title: 'A new title' }) | ||
}) | ||
.then (product) -> | ||
product.update title: 'A new title' | ||
Product.first() | ||
.then((product) => { | ||
.then (product) -> | ||
product.destroy() | ||
}) | ||
Product.first() | ||
.then((product) => { | ||
.then (product) -> | ||
product.reload() | ||
}) | ||
``` | ||
@@ -165,95 +152,100 @@ | ||
### Associations between objects defined by simple class methods | ||
* Associations between objects defined by simple class methods | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('orders') | ||
} | ||
} | ||
```coffee | ||
class MyLibrary.Product extends MyLibrary.Base | ||
@hasMany 'orders' | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.belongsTo('product') | ||
} | ||
} | ||
class MyLibrary.Order extends MyLibrary.Base | ||
@belongsTo 'product' | ||
``` | ||
This defines a number of methods on each class. For `hasMany`: | ||
```javascript | ||
```coffee | ||
product = Product.build() | ||
product.orders() // collection proxy to use for more queries (see below) | ||
product.orders().build() // local construction | ||
await product.orders().create() // persisted construction | ||
await product.orders().assign() // persisted assignment | ||
await product.orders().push() // persisted concatenation | ||
await product.orders().delete() // persisted deletion of association (not the resources themselves) | ||
await product.orders().deleteAll() | ||
await product.orders().reload() | ||
product.orders().target() // currently loaded collection | ||
product.orders().toArray() // currently loaded collection as array | ||
product.orders().empty() // Whether or not the current target is empty | ||
product.orders().size() // The size of the current target | ||
product.orders() # collection proxy to use for more queries (see below) | ||
product.orders().toArray() # read | ||
product.orders().build() # local construction | ||
product.orders().create() # persisted construction | ||
product.orders().assign() # persisted assignment | ||
product.orders().push() # persisted concatenation | ||
product.orders().delete() # persisted deletion of association (not the resources themselves) | ||
product.orders().deleteAll() | ||
product.orders().reload() | ||
product.orders().empty() # NOTE: Only indicates if the collection currently loaded is empty | ||
product.orders().size() # NOTE: Only gives the size of the collection currently loaded | ||
``` | ||
In regards to association collection proxies, you can work off them just like you would any other ActiveResource relation: | ||
```javascript | ||
product.orders().where({ title: 'A product title' }).select('title').last(10) | ||
.then((orders) => { | ||
// orders related to the product | ||
}) | ||
In regards to association collection proxies, you can work off them just like you would any other ActiveResource `Relation` class: | ||
```coffee | ||
product.orders().where(title: 'A product title').select('title').last(10) | ||
.then (orders) -> | ||
# result will only be orders related to `product` | ||
product.orders().includes('merchant').create({ title: 'A product title' }, (order) => { | ||
if(order.valid()) { | ||
order.merchant() // included in response | ||
} else { | ||
product.orders().includes('merchant').create title: 'A product title', (order) -> | ||
if order.valid() | ||
order.merchant() # != null, was included in response | ||
else | ||
order.errors() | ||
} | ||
}) | ||
``` | ||
None of the `hasMany` methods above will assign the actual `target()` of the association to their result, | ||
It is important to note that none of the `hasMany` methods above will assign the actual target of the association to their result, | ||
nor will the association be considered "loaded." For example: | ||
```javascript | ||
product.orders().where({ title: 'A product title' }).select('title').last(10) | ||
.then((orders) => { | ||
orders // not empty | ||
product.orders().target() // empty | ||
product.association('orders').loaded() // false | ||
}) | ||
```coffee | ||
product.orders().where(title: 'A product title').select('title').last(10) | ||
.then (orders) -> | ||
orders # != [] | ||
product.orders().toArray() # == [] | ||
product.association('orders').loaded() # == false | ||
``` | ||
To accomplish this, one must `load` the association either in the initial query, or at some later point in time: | ||
```javascript | ||
To accomplish this, one must *load* the association either in the initial query, or at some later point in time: | ||
```coffee | ||
Product.includes('orders').first() | ||
.then((product) => { | ||
product.orders().target() // not empty | ||
product.association('orders').loaded() // true | ||
}); | ||
.then (product) -> | ||
product.orders().toArray() # != [] | ||
product.association('orders').loaded() # == true | ||
Product.first() | ||
.then((product) => { | ||
product.association('orders').loaded() // false | ||
.then (product) -> | ||
product.association('orders').loaded() # == false | ||
product.orders().load() | ||
.then(() => { | ||
product.association('orders').loaded() // true | ||
}); | ||
}); | ||
product.loadOrders() | ||
.then -> | ||
product.association('orders').loaded() # == true | ||
product.orders().loadTarget() | ||
.then -> | ||
product.association('orders').loaded() # == true | ||
product.orders().reload() | ||
.then -> | ||
product.association('orders').loaded() # == true | ||
``` | ||
There are a number of methods defined for singular associations (`hasOne`, `belongsTo`) as well: | ||
**In general, it is best to include every association you'll need to do your business in the very first query.** | ||
```javascript | ||
order = Order.build() | ||
order.product() // read locally | ||
await order.loadProduct() // read persisted | ||
It is also worth noting that most `Relation` methods (and association `Relation` methods here) will return promises, and do not | ||
hit any sort of cache. If you want to make a synchronous method call that gives you the current target of the association (loaded or not), you have a few options: | ||
order.assignProduct() // assign locally | ||
await order.updateProduct() // persist assignment | ||
```coffee | ||
Product.includes('orders').first() | ||
.then (product) -> | ||
product.orders().all(cached: true) | ||
order.buildProduct() // local construction | ||
await order.createProduct() // persist construction | ||
product.orders().toArray() | ||
``` | ||
There are a number of methods defined for singular associations (`hasOne`, `belongsTo`) as well: | ||
```coffee | ||
order = Order.build() | ||
order.product() # read locally | ||
order.loadProduct() # read persisted | ||
order.assignProduct() # assign locally | ||
order.updateProduct() # persist assignment | ||
order.buildProduct() # local construction | ||
order.createProduct() # persist construction | ||
``` | ||
**You should never make a direct assignment like `product=`, because ActiveResource is not aware when this happens and it may cause unexpected results.** | ||
@@ -263,11 +255,11 @@ | ||
### Reflections on associations | ||
* Reflections on associations | ||
```javascript | ||
let reflection = Order.reflectOnAssociation('product'); | ||
reflection.name // 'product' | ||
reflection.macro // 'belongsTo' | ||
reflection.klass() // Product | ||
```coffee | ||
Order.reflectOnAllAssociations().each (reflection) -> | ||
reflection.name # == 'product' | ||
reflection.macro # == 'belongsTo' | ||
reflection.klass() # == Product | ||
Order.reflectOnAllAssociations().each((reflection) => { /* ... */ }); | ||
Order.reflectOnAssociation('product') | ||
``` | ||
@@ -277,15 +269,12 @@ | ||
### Attribute management | ||
* Attribute management | ||
```javascript | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.attributes('price', 'quantity') | ||
} | ||
} | ||
```coffee | ||
class MyLibrary.Order extends MyLibrary.Base | ||
@attributes('price', 'quantity') | ||
order = Order.build() | ||
order.assignAttributes({ price: 5.0 }) | ||
order.attributes() // { price: 5.0 } | ||
order.assignAttributes(price: 5.0) | ||
order.attributes() # == { price: 5.0 } | ||
``` | ||
@@ -295,4 +284,5 @@ | ||
### Change tracking | ||
* Change tracking | ||
Defining `attributes` on resource classes allows changes to those attributes to be tracked, as will | ||
@@ -304,14 +294,13 @@ relationships defined using `hasMany`, `belongsTo`, etc. | ||
```javascript | ||
```coffee | ||
Order.find(1) | ||
.then((order) => { | ||
order.price // 5.0 | ||
order.quantity // 2 | ||
order.price = 10.0; | ||
order.changedFields().toArray() // ['price'] | ||
order.save() // only sends +price+ to server | ||
}); | ||
.then (order) => | ||
order.price # == 5.0 | ||
order.quantity # == 2 | ||
order.price = 10.0 | ||
order.changedFields().toArray() # => ['price'] | ||
order.save # only sends change to price to server | ||
``` | ||
@@ -321,53 +310,63 @@ | ||
### Typing | ||
* Better typing, constantizing, module mixins than Javascript alone | ||
```javascript | ||
Order.build().isA(Order) // true | ||
Order.build().isA(Product) // false | ||
```coffee | ||
Order.build().isA(Order) # == true | ||
Order.build().isA(Product) # == false | ||
Order.build().klass() // Order | ||
Order.build().klass() # == Order | ||
``` | ||
* * * | ||
```coffee | ||
class MyLibrary.Order extends MyLibrary.Base | ||
this.className = 'Order' | ||
<a name="collection"></a> | ||
MyLibrary.constantize('Order') # == MyLibrary.Order | ||
``` | ||
### Collections | ||
```coffee | ||
class MyModule | ||
@method1: -> | ||
```javascript | ||
let collection = ActiveResource.Collection.build([product1, product2]) | ||
class Order extends MyLibrary.Base | ||
ActiveResource.extend(this, MyModule) | ||
collection.all() | ||
collection.toArray() | ||
collection.size() | ||
collection.empty() | ||
collection.include(item) | ||
collection.first(n) | ||
collection.last(n) | ||
collection.each((i) => /* ... */) | ||
collection.map((i) => /* ... */) | ||
collection.inject({}, (memo, iterator) => /* ... */) | ||
collection.compact() | ||
collection.flatten() | ||
collection.join() | ||
collection.push(items) | ||
collection.delete(items) | ||
collection.clear() | ||
collection.select((i) => /* ... */) | ||
collection.detect((i) => /* ... */) | ||
Order.method1 # defined | ||
class Product extends MyLibrary.Base | ||
ActiveResource.include(this, MyModule) | ||
Order.build().method1 # defined | ||
``` | ||
`Collection` is returned from requests for collections such as `all()` and `hasManyAssociation.target()`. | ||
* * * | ||
### Pagination | ||
* A wrapper class for Array that is similar to Ruby Array functionality | ||
```javascript | ||
Order.perPage(10).all() | ||
.then(async (orders) => { | ||
if(orders.hasNextPage()) await orders.nextPage(); | ||
```coffee | ||
c = ActiveResource::Collection.build([product1, product2]) | ||
if(orders.hasPrevPage()) await orders.prevPage(); | ||
}) | ||
c.all() | ||
c.toArray() | ||
c.size() | ||
c.empty() | ||
c.include(item) | ||
c.first(n) | ||
c.last(n) | ||
c.each (i) -> ... | ||
c.inject {}, (h, i) -> ... | ||
c.map (i) -> ... | ||
c.compact() | ||
c.flatten() | ||
c.join() | ||
c.push(items) | ||
c.delete(items) | ||
c.clear() | ||
c.select (i) -> ... | ||
c.detect (i) -> ... | ||
``` | ||
These make use of Underscore.js. [Learn more](http://docs.ruby-lang.org/en/2.0.0/Array.html) | ||
This is the class that will be returned from `Relation#all()`, etc. | ||
* * * | ||
@@ -377,7 +376,7 @@ | ||
### Configuration | ||
* Customization through properties/options on the library itself, as well as base classes and associations: | ||
#### `ResourceLibrary.baseUrl` | ||
```javascript | ||
```coffee | ||
ActiveResource.createResourceLibrary( | ||
@@ -392,9 +391,6 @@ 'http://example.com/api/v1' | ||
```javascript | ||
```coffee | ||
ActiveResource.createResourceLibrary( | ||
'http://example.com/api/v1', | ||
{ | ||
headers: { | ||
'Authorization': 'Bearer [TOKEN]' | ||
} | ||
headers: { | ||
'Authorization': 'Basic xxx' | ||
} | ||
@@ -409,27 +405,20 @@ ) | ||
```javascript | ||
let MyLibrary = ActiveResource.createResourceLibrary( | ||
'http://example.com/api/v1', | ||
{ | ||
constantizeScope: window | ||
} | ||
```coffee | ||
MyLibrary = ActiveResource.createResourceLibrary( | ||
constantizeScope: window | ||
) | ||
MyLibrary.createResource( | ||
class Product extends MyLibrary.Base {} | ||
) | ||
class window.Product extends MyLibrary.Base | ||
this.className = 'Product' | ||
window.Product // defined | ||
MyLibrary.constantize('Product') # == window.Product | ||
``` | ||
*This property is optional, and defaults to null.* It specifies the object to assign classes to. If null, classes will be added as properties to your `ResourceLibrary`. | ||
*This property is optional, and defaults to null.* It specifies the object to search properties of when looking up a class name to find a class constant. If null, ActiveResource will search both `MyLibrary` and `MyLibrary::`, if your resource library variable were named `MyLibrary`. | ||
#### `ResourceLibrary.interface` | ||
```javascript | ||
```coffee | ||
ActiveResource.createResourceLibrary( | ||
'http://example.com/api/v1', | ||
{ | ||
interface: MyCustomInterface | ||
} | ||
interface: MyCustomInterface | ||
) | ||
@@ -440,92 +429,67 @@ ``` | ||
a server and ActiveResource, constructing requests from input data, and constructing objects from response data. Right now, the only | ||
interface that is supported internally is `JsonApi`, which is in accordance with the [JSON API specification](http://jsonapi.org/). | ||
interface that is supported is `JsonApi`, which is in accordance with the [JSON API specification](http://jsonapi.org/). | ||
You can create your own custom interface if your API adheres to its own standard by extending `ActiveResource.Interfaces.Base`. | ||
#### `Base.className` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static className = 'Product'; | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
this.className = 'Product' | ||
``` | ||
**This property is optional.** It is so the library will continue to work in minified environments, where a call to `constructor.name` might yield a random result instead of the intended class name. | ||
**This property is required.** It is so the library will continue to work in minified environments, where a call to `constructor.name` might yield a random result instead of the intended class name. | ||
#### `Base.queryName` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static queryName = 'products'; | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
this.queryName = 'products' | ||
``` | ||
**This property is optional.** This is the name that will be used in URLs, so a call like `Product.all()` will result in an HTTP request `GET /api/v1/products`. Defaults to the pluralized form of `className` above. | ||
**This property is required.** This is the name that will be used in URLs, so a call like `Product.all()` will result in an HTTP request `GET /api/v1/products` | ||
#### `Base.primaryKey` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static primaryKey = 'token'; | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
this.primaryKey = 'token' | ||
``` | ||
*This property is optional.* It tells ActiveResource which attribute is the primaryKey of the resource. | ||
*This property is optional.* It tells ActiveResource which property in a response object is the primaryKey of the resource being returned, | ||
as well as telling ActiveResource which key to use when making foreign key assignments | ||
#### `Association.className` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('specialOrders', { className: 'Order' }) | ||
} | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
@hasMany 'specialOrders', className: 'Order' | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.belongsTo('product') | ||
} | ||
} | ||
class Order extends MyLibrary.Base | ||
@belongsTo 'product' | ||
``` | ||
This option allows you to name an association by one name, but have that association refer to an existing class of a different name. | ||
This option allows you to name an association by one name, but have that association refer to an existing class of a different name | ||
#### `Association.as` && `Association.polymorphic` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('orders', { as: 'resource', inverseOf: 'resource' }) | ||
} | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
@hasMany 'orders', as: 'resource' | ||
class Service extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('orders', { as: 'resource', inverseOf: 'resource' }) | ||
} | ||
} | ||
class Store extends MyLibrary.Base | ||
@hasMany 'orders', as: 'resource' | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.belongsTo('resource', { polymorphic: true, inverseOf: 'orders' }) | ||
} | ||
} | ||
class Order extends MyLibrary.Base | ||
@belongsTo 'resource', polymorphic: true | ||
``` | ||
These options work together to allow for polymorphic associations between models. See `inverseOf` explanation below. | ||
These options work together to allow for polymorphic associations between models. | ||
#### `Association.inverseOf` | ||
```javascript | ||
class Product extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('orders', { inverseOf: 'product' }) | ||
} | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
@hasMany 'orders', inverseOf: 'product' | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.belongsTo('product', { inverseOf: 'product' }) | ||
} | ||
} | ||
class Order extends MyLibrary.Base | ||
@belongsTo 'product', inverseOf: 'orders' | ||
``` | ||
@@ -536,25 +500,49 @@ | ||
#### `Association.autosave` | ||
#### `Association.foreignKey` | ||
```javascript | ||
class Order extends MyLibrary.Base { | ||
static define() { | ||
this.hasMany('orderItems', { autosave: true }) | ||
} | ||
} | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
@hasMany 'orders', foreignKey: 'ownerProductId' | ||
class OrderItem extends MyLibrary.Base { | ||
static define() { | ||
this.belongsTo('order') | ||
} | ||
} | ||
class Order extends MyLibrary.Base | ||
@belongsTo 'ownerProduct', className: 'Product' | ||
let order = Order.build({ orderItems: [OrderItem.build({ amount: 5.0 })] }); | ||
await order.save() // sends orderItems attributes to server too | ||
Product.first() | ||
.then (product) -> | ||
order = product.orders().build() # order.ownerProductId == product.id | ||
``` | ||
This option allows you to specify that associated resources(s) of a resource should be saved with the resource itself. | ||
This option allows you to define the foreign key that is set on a child association (`belongsTo`) when assignments/constructions are | ||
made. | ||
## Sponsors | ||
#### `Association.primaryKey` | ||
[![Occasion](https://www.getoccasion.com/wp-content/uploads/2016/01/Occasion-Logo-Black_Web1.png)](https://www.getoccasion.com) | ||
```coffee | ||
class Product extends MyLibrary.Base | ||
@hasMany 'orders', primaryKey: 'token', foreignKey: 'ownerProductId' | ||
class Order extends MyLibrary.Base | ||
@belongsTo 'ownerProduct', className: 'Product' | ||
Product.first() | ||
.then (product) -> | ||
order = product.orders().build() # order.ownerProductId == product.token | ||
``` | ||
This option allows you to define the primary key of the parent association that is assigned as the foreign key to the child association | ||
when assignments/constructions are made. | ||
#### `Association.autosave` | ||
```coffee | ||
class Order extends MyLibrary.Base | ||
@hasMany 'orderItems', autosave: true | ||
class OrderItem extends MyLibrary.Base | ||
@belongsTo 'order' | ||
order = Order.build(orderItems: [OrderItem.build(amount: 5.0)]) | ||
order.save() # sends orderItems attributes to server too | ||
``` | ||
This option allows you to specify that associated object(s) of a resource should be saved when the resource itself is saved. |
@@ -63,12 +63,2 @@ { | ||
} | ||
}, | ||
"payment_source": { | ||
"data": { | ||
"id": "1", | ||
"type": "payment_methods" | ||
}, | ||
"links": { | ||
"self": "https://example.com/api/v1/orders/1/relationships/customer/", | ||
"related": "https://example.com/api/v1/orders/1/customer/" | ||
} | ||
} | ||
@@ -110,14 +100,4 @@ }, | ||
} | ||
}, | ||
{ | ||
"id": "1", | ||
"type": "payment_methods", | ||
"attributes": { | ||
"value": 10.0 | ||
}, | ||
"links": { | ||
"self": "https://example.com/api/v1/comments/1/" | ||
} | ||
} | ||
] | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
15
98
2
994111
4512
537
1
+ Addedunderscore@1.13.7(transitive)
- Removedunderscore@1.13.1(transitive)
Updatedunderscore@^1.13.1
Updatedunderscore.inflection@latest