Skip to content

Commit

Permalink
Add methods to define associations on an entity
Browse files Browse the repository at this point in the history
  • Loading branch information
danschultz committed Aug 17, 2011
1 parent 6b71b90 commit 7323031
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 706 deletions.
140 changes: 140 additions & 0 deletions src/mesh/model/Associations.as
@@ -0,0 +1,140 @@
package mesh.model
{
import flash.utils.Proxy;
import flash.utils.flash_proxy;

import mesh.model.associations.Association;

/**
* The <code>Associations</code> class is a hash of properties to their associations for an entity.
* Each <code>Entity</code> contains its own associations hash, and can be accessed through the
* <code>Entity.associations</code> property.
*
* <p>
* This class extends <code>Proxy</code> and provides looping over each association using the
* <code>for each..in</code> keywords. In addition, you can retrieve and association using
* <code>.</code> (dot) syntax, where the keyword after the dot is the property defining the
* association.
* </p>
*
* @author Dan Schultz
*/
public dynamic class Associations extends Proxy
{
private var _entity:Entity;
private var _mappings:Object = {};
private var _properties:Array = [];

/**
* Constructor.
*/
public function Associations(entity:Entity)
{
super();
_entity = entity;
}

/**
* Checks if a property is mapped to an association.
*
* @param property The property to check.
* @return <code>true</code> if the property is mapped.
*/
public function isAssociation(property:String):Boolean
{
return _mappings.hasOwnProperty(property);
}

/**
* Maps a property on an entity to an association object.
*
* @param property The property to map.
* @param association The association to map.
*/
public function map(property:String, association:Association):void
{
if (!isAssociation(property)) {
_mappings[property] = association;
_properties.push(property);
}
}

/**
* Returns the association mapped to the given property.
*
* @param property The association's property.
* @return An association, or <code>null</code> if one is not defined.
*/
public function mappedTo(property:String):Association
{
return _mappings[property];
}

/**
* Removes the association mapped to the given property.
*
* @param property The property to remove the association on.
*/
public function unmap(property:String):void
{
if (isAssociation(property)) {
delete _mappings[property];
_properties.splice(_properties.indexOf(property), 1);
}
}

/**
* @inheritDoc
*/
override flash_proxy function deleteProperty(name:*):Boolean
{
return name != null && isAssociation(name.toString()) ? unmap(name.toString()) : false;
}

/**
* @inheritDoc
*/
override flash_proxy function getProperty(name:*):*
{
return name != null && isAssociation(name.toString()) ? mappedTo(name.toString()) : undefined;
}

/**
* @inheritDoc
*/
override flash_proxy function hasProperty(name:*):Boolean
{
return name != null && isAssociation(name.toString());
}

/**
* @inheritDoc
*/
override flash_proxy function nextName(index:int):String
{
return _iteratingItems[index-1];
}

private var _iteratingItems:Array;
private var _len:int;
/**
* @inheritDoc
*/
override flash_proxy function nextNameIndex(index:int):int
{
if (index == 0) {
_iteratingItems = _properties.concat();
_len = _iteratingItems.length;
}
return index < _len ? index+1 : 0;
}

/**
* @inheritDoc
*/
override flash_proxy function nextValue(index:int):*
{
return _mappings[_iteratingItems[index-1]];
}
}
}
78 changes: 14 additions & 64 deletions src/mesh/model/Entity.as
@@ -1,13 +1,11 @@
package mesh.model
{
import flash.errors.IllegalOperationError;
import flash.events.EventDispatcher;
import flash.utils.flash_proxy;

import mesh.core.inflection.humanize;
import mesh.core.object.copy;
import mesh.core.reflection.Type;
import mesh.model.associations.Association;
import mesh.model.associations.HasManyAssociation;
import mesh.model.associations.HasOneAssociation;
import mesh.model.query.Query;
Expand Down Expand Up @@ -68,15 +66,19 @@ package mesh.model
*/
public static const SYNCED:int = 0x04000;

private var _associations:Object = {};
private var _aggregates:Aggregates = new Aggregates(this);
private var _associations:Associations;
private var _aggregates:Aggregates;

/**
* Constructor.
*/
public function Entity(values:Object = null)
{
super();

_associations = new Associations(this);
_aggregates = new Aggregates(this);

copy(values, this);
addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, handlePropertyChange);
}
Expand All @@ -86,35 +88,6 @@ package mesh.model
_aggregates.add(property, type, mappings);
}

/**
* Returns an association proxy for the given the given property. The proxy that is
* returned is determined by the relationship type. For instance, if the property is
* a has-many relationship, a <code>HasManyAssociation</code> is returned.
*
* @param property The property of the relationship to get the proxy for.
* @return An association proxy.
*/
protected function association(property:String):*
{
if (!_associations.hasOwnProperty(property)) {
throw new ArgumentError("Undefined association on property '" + property + "'");
}
return _associations[property];
}

/**
* Maps a property to an association object.
*
* @param property The property to associate.
* @param association The association to map.
* @return The mapped association.
*/
protected function associate(property:String, association:Association):*
{
_associations[property] = association;
return _associations[property];
}

/**
* Sets up a has-one association for a property.
*
Expand All @@ -123,9 +96,9 @@ package mesh.model
* @param options Any options to configure the association.
* @return The association object.
*/
protected function hasOne(property:String, query:Query, options:Object = null):HasOneAssociation
protected function hasOne(property:String, query:Query, options:Object = null):void
{
throw new IllegalOperationError("Entity.hasOne() is not implemented.");
_associations.map(property, new HasOneAssociation(this, query, options));
}

/**
Expand All @@ -136,20 +109,9 @@ package mesh.model
* @param options Any options to configure the association.
* @return The association object.
*/
protected function hasMany(property:String, query:Query, options:Object = null):HasManyAssociation
protected function hasMany(property:String, query:Query, options:Object = null):void
{
throw new IllegalOperationError("Entity.hasMany() is not implemented.");
}

/**
* Checks if an association has been defined for a property.
*
* @param property The property to check.
* @return <code>true</code> if the property has a defined association.
*/
protected function isAssociated(property:String):Boolean
{
return _associations.hasOwnProperty(property);
_associations.map(property, new HasManyAssociation(this, query, options));
}

/**
Expand Down Expand Up @@ -393,10 +355,11 @@ package mesh.model
}

/**
* A hash of the associations for this entity, where the key represents the association's
* property, and the value is the <code>Association</code>.
* The hash the associations defined on this entity. The associations hash is defined as
* key-value pairs where the key is the property the associaiton is defined on, and the
* value is the association object.
*/
public function get associations():Object
public function get associations():Associations
{
return _associations;
}
Expand Down Expand Up @@ -478,19 +441,6 @@ package mesh.model
return changes.hasChanges;
}

/**
* <code>true</code> if this entity contains an association marked for auto-save that is dirty.
*/
public function get hasDirtyAssociations():Boolean
{
for each (var association:Association in associations) {
if (association.definition.autoSave && association.isDirty) {
return true;
}
}
return false;
}

/**
* <code>true</code> if this entity is a new record that needs to be persisted. By default,
* an entity is considered new if its ID is 0. Sub-classes may override this implementation
Expand Down
52 changes: 39 additions & 13 deletions src/mesh/model/associations/Association.as
@@ -1,36 +1,37 @@
package mesh.model.associations
{
import flash.utils.flash_proxy;
import flash.events.EventDispatcher;

import mesh.core.inflection.humanize;
import mesh.core.proxy.DataProxy;
import mesh.core.reflection.Type;
import mesh.model.Entity;
import mesh.model.query.Query;

import mx.events.PropertyChangeEvent;

use namespace flash_proxy;

/**
* An association class is a proxy object that contains the references to the objects in
* a relationship, where the <em>owner</em> represents the object hosting the association,
* and the <em>object</em> is the actual associated object.
*
* @author Dan Schultz
*/
public dynamic class Association extends DataProxy
public class Association extends EventDispatcher
{
/**
* Constructor.
*
* @param owner The parent that owns the relationship.
* @param relationship The relationship being represented by the proxy.
* @param query The query that provides the data to this association.
* @param options The options for this association.
*/
public function Association(owner:Entity, definition:AssociationDefinition)
public function Association(owner:Entity, query:Query, options:Object = null)
{
super();

_query = query;
_options = options != null ? options : {};
_owner = owner;
_definition = definition;
}

/**
Expand Down Expand Up @@ -74,7 +75,10 @@ package mesh.model.associations

}

public function toString():String
/**
* @private
*/
override public function toString():String
{
return humanize(reflect.className).toLowerCase();
}
Expand All @@ -99,13 +103,26 @@ package mesh.model.associations
return _isLoading;
}

private var _definition:AssociationDefinition;
private var _object:*;
/**
* The data assigned to this association.
*/
public function get object():*
{
return _object;
}
public function set object(value:*):void
{
_object = value;
}

private var _options:Object;
/**
* The relationship model that this association represents.
* The options defined for this association.
*/
flash_proxy function get definition():AssociationDefinition
protected function get options():Object
{
return _definition;
return _options;
}

private var _owner:Entity;
Expand All @@ -117,6 +134,15 @@ package mesh.model.associations
return _owner;
}

private var _query:Query;
/**
* The query that loads the data for this association.
*/
public function get query():Query
{
return _query;
}

private var _reflect:Type;
/**
* A reflection on this object.
Expand Down

0 comments on commit 7323031

Please sign in to comment.