Skip to content

Commit

Permalink
Revive destroyed entities when added to an association
Browse files Browse the repository at this point in the history
  • Loading branch information
danschultz committed Aug 30, 2011
1 parent 2559e11 commit c3014c9
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 9 deletions.
5 changes: 3 additions & 2 deletions src/mesh/model/Entity.as
Expand Up @@ -241,9 +241,10 @@ package mesh.model
*/
public function revive():void
{
if (status.isDestroyed) {
id = 0;
if (status.isDestroyed && status.isSynced) {
id = null;
}
status.revive();
}

private function initializeAssociations():void
Expand Down
26 changes: 26 additions & 0 deletions src/mesh/model/EntityStatus.as
Expand Up @@ -32,6 +32,7 @@ package mesh.model
private var _synced:Action;

private var _entity:Entity;
private var _revive:Action;

/**
* Constructor.
Expand Down Expand Up @@ -88,6 +89,16 @@ package mesh.model
_loading.trigger();
}

/**
* Puts the entity into either a new dirty state or persisted state. If the entity has destroyed
* from its data source, then the entity is put into a new dirty state. Otherwise its put into
* a persisted state and marked as dirty if it has property changes.
*/
public function revive():void
{
_revive.trigger();
}

/**
* Puts the entity into a synced state. The synced state represents that the application and
* the remote source are the same.
Expand Down Expand Up @@ -117,6 +128,21 @@ package mesh.model
_synced = _state.createAction("synced");
_synced.transitionTo(persistedState, [loadingBusyState, newDirtyState, persistedDirtyState]);
_synced.transitionTo(destroyedState, destroyedDirtyState);

_revive = _state.createAction("revive");
_revive.transitionTo(newDirtyState, destroyedState);
_revive.transitionTo(newDirtyState, destroyedDirtyState, function():Boolean
{
return _entity.id == null;
});
_revive.transitionTo(persistedState, destroyedDirtyState, function():Boolean
{
return !_entity.hasPropertyChanges;
});
_revive.transitionTo(persistedDirtyState, destroyedDirtyState, function():Boolean
{
return _entity.hasPropertyChanges;
});
}

public function get isBusy():Boolean
Expand Down
44 changes: 43 additions & 1 deletion src/mesh/model/associations/Association.as
Expand Up @@ -7,6 +7,7 @@ package mesh.model.associations

import mesh.core.inflection.humanize;
import mesh.core.reflection.Type;
import mesh.core.state.StateEvent;
import mesh.model.Entity;

import mx.events.PropertyChangeEvent;
Expand Down Expand Up @@ -43,14 +44,54 @@ package mesh.model.associations
* Called by sub-classes when an entity is added to an association.
*
* @param entity The entity that was associated.
* @param revive Indicates if the entity should be revived when associated.
*/
protected function associate(entity:Entity):void
protected function associate(entity:Entity, revive:Boolean):void
{
if (owner.store && entity.store == null) owner.store.add(entity);

entity.addEventListener(StateEvent.ENTER, handleEntityStatusChange);
if (revive) entity.revive();

_entities.add(entity);
populateInverseRelationship(entity);
}

private function handleEntityStatusChange(event:StateEvent):void
{
var entity:Entity = Entity( event.target );

if (_entities.contains(entity)) {
if (entity.status.isDestroyed) {
entityDestroyed(entity);
} else {
entityRevived(entity);
}
}
}

/**
* Called when the entity's status changes from a destroyed to non-destroyed state. This allows
* the association to add the entity back to its host.
*
* @param entity The entity that changed.
*/
protected function entityRevived(entity:Entity):void
{

}

/**
* Called when the entity's status changes from a non-destroyed state to a destroyed state. This
* allows the association to remove the entity from its host.
*
* @param entity The entity that changed.
*/
protected function entityDestroyed(entity:Entity):void
{

}

private function handleOwnerPropertyChange(event:PropertyChangeEvent):void
{
if (event.property is String && event.property.toString() == property) {
Expand Down Expand Up @@ -89,6 +130,7 @@ package mesh.model.associations
*/
protected function unassociate(entity:Entity):void
{
entity.removeEventListener(StateEvent.ENTER, handleEntityStatusChange);
_entities.remove(entity);
}

Expand Down
21 changes: 20 additions & 1 deletion src/mesh/model/associations/AssociationCollection.as
Expand Up @@ -23,6 +23,25 @@ package mesh.model.associations
_list.addEventListener(CollectionEvent.COLLECTION_CHANGE, handleListCollectionChange);
}

/**
* @inheritDoc
*/
override protected function entityDestroyed(entity:Entity):void
{
_list.removeItemAt(_list.getItemIndex(entity));
associate(entity, false);
}

/**
* @inheritDoc
*/
override protected function entityRevived(entity:Entity):void
{
if (!_list.contains(entity)) {
_list.addItem(entity);
}
}

private function handleListCollectionChange(event:CollectionEvent):void
{
switch (event.kind) {
Expand All @@ -48,7 +67,7 @@ package mesh.model.associations
private function handleEntitiesAdded(items:Array):void
{
for each (var entity:Entity in items) {
associate(entity);
associate(entity, true);
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/mesh/model/associations/HasOneAssociation.as
Expand Up @@ -12,14 +12,31 @@ package mesh.model.associations
super(owner, property, options);
}

/**
* @inheritDoc
*/
override protected function entityDestroyed(entity:Entity):void
{
owner[property] = null;
associate(entity, false);
}

/**
* @inheritDoc
*/
override protected function entityRevived(entity:Entity):void
{
owner[property] = entity;
}

/**
* @inheritDoc
*/
override public function set object(value:*):void
{
if (object != null) unassociate(object);
super.object = value;
if (object != null) associate(object);
if (object != null) associate(object, true);
}
}
}
17 changes: 17 additions & 0 deletions tests/mesh/TestSource.as
@@ -0,0 +1,17 @@
package mesh
{
import mesh.model.source.FixtureSource;
import mesh.model.source.MultiSource;

public class TestSource extends MultiSource
{
public function TestSource()
{
super();
map(Customer, new FixtureSource(Customer));
map(Account, new FixtureSource(Account));
map(Person, new FixtureSource(Person));
map(Order, new FixtureSource(Order));
}
}
}
66 changes: 66 additions & 0 deletions tests/mesh/model/EntityRevivalTests.as
@@ -0,0 +1,66 @@
package mesh.model
{
import mesh.Name;
import mesh.Person;
import mesh.TestSource;
import mesh.model.store.Store;

import org.flexunit.assertThat;
import org.hamcrest.object.equalTo;

public class EntityRevivalTests
{
private var _person:Person;
private var _store:Store;

[Before]
public function setup():void
{
_person = new Person({name:new Name("Jimmy", "Page"), age:67});

_store = new Store(new TestSource());
_store.add(_person);
_store.commit();
}

[Test]
public function testStatusIsNewIfRevivedWithoutID():void
{
var person:Person = new Person({name:new Name("Steve", "Jobs")});
_store.add(person);

person.destroy();
person.revive();
assertThat(person.status.isNew, equalTo(true));
}

[Test]
public function testStatusIsPersistedAndDirtyIfRevivedWithPropertyChanges():void
{
_person.age++;

_person.destroy();
_person.revive();
assertThat(_person.status.isPersisted && _person.status.isDirty, equalTo(true));
}

[Test]
public function testStatusIsPersistedAndSyncedIfNoPropertyChanges():void
{
_person.destroy();
_person.revive();
assertThat(_person.status.isPersisted && !_person.status.isDirty, equalTo(true));
}

[Test]
public function testStatusIsNewIfDestroyedAndSynced():void
{
_person.destroy();
_store.commit();

_person.revive();
assertThat(_person.id, equalTo(null));
assertThat(_person.status.isNew, equalTo(true));
}
}
}
63 changes: 61 additions & 2 deletions tests/mesh/model/associations/HasManyAssociationTests.as
Expand Up @@ -3,7 +3,7 @@ package mesh.model.associations
import mesh.Address;
import mesh.Customer;
import mesh.Order;
import mesh.model.source.Source;
import mesh.TestSource;
import mesh.model.store.Store;

import mx.collections.ArrayList;
Expand All @@ -19,7 +19,7 @@ package mesh.model.associations
[Before]
public function setup():void
{
_store = new Store(new Source());
_store = new Store(new TestSource());
_customer = new Customer();
_customer.orders = new ArrayList();
_store.add(_customer);
Expand Down Expand Up @@ -57,5 +57,64 @@ package mesh.model.associations

assertThat(order.customer, equalTo(_customer));
}

[Test]
public function testDestroyedEntityIsRemovedFromAssociation():void
{
var order:Order = new Order();
order.shippingAddress = new Address("2306 Zanker Rd", "San Jose");
order.total = 10.00;
_customer.orders = new ArrayList([order]);
_store.commit();
assertThat("Precondition failed", order.status.isSynced, equalTo(true));

order.destroy();
assertThat(_customer.orders.length, equalTo(0));
}

[Test]
public function testRevivedEntityIsAddedBackToAssociation():void
{
var order:Order = new Order();
order.shippingAddress = new Address("2306 Zanker Rd", "San Jose");
order.total = 10.00;
_customer.orders = new ArrayList([order]);
_store.commit();
assertThat("Precondition failed", order.status.isSynced, equalTo(true));

order.destroy();
order.revive();
assertThat(_customer.orders.length, equalTo(1));
}

[Test]
public function testEntityIsNotAddedMultipleTimesOnStatusChange():void
{
var order:Order = new Order();
order.shippingAddress = new Address("2306 Zanker Rd", "San Jose");
order.total = 10.00;
_customer.orders = new ArrayList([order]);
_store.commit();
assertThat("Precondition failed", order.status.isSynced, equalTo(true));

order.synced();
assertThat(_customer.orders.length, equalTo(1));
}

[Test]
public function testDestroyedEntityIsRevivedWhenAddedToAssociation():void
{
var order:Order = new Order();
order.shippingAddress = new Address("2306 Zanker Rd", "San Jose");
order.total = 10.00;
_customer.orders = new ArrayList([order]);
_store.commit();
assertThat("Precondition failed", order.status.isSynced, equalTo(true));

order.destroy();
_customer.orders.addItem(order);
assertThat(order.status.isPersisted, equalTo(true));
assertThat(_customer.orders.length, equalTo(1));
}
}
}

0 comments on commit c3014c9

Please sign in to comment.