Class DS.RootState
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:
record.get('currentState.stateName');
//=> "root.created.uncommitted"
The hierarchy of valid states that ship with ember data looks like this:
* 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:
aState: DS.State.create({
myEvent: function(manager, param) {
console.log("Received myEvent with", param);
}
})
To trigger this event:
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:
* 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:
var statePath = record.get('stateManager.currentPath');
if (statePath === 'created.inFlight') {
doSomething();
}
You can say:
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.