Skip to content

Commit

Permalink
Populate foreign key when has-one association is assigned
Browse files Browse the repository at this point in the history
  • Loading branch information
danschultz committed Aug 31, 2011
1 parent 8456c89 commit a4a8f83
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 61 deletions.
2 changes: 1 addition & 1 deletion src/mesh/model/Entity.as
Expand Up @@ -434,7 +434,7 @@ package mesh.model
*/
protected function get serializableOptions():Object
{
return null;
return {exclude:["storeKey"]};
}

private var _status:EntityStatus;
Expand Down
52 changes: 34 additions & 18 deletions src/mesh/model/associations/Association.as
Expand Up @@ -66,28 +66,26 @@ package mesh.model.associations
* @param entity The entity that was associated.
* @param revive Indicates if the entity should be revived when associated.
*/
protected function associate(entity:Entity, revive:Boolean):void
protected function associate(entity:Entity):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);
if (entity.status.isDestroyed) {
entity.revive();
} else {
if (owner.store && entity.store == null) owner.store.add(entity);
listenForStatusChanges(entity);
_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);
}
}
if (entity.status.isDestroyed) {
handleEntityDestroyed(entity);
} else {
handleEntityRevived(entity);
}
}

/**
Expand All @@ -108,8 +106,20 @@ package mesh.model.associations
* @param entity The entity that changed.
*/
protected function entityDestroyed(entity:Entity):void
{

{
listenForStatusChanges(entity);
}

private function handleEntityDestroyed(entity:Entity):void
{
entityDestroyed(entity);
}

private function handleEntityRevived(entity:Entity):void
{
if (!_entities.contains(entity)) {
entityRevived(entity);
}
}

private function handleOwnerPropertyChange(event:PropertyChangeEvent):void
Expand All @@ -132,6 +142,12 @@ package mesh.model.associations
}
}

private function listenForStatusChanges(entity:Entity):void
{
entity.removeEventListener(StateEvent.ENTER, handleEntityStatusChange);
entity.addEventListener(StateEvent.ENTER, handleEntityStatusChange);
}

/**
* Loads the data for this association. If this is a non-lazy association, then nothing happens.
*/
Expand Down
7 changes: 4 additions & 3 deletions src/mesh/model/associations/AssociationCollection.as
Expand Up @@ -3,7 +3,6 @@ package mesh.model.associations
import flash.errors.IllegalOperationError;
import flash.events.Event;

import mesh.core.inflection.humanize;
import mesh.model.Entity;
import mesh.model.store.LocalQuery;
import mesh.model.store.Query;
Expand Down Expand Up @@ -36,15 +35,17 @@ package mesh.model.associations
*/
override protected function entityDestroyed(entity:Entity):void
{
super.entityDestroyed(entity);
_list.removeItemAt(_list.getItemIndex(entity));
associate(entity, false);
}

/**
* @inheritDoc
*/
override protected function entityRevived(entity:Entity):void
{
super.entityRevived(entity);

if (!_list.contains(entity)) {
_list.addItem(entity);
}
Expand Down Expand Up @@ -75,7 +76,7 @@ package mesh.model.associations
private function handleEntitiesAdded(items:Array):void
{
for each (var entity:Entity in items) {
associate(entity, true);
associate(entity);
}
}

Expand Down
60 changes: 54 additions & 6 deletions src/mesh/model/associations/HasAssociation.as
Expand Up @@ -6,6 +6,7 @@ package mesh.model.associations
import mesh.model.Entity;

import mx.binding.utils.ChangeWatcher;
import mx.events.PropertyChangeEvent;

/**
* The base class for any association that links to a single entity.
Expand All @@ -20,25 +21,50 @@ package mesh.model.associations
public function HasAssociation(owner:Entity, property:String, options:Object = null)
{
super(owner, property, options);
checkForRequiredFields();
}

private function checkForRequiredFields():void
{
if (entityType == null) throw new IllegalOperationError("Undefined entity type for " + this);
if (foreignKey != null && !owner.hasOwnProperty(foreignKey)) throw new IllegalOperationError("Undefined foreign key for " + this);
}

/**
* @inheritDoc
*/
override protected function associate(entity:Entity):void
{
super.associate(entity);
populateForeignKey();
entity.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handleAssociatedEntityPropertyChange);
}

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

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

private function handleAssociatedEntityPropertyChange(event:PropertyChangeEvent):void
{
if (event.property == "id") {
populateForeignKey();
}
}

private var _loadingEntity:Entity;
private var _loadingEntityWatcher:ChangeWatcher;
/**
Expand All @@ -50,10 +76,15 @@ package mesh.model.associations

if (_loadingEntity == null) {
_loadingEntity = owner.store.find(entityType, owner[foreignKey]);

_loadingEntityWatcher = ChangeWatcher.watch(_loadingEntity.status, "isSynced", function(event:Event):void
{
loaded(_loadingEntity);
});

if (_loadingEntity.status.isSynced) {
loaded(_loadingEntity);
}
}
}

Expand All @@ -71,14 +102,31 @@ package mesh.model.associations
}
}

private function populateForeignKey():void
{
// If the foreign key is undefined, try to automagically set it.
var key:String = foreignKey != null ? foreignKey : property + "Id";

if (owner.hasOwnProperty(key)) {
owner[key] = object.id;
}
}

/**
* @inheritDoc
*/
override protected function unassociate(entity:Entity):void
{
super.unassociate(entity);
entity.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handleAssociatedEntityPropertyChange);
if (foreignKey != null) owner[foreignKey] = null;
}

/**
* The property on the owner that defines the foreign key to load this association.
*/
protected function get foreignKey():String
{
if (options.foreignKey == null || !owner.hasOwnProperty(options.foreignKey)) {
throw new IllegalOperationError("Undefined foreign key for " + this);
}
return options.foreignKey;
}

Expand All @@ -93,7 +141,7 @@ package mesh.model.associations
} catch (e:Error) {

}
throw new IllegalOperationError("Undefined entity type for " + this);
return null;
}

/**
Expand All @@ -103,7 +151,7 @@ package mesh.model.associations
{
if (object != null) unassociate(object);
super.object = value;
if (object != null) associate(object, true);
if (object != null) associate(object);
}
}
}
1 change: 1 addition & 0 deletions src/mesh/model/source/FixtureSource.as
Expand Up @@ -110,6 +110,7 @@ package mesh.model.source
{
if (_fixtures[data.id] != null) {
entity.fromObject(_fixtures[data.id]);
entity.synced();
} else {
entity.errored();
}
Expand Down
26 changes: 21 additions & 5 deletions src/mesh/model/store/Commit.as
Expand Up @@ -93,10 +93,15 @@ package mesh.model.store
private function copyData(entities:Array, data:Array = null, copier:Function = null):void
{
if (data != null) {
entities = storeEntities(entities);
var entitiesFromStore:Array = storeEntities(entities);
for (var i:int = 0; i < data.length; i++) {
if (copier != null) copier(entities[i], data[i]);
else copy(data[i], entities[i]);
if (copier != null) {
copier(entities[i], data[i]);
copier(entitiesFromStore[i], data[i]);
} else {
copy(data[i], entities[i]);
copy(data[i], entitiesFromStore[i]);
}
}
}
}
Expand All @@ -106,8 +111,14 @@ package mesh.model.store
_committed.addAll(entities);
_operation.completed(entities);

for each (var entity:Entity in storeEntities(entities)) {
entity.synced();
for each (var entity:Entity in entities) {
entityInStore(entity).synced();

// Notify dependencies that their dependents have been committed, and any foreign keys
// should be synced now.
for each (var depenency:Entity in storeEntities(_dependencies.dependenciesFor(entity))) {
entityInStore(depenency).synced();
}
}

commitDependents(entities);
Expand All @@ -131,6 +142,11 @@ package mesh.model.store
}
}

private function entityInStore(entity:Entity):Entity
{
return _store.index.findByKey(entity.storeKey);
}

/**
* The data source calls this method when an entity failed during a commit to the
* backend.
Expand Down
10 changes: 9 additions & 1 deletion src/mesh/model/store/Commits.as
Expand Up @@ -37,7 +37,6 @@ package mesh.model.store

private var _queue:OperationQueue;
private var _commits:Array = [];
private var _checkpoint:Commit;

/**
* Constructor.
Expand Down Expand Up @@ -114,6 +113,15 @@ package mesh.model.store
return snapshot.readObject();
}

private var _checkpoint:Commit;
/**
* The last successful commit of the store.
*/
public function get checkpoint():Commit
{
return _checkpoint;
}

private var _failed:Commit;
/**
* The commit that failed.
Expand Down
1 change: 1 addition & 0 deletions tests/mesh/Account.as
Expand Up @@ -6,6 +6,7 @@ package mesh

public class Account extends Entity
{
[Bindable] public var customerId:int;
[Bindable] public var customer:Customer;
[Bindable] public var number:String;

Expand Down
2 changes: 1 addition & 1 deletion tests/mesh/Customer.as
Expand Up @@ -23,7 +23,7 @@ package mesh
{
super(properties);

hasOne("account", {inverse:"customer", isMaster:true});
hasOne("account", {inverse:"customer", isMaster:true, foreignKey:"accountId"});
hasMany("orders", {inverse:"customer", isMaster:true});
}

Expand Down
2 changes: 1 addition & 1 deletion tests/mesh/Employee.as
Expand Up @@ -9,7 +9,7 @@ package mesh
public class Employee extends Person
{
[Bindable] public var employer:Organization;
public var employerId:int;
[Bindable] public var employerId:int;
[Bindable] public var tasks:IList;

public function Employee(properties:Object=null)
Expand Down
1 change: 1 addition & 0 deletions tests/mesh/Organization.as
Expand Up @@ -19,6 +19,7 @@ package mesh
hasMany("employees", {
lazy:true,
isMaster:true,
inverse:"employer",
query:new RemoteQuery().on(Employee).where(function(employee:Employee):Boolean
{
return id == employee.employerId;
Expand Down
Expand Up @@ -44,7 +44,7 @@ package mesh.model.associations

employee.associations.employer.load();
assertThat(employee.employer, notNullValue());
assertThat(employee.associations.employees.isLoaded, equalTo(true));
assertThat(employee.associations.employer.isLoaded, equalTo(true));
}
}
}

0 comments on commit a4a8f83

Please sign in to comment.