DS.RootState Class addon/-private/system/model/states.js:12


State

Each record has a currentState property that explicitly tracks what state a record is in at any given time. For instance, if a record is newly created and has not yet been sent to the adapter to be saved, it would be in the root.loaded.created.uncommitted state. If a record has had local modifications made to it that are in the process of being saved, the record would be in the root.loaded.updated.inFlight state. (This state paths will be explained in more detail below.)

Events are sent by the record or its store to the record's currentState property. How the state reacts to these events is dependent on which state it is in. In some states, certain events will be invalid and will cause an exception to be raised.

States are hierarchical and every state is a substate of the RootState. For example, a record can be in the root.deleted.uncommitted state, then transition into the root.deleted.inFlight state. If a child state does not implement an event handler, the state manager will attempt to invoke the event on all parent states until the root state is reached. The state hierarchy of a record is described in terms of a path string. You can determine a record's current state by getting the state's stateName property:

1
2
record.get('currentState.stateName');
//=> "root.created.uncommitted"

The hierarchy of valid states that ship with ember data looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* root
  * deleted
    * saved
    * uncommitted
    * inFlight
  * empty
  * loaded
    * created
      * uncommitted
      * inFlight
    * saved
    * updated
      * uncommitted
      * inFlight
  * loading

The DS.Model states are themselves stateless. What that means is that, the hierarchical states that each of those points to is a shared data structure. For performance reasons, instead of each record getting its own copy of the hierarchy of states, each record points to this global, immutable shared instance. How does a state know which record it should be acting on? We pass the record instance into the state's event handlers as the first argument.

The record passed as the first parameter is where you should stash state about the record if needed; you should never store data on the state object itself.

Events and Flags

A state may implement zero or more events and flags.

Events

Events are named functions that are invoked when sent to a record. The record will first look for a method with the given name on the current state. If no method is found, it will search the current state's parent, and then its grandparent, and so on until reaching the top of the hierarchy. If the root is reached without an event handler being found, an exception will be raised. This can be very helpful when debugging new features.

Here's an example implementation of a state with a myEvent event handler:

1
2
3
4
5
aState: DS.State.create({
  myEvent: function(manager, param) {
    console.log("Received myEvent with", param);
  }
})

To trigger this event:

1
2
record.send('myEvent', 'foo');
//=> "Received myEvent with foo"

Note that an optional parameter can be sent to a record's send() method, which will be passed as the second parameter to the event handler.

Events should transition to a different state if appropriate. This can be done by calling the record's transitionTo() method with a path to the desired state. The state manager will attempt to resolve the state path relative to the current state. If no state is found at that path, it will attempt to resolve it relative to the current state's parent, and then its parent, and so on until the root is reached. For example, imagine a hierarchy like this:

1
2
3
4
5
* created
  * uncommitted <-- currentState
  * inFlight
* updated
  * inFlight

If we are currently in the uncommitted state, calling transitionTo('inFlight') would transition to the created.inFlight state, while calling transitionTo('updated.inFlight') would transition to the updated.inFlight state.

Remember that only events should ever cause a state transition. You should never call transitionTo() from outside a state's event handler. If you are tempted to do so, create a new event and send that to the state manager.

Flags

Flags are Boolean values that can be used to introspect a record's current state in a more user-friendly way than examining its state path. For example, instead of doing this:

1
2
3
4
var statePath = record.get('stateManager.currentPath');
if (statePath === 'created.inFlight') {
  doSomething();
}

You can say:

1
2
3
if (record.get('isNew') && record.get('isSaving')) {
  doSomething();
}

If your state does not set a value for a given flag, the value will be inherited from its parent (or the first place in the state hierarchy where it is defined).

The current set of flags are defined below. If you want to add a new flag, in addition to the area below, you will also need to declare it in the DS.Model class.