Redux-ORM
## Installation
```bash
npm install redux-orm --save
```
Or with a script tag exposing a global called `ReduxOrm`:
```html
```
* [Latest browser build (minimized)](https://unpkg.com/redux-orm/dist/redux-orm.min.js)
* [Source Map](https://unpkg.com/redux-orm/dist/redux-orm.min.js.map)
* [Latest browser build](https://unpkg.com/redux-orm/dist/redux-orm.js) (only use if size does not matter)
### Polyfill
Redux-ORM uses some ES2015+ features, such as `Set`. If you are using Redux-ORM in a pre-ES2015+ environment, you should load a polyfill like [`babel-polyfill`](https://babeljs.io/docs/usage/polyfill/) before using Redux-ORM.
### Extensions
* [`redux-orm-proptypes`](https://github.com/tommikaikkonen/redux-orm-proptypes): React PropTypes validation and defaultProps mixin for Redux-ORM Models
## Usage
For a detailed walkthrough see [a guide to creating a simple app with Redux-ORM](https://github.com/tommikaikkonen/redux-orm-primer). Its not up-to-date yet but the [code has a branch for version 0.9](https://github.com/tommikaikkonen/redux-orm-primer/tree/migrate_to_0_9). The Redux docs have a [short section](https://redux.js.org/recipes/structuring-reducers/updating-normalized-data#redux-orm) on Redux-ORM as well.
### Declare Your Models
You can declare your models with the ES6 class syntax, extending from `Model`. You need to declare all your non-relational fields on the Model, and declaring all data fields is recommended as the library doesn't have to redefine getters and setters when instantiating Models. Redux-ORM supports one-to-one and many-to-many relations in addition to foreign keys (`oneToOne`, `many` and `fk` imports respectively). Non-related properties can be accessed like in normal JavaScript objects.
```
…
```
### Register Models and Generate an Empty Database State
Defining fields on a Model specifies the table structure in the database for that Model. In order to generate a description of the whole database's structure, we need a central place to register all Models we want to use.
An instance of the ORM class registers Models and handles generating a full schema from all the models and passing that information to the database. Often you'll want to have a file where you can import a single ORM instance across the app, like this:
```javascript
// orm.js
import { ORM } from 'redux-orm';
import { Book, Author, Publisher } from './models';
const orm = new ORM({
stateSelector: state => state.orm,
});
orm.register(Book, Author, Publisher);
export default orm;
```
You could also define *and* register the models to an ORM instance in the same file, and export them all.
Now that we've registered Models, we can generate an empty database state. Currently that's a plain, nested JavaScript object that is structured similarly to relational databases.
```javascript
// index.js
import orm from './orm';
const emptyDBState = orm.getEmptyState();
```
### Applying Updates to the Database
When we have a database state, we can start an ORM session on that to apply updates. The ORM instance provides a `session` method that accepts a database state as it's sole argument, and returns a Session instance.
```javascript
const session = orm.session(emptyDBState);
```
Session-specific classes of registered Models are available as properties of the session object.
```javascript
const Book = session.Book;
```
Models provide an interface to query and update the database state.
```javascript
Book.withId(1).update({ name: 'Clean Code' });
Book.all().filter(book => book.name === 'Clean Code').delete();
Book.idExists(1)
// false
```
The initial database state is not mutated. A new database state with the updates applied can be found on the `state` property of the Session instance.
```javascript
const updatedDBState = session.state;
```
## Redux Integration
To integrate Redux-ORM with Redux at the most basic level, you can define a reducer that instantiates a session from the database state held in the Redux state slice, then when you've applied all of your updates, you can return the next state from the session.
```
…
```
Previously we advocated for reducers specific to Models by attaching a static `reducer` function on the Model class. If you want to define your update logic on the Model classes, you can specify a `reducer` static method on your model which accepts the action as the first argument, the session-specific Model as the second, and the whole session as the third.
```
…
```
To get a reducer for Redux that calls these `reducer` methods:
```javascript
import { createReducer } from 'redux-orm';
import orm from './orm';
const reducer = createReducer(orm);
```
This reducer needs to be hooked into your Redux store. Make sure that the key under which you store it is also the key that you use to retrieve the ORM's state in its `stateSelector`. Otherwise selectors won't work properly.
`createReducer` is really simple, so we'll just paste the source here.
```javascript
function createReducer(orm, updater = defaultUpdater) {
return (state, action) => {
const session = orm.session(state || orm.getEmptyState());
updater(session, action);
return session.state;
};
}
function defaultUpdater(session, action) {
session.sessionBoundModels.forEach(modelClass => {
if (typeof modelClass.reducer === 'function') {
modelClass.reducer(action, modelClass, session);
}
});
}
```
As you can see, it just instantiates a new Session, loops through all the Models in the session, and calls the `reducer` method if it exists. Then it returns the new database state that has all the updates applied.
### Use with React
Use memoized selectors to make queries into the state. Redux-ORM uses smart memoization: the below selector accesses `Author` and `AuthorBooks` branches (`AuthorBooks` is a many-to-many branch generated from the model field declarations), and the selector will be recomputed only if those branches change. The accessed branches are resolved on the first run.
```
…
```
Selectors created with `createSelector` can be used as input to any additional `reselect` selectors you want to use. They are also great to use with `redux-thunk`: get the whole state with `getState()`, pass the ORM branch to the selector, and get your results. A good use case is serializing data to a custom format for a 3rd party API call.
Because selectors are memoized, you can use pure rendering in React for performance gains.
```jsx
// components.js
import React from 'react';
import { authorSelector } from './selectors';
import { connect } from 'react-redux';
function AuthorList({ authors }) {
const items = authors.map(author => (
{author.name} has written {author.books.join(', ')}
));
return (
);
}
function mapStateToProps(state) {
return {
authors: authorSelector(state),
};
}
export default connect(mapStateToProps)(AuthorList);
```
## Understanding Redux-ORM
### An ORM?
Well, yeah. Redux-ORM deals with related data, structured similar to a relational database. The database in this case is a simple JavaScript object database.
### Why?
For simple apps, writing reducers by hand is alright, but when the number of object types you have increases and you need to maintain relations between them, things get hairy. ImmutableJS goes a long way to reduce complexity in your reducers, but Redux-ORM is specialized for relational data.
### Immutability
Say we start a session from an initial database state situated in the Redux atom, update the name of a certain book.
First, a new session:
```javascript
import { orm } from './models';
const dbState = store.getState().orm; // getState() returns the Redux state
const sess = orm.session(dbState);
```
The session maintains a reference to a database state. We haven't
updated the database state, therefore it is still equal to the original
state.
```javascript
sess.state === dbState
// true
```
Let's apply an update.
```javascript
const book = sess.Book.withId(1)
book.name // 'Refactoring'
book.name = 'Clean Code'
book.name // 'Clean Code'
sess.state === dbState
// false
```
The update was applied, and because the session does not mutate the original state, it created a new one and swapped `sess.state` to point to the new one.
Let's update the database state again through the ORM.
```javascript
// Save this reference so we can compare.
const updatedState = sess.state;
book.name = 'Patterns of Enterprise Application Architecture';
sess.state === updatedState
// true. If possible, future updates are applied with mutations. If you want
// to avoid making mutations to a session state, take the session state
// and start a new session with that state.
```
If possible, future updates are applied with mutations. In this case, the database was already mutated, so the pointer doesn't need to change. If you want to avoid making mutations to a session state, take the session state and start a new session with that state.
### Customizability
Just like you can extend `Model`, you can do the same for `QuerySet` (customize methods on Model instance collections). You can also specify the whole database implementation yourself (documentation pending).
### Caveats
The ORM abstraction will never be as performant compared to writing reducers by hand, and adds to the build size of your project. If you have very simple data without relations, Redux-ORM may be overkill. The development convenience benefit is considerable though.
## API
### ORM
See the full documentation for ORM [here](https://redux-orm.github.io/redux-orm/api/ORM)
#### Instantiation:
```javascript
const orm = new ORM({
stateSelector: state => state.orm, // wherever the reducer is put during createStore
});
```
#### Instance methods:
* `register(...models: Array)`: registers Model classes to the `ORM` instance.
* `session(state: any)`: begins a new `Session` with `state`.
### Redux Integration
* `createReducer(orm: ORM)`: returns a reducer function that can be plugged into Redux. The reducer will return the next state of the database given the provided action. You need to register your models before calling this.
* `createSelector(orm: ORM, [...inputSelectors], selectorFunc)`: returns a memoized selector function for `selectorFunc`. `selectorFunc` receives `session` as the first argument, followed by any inputs from `inputSelectors`. Note that the first inputSelector must return the db-state to create a session from. Read the full documentation for details.
### Model
See the full documentation for `Model` [here](https://redux-orm.github.io/redux-orm/api/Model).
**Instantiation**: Don't instantiate directly; use the class methods `create` and `upsert` as documented below.
**Class Methods**:
* `withId(id)`: gets the Model instance with id `id`.
* `idExists(id)`: returns a boolean indicating if an entity with id `id` exists in the state.
* `exists(matchObj)`: returns a boolean indicating if an entity whose properties match `matchObj` exists in the state.
* `get(matchObj)`: gets a Model instance based on matching properties in `matchObj` (if you are sure there is only one matching instance).
* `create(props)`: creates a new Model instance with `props`. If you don't supply an id, the new `id` will be `Math.max(...allOtherIds) + 1`.
* `upsert(props)`: either creates a new Model instance with `props` or, in case an instance with the same id already exists, updates that one - in other words it's **create or update** behaviour.
You will also have access to almost all [QuerySet instance methods](https://redux-orm.github.io/redux-orm/api/QuerySet) from the class object for convenience, including `where` and the like.
#### Instance Attributes:
* `ref`: returns a direct reference to the p