Skip to content

0.9 Migration Guide

Tommi Kaikkonen edited this page Dec 19, 2016 · 2 revisions

Redux-ORM 0.9 Migration Guide

This release has a number of breaking changes that greatly simplify the API and internals, clarify naming and eliminate some significant problems users have.

The breaking changes are big. However, the vastly simplified internals should make it far easier for other people to contribute to the code, and anyone to debug the problems they might experience using the library. Breaking changes suck, and to make the job a bit easier I wrote a migration guide and added informative errors and deprecation warnings to the codebase where possible. They will be removed in the next minor version.

The largest changes are eager updates, lazy QuerySets, decoupled Redux integration API and mandatory optional but highly recommended non-relationship attribute definitions.

In addition to this guide, it's informative to look at the diff for migrating the Redux-ORM Primer repo to 0.9.

Here are the migration steps.

  • Rename Schema to ORM (See reasoning), use ORM#session(state) instead of ORM#from(state[, action]), ORM#mutableSession(state) instead of ORM#withMutations(state). Use ORM#getEmptyState() instead of ORM#getDefaultState(). These steps have deprecation and removal warnings in the codebase.

    Old 0.8

    import { Schema } from 'redux-orm';
    import { Book, Author } from './models';
    
    const schema = new Schema();
    schema.register(Book, Author);
    
    const initialState = schema.getDefaultState();
    const session = schema.from(initialState);
    
    session.Book.create({ title: 'My Book', author: session.Author.first() });
    
    // Updates were not applied until `getNextState` was called.
    expect(session.Book.filter({ title: 'My Book' }).exists()).to.be.false;
    
    const nextState = session.getNextState();
    
    const mutableSession = schema.withMutations(nextState);
    
    mutableSession.Book.filter({ title: 'My Book' }).delete();
    
    const finalState = mutableSession.getNextState();

    New 0.9

    import { ORM } from 'redux-orm'; // ORM instead of Schema
    import { Book, Author } from './models';
    
    const orm = new ORM();
    orm.register(Book, Author);
    
    const initialState = orm.getEmptyState(); // getDefaultState -> getEmptyState
    const session = orm.session(initialState); // .session instead of .from
    
    session.Book.create({ title: 'My Book', author: session.Author.first() });
    
    // Updates are applied eagerly.
    expect(session.Book.filter({ title: 'My Book' }).exists()).to.be.true; // Now true
    
    const nextState = session.state; // Access .state instead of calling .getNextState()
    
    const mutableSession = orm.mutableSession(nextState); // .withMutations -> .mutableSession
    
    mutableSession.Book.filter({ title: 'My Book' }).delete();
    
    const finalState = mutableSession.state;
  • Check if your logic depends on the lazy updates (as shown on the above point). Probably not, for example none of the update logic had to be changed in Redux-ORM primer. Updates are applied eagerly now.

  • Use the new Redux integration API.

    Old 0.8:

    import { Schema } from 'redux-orm';
    import { Book, Author } from './models';
    
    const schema = new Schema();
    schema.register(Book, Author);
    const reduxReducer = schema.reducer();
    const mySelector = schema.createSelector(state => state.orm, session => {
        return session.Book.all().toRefArray();
    });

    New 0.9:

    import { ORM, createReducer, createSelector } from 'redux-orm';
    import { Book, Author } from './models';
    const orm = new ORM();
    orm.register(Book, Author);
    const reduxReducer = createReducer(orm);
    const mySelector = createSelector(orm, state => state.orm, session => {
        return session.Book.all().toRefArray();
    });

    See below for Redux usage without createReducer - it's very simple and explicit.

  • Update to the new Model-specific reducer API if you use it. Take note of the removed getNextState() call; The new reducer will not return anything, just apply the updates in the session. (it's not really a reducer anymore, more of an action handler; the name might be updated in the future). Returning a custom state is not supported anymore.

    Old 0.8:

    class MyModel {
        reducer(state, action, SessionSpecificMyModel, session) {
            if (action.type === 'CREATE') {
                SessionSpecificMyModel.create(action.payload);
            }
            return SessionSpecificMyModel.getNextState();
        }
    }

    New 0.9:

    class MyModel {
        reducer(action, SessionSpecificMyModel, session) { // first state argument removed
            if (action.type === 'CREATE') {
                SessionSpecificMyModel.create(action.payload);
            }
            // no return value
        }
    }

    I recommend making your own custom reducer to Redux like this:

    function dbReducer(state, action) {
        const session = orm.session(state);
        if (action.type === 'CREATE') {
            session.Book.create(action.payload);
        }
        return session.state;
    }
  • Remove usage of the QuerySet flagging API

    In 0.8 the default was for filter/exclude/orderBy to operate on Model instances. They now operate on object references in the database. If you attempt to use flagging API, an error will be thrown.

    Old 0.8:

    const books = session.Book.all();
    const released = books.filter(bookInstance => bookInstance.isReleased()).toRefArray();
    const releasedAlt = books.withRefs.filter(bookRef => bookRef.releaseYear >= 2016).toRefArray();
    
    const releasedFirstModel = books.first();
    const releasedFirstRef = books.withRefs.first();

    New 0.9:

    const books = session.Book.all();
    
    // queries are handled by the database which is not aware of Models. If you need Model
    // functionality in the filter, use a function predicate and instantiate a model with the ref.
    const released = books.filter(bookRef => (new Book(bookRef)).isReleased()).toRefArray();
    
    // No need for withRefs flag.
    const releasedAlt = books.filter(bookRef => bookRef.releaseYear >= 2016).toRefArray();
    
    const releasedFirstModel = released.first();
    const releasedFirstRef = released.first().ref;
  • If you used .map or .forEach from a QuerySet, call .toModelArray() or .toRefArray() first and use them from the native Array. If you attempt to use these methods, an error will be thrown.

  • Optional but recommended: For all non-relationship fields, add attr() as the value in the field definition. When you declare these fields, the getters and setters will be set once when you register the model instead of needing to do it on every Model instantiation.

    Old 0.8:

    Book.fields = {
        // implicit id field
        // implicit name field
        author: fk('Author'),
    };

    New 0.9:

    import { attr, fk } from 'redux-orm';
    
    Book.fields = {
        id: attr(),
        name: attr(),
        author: fk('Author'),
    };
Clone this wiki locally