Enterprise Objects Framework interacts with generic records and custom classes the same way. It defines the set of methods for supporting operations common to all enterprise objects and uses only those methods. The EOEnterpriseObject interface (or informal protocol in Objective-C) specifies the complete set. It includes methods for initializing instances, announcing changes, setting and retrieving property values, and performing validation of state.
Generating Source Files
The easiest way to create a custom enterprise object is with EOModeler's Generate Java Files, Generate ObjC Files, and Generate Client Java Files commands. These commands take the attributes and relationships you've defined in your model, and use them to generate source code for a corresponding enterprise object class. Superclass
EOModeler assumes that your custom classes descend from EOCustomObject (or NSObject in Objective-C) so that they inherit the Framework's default implementations of the EOEnterpriseObject interface. Instance Variables
EOModeler's generated source code files contain instance variables for all of the corresponding entities class properties, both attributes and relationships alike. For example, the following code excerpts show the instance variables created for the Member class:
protected String city;In Objective-C (from Member.h)
protected NSGregorianDate memberSince;
protected String phone;
protected String state;
protected String streetAddress;
protected String zip;
protected Number customerID;
protected String firstName;
protected String lastName;
protected CreditCard creditCard;
protected NSMutableArray rentals;
NSString *city;All of the instance variables correspond to attributes in the Member entity except for creditCard and rentals, which correspond to relationships.
NSCalendarDate *memberSince;
NSString *phone;
NSString *state;
NSString *streetAddress;
NSString *zip;
NSNumber *customerID;
NSString *firstName;
NSString *lastName;
CreditCard *creditCard;
NSMutableArray *rentals;
The default mechanism for assigning unique primary keys is provided with the EOAdaptorChannel method primaryKeyForNewRowWithEntity (or primaryKeyForNewRowWithEntity: in Objective-C). If you need to provide a custom mechanism for assigning primary keys, you can implement the EODatabaseContext delegate method databaseContextNewPrimaryKey (or databaseContext:newPrimaryKeyForObject:entity: in Objective-C). Using either of these two techniques means you don't need to store the primary key in your enterprise object. For more discussion of primary keys, see the chapter "Answers to Common Design Questions".
Note that Enterprise Objects Framework doesn't support modifiable primary key values-you shouldn't design your application so that users can change a primary key's value. If you really need this behavior, you have to implement it by deleting an affected object and reinserting it with a new primary key.
Writing Accessor Methods
When you generate source files for custom classes in EOModeler, the resulting files include default accessor methods. Accessor methods let you set and return the values of your class properties (instance variables). For example, here are some of Customer's accessor methods:
public NSGregorianDate memberSince() {In Objective-C (from Member.m)
willChange();
return memberSince;
}
public void setMemberSince(NSGregorianDate value) {
willChange();
memberSince = value;
}
public CreditCard creditCard() {
willChange();
return creditCard;
}
public void setCreditCard(CreditCard value) {
willChange();
creditCard = value;
}
public NSArray rentals() {
willRead();
return rentals;
}
public void setRentals(NSMutableArray value) {
willChange();
rentals = value;
}
public void addToRentals(Rental object) {
willChange();
rentals.addObject(object);
}
public void removeFromRentals(Rental object) {
willChange();
rentals.removeObject(object);
}
- (void)setMemberSince:(NSCalendarDate *)valueFeatures introduced by these code excerpts are discussed in the following sections.
{
[self willChange];
[memberSince autorelease];
memberSince = [value retain];
}
- (NSCalendarDate *)memberSince { return memberSince; }
- (void)setCreditCard:(CreditCard *)value
{
// a to-one relationship
[self willChange];
[creditCard autorelease];
creditCard = [value retain];
}
- (CreditCard *)creditCard { return creditCard; }
- (void)addToRentals:(Rental *)object
{
// a to-many relationship
[self willChange];
[rentals addObject:object];
}
- (void)removeFromRentals:(Rental *)object
{
// a to-many relationship
[self willChange];
[rentals removeObject:object];
}
- (NSArray *)rentals { return rentals; }
Note: You don't have to provide accessor methods for your object's properties. The Framework can access your object's properties directly (through its instance variables). For more information, see the section "Accessing an Enterprise Object's Data"."
Change Notification
In the above code excerpts from Member source files, you can see that each of the "set" methods includes an invocation of the willChange method. Faulting
Similar to the way "set" methods invoke the willChange method, Java "get" methods include an invocation of willRead. This method is part of Enterprise Objects Framework's faulting mechanism for postponing an object's initialization until its actually needed. Faulting is also provided with Objective-C, but its implementation is different and doesn't require the willRead method. Accessing Data through Relationships
Relationships and flattened properties are treated no differently than properties based on the entity's original attributes.
customer.creditCard().limit();In Objective-C:
[[member creditCard] limit];You can also modify attributes in the object graph regardless of what table they came from. For example, suppose that Customer's streetAddress attribute was flattened from an Address entity. Customer could have a setStreetAddress method that modified the attribute, even though it's not actually stored in the Customer table.
This data transport mechanism is defined by the EOKeyValueCoding interface (or informal protocol in Objective-C). It specifies that properties of an object are accessed indirectly by name (or key), rather than directly through invocation of an accessor method or as instance variables. EOKeyValueCoding is incorporated in the EOEnterpriseObject interfaces, so your custom classes automatically inherit key-value coding behavior that is sufficient for most purposes.
The basic methods for accessing an object's values are takeValueForKey and valueForKey (takeValue:forKey: and valueForKey: in Objective-C), which set or return the value for the specified key, respectively. The default implementations use the accessor methods normally implemented by objects (or to access instance variables directly if need be), so that you don't have to write special code simply to integrate your objects into the Enterprise Objects Framework.
There are corresponding methods takeStoredValueForKey and storedValueForKey (takeStoredValue:forKey: and storedValueForKey: in Objective-C) are similar, but they're considered to be private to the Framework and to the enterprise object class that provides them. The stored value methods are for use by the Framework for transporting data to and from trusted sources. For example, takeStoredValueForKey is used to initialize an object's properties with values fetched from the database, whereas takeValueForKey is used to modify an object's properties to values provided by a user.
Public Access with the Basic Methods
When accessing an object's class properties, the default implementations of the basic key-value coding methods use the class definition. They look for "set" methods in the following sequence:
setProperty _setPropertywhere property is the name of the property as in lastName. Note that the first letter of the property name is made uppercase as in setLastName.
Similarly, the key-value coding methods look for "get" methods in the following sequence:
getProperty property _getProperty _propertyIf no accessor methods can be found, the key-value coding methods look for instance variables containing the property's name and sets or retrieves their value directly. The search order for instance variables is as follows:
property _propertywhere property is the name of the property as in lastName.
By using the accessor methods you normally define for your objects, the default implementations of the key-value coding methods allow you to concentrate on implementing custom behavior. They also allow your objects to determine how their properties are accessed. For example, your Employee class can define a salary method that just returns an employee's salary directly or calculates it from another value.
Note: You shouldn't use "set" methods to perform validation. Rather, you should use the validation methods, described in "Performing Validation".
Private Access with the Stored Value Methods
void setBonus(double newBonus) {In Objective-C:
willChange();
_total += (newBonus - _bonus);
_bonus = newBonus;
}
- (void)setBonus:(double)newBonus {This total-updating code should be activated when the object is updated with values provided by a user (through the user interface), but not when the bonus property is restored from the database. Since the Framework restores the property using takeStoredValueForKey and since this method accesses the _bonus instance variable in preference to calling the public accessor, the unnecessary (and possibly expensive and harmful) recomputation of _total is avoided. If the object actually wants to intervene when a property is set from the database, it has two options:
[self willChange];
_total += (newBonus - _bonus);
_bonus = newBonus;
}
One kind of behavior you might want to add to your enterprise object class is the ability to apply a filter to an object's to-many relationship property and return a subset of the destination objects. For example, you could have an overdueRentals method in the Customer class that returns the subset of a customer's rentals that are overdue:
In Java:
public NSArray overdueRentals() {In Objective-C:
EOQualifier qualifier;
qualifier = new EOKeyValueQualifier(
"isOverdue",
EOQualifier.QualifierOperatorEqual,
new Boolean(true));
return EOQualifier.filteredArrayWithQualifier(
allRentals(),
qualifier);
}
- (NSArray *)overdueRentals {The overdueRentals method creates a qualifier to find all of a customer's overdue rentals. The qualifier is performed in memory against the complete set of a customer's rentals.
EOQualifier *qualifier;
qualifier = [[[EOKeyValueQualifier alloc]
initWithKey:@"isOverdue"
operatorSelector:EOQualifierOperatorEqual
value:[NSNumber numberWithBool:YES]autorelease];
return [[self allRentals]
filteredArrayUsingQualifier:qualifier];
}
With the addition of this method, Customer has a "get" accessor method with no corresponding "set" method. A setOverdueRentals method doesn't make sense because overdueRentals is derived. However, since Enterprise Objects Framework invokes accessor methods automatically, you may wonder if the absence of a setOverdueRentals method can cause problems. The answer is no, you don't have to define a corresponding set method. Enterprise Objects Framework will not attempt to invoke setOverdueRentals unless you bind the overdueRentals property to an editable user interface object.
The EOValidation interface (informal protocol in Objective-C) defines the way that enterprise objects validate their values. The validation methods can check for illegal value types, values outside of established limits, illegal relationships, and so on. Like EOKeyValueCoding, EOValidation is incorporated in the EOEnterpriseObject interface, so your custom classes automatically inherit some basic validation behavior. The default implementations of these methods check the object's state against constraints and rules defined in your model.
There are two kinds of validation methods. The first validates an entire object to see if it's ready for a specific operation (inserting, updating, and deleting) and the second validates individual properties. The two different types are discussed in more detail in the following sections. For more a more detailed discussion, see the EOValidation interface specification in the Enterprise Objects Framework Reference.
You might want to override these methods to perform additional validation to what can be specified in a model. For example, your application should never allow a fee to be deleted if it hasn't been paid. You could implement this in a Fee class as follows:
In Java:
public void validateForDelete() throws EOValidation.ExceptionIn Objective-C:
{
EOValidation.Exception superException = null;
EOValidation.Exception myException = null;
try {
super.validateForDelete()
} catch (EOValidation.Exception s) {
superException = s;
}
if (!isPaid())
myException = new EOValidation.Exception(
"You can't remove an unpaid fee.");
if (superException && myException) {
NSMutableArray exceptions = new NSMutableArray();
exceptions.addObject(superException);
exceptions.addObject(myException);
throw
EOValidation.Exception.aggregateExceptionWithExceptions(
exceptions);
} else if (superException) {
throw(superException);
} else if (myException) {
throw(myException);
}
}
- (NSException *)validateForDeleteThe default implementation of validateForDelete inherited by your custom classes performs basic checking based on referential integrity rules specified in your model. Your custom classes should therefore invoke super's implementation before performing their own validation as and should combine any exception thrown by super's implementation with their own as shown above.
{
NSException *superException = [super validateForSave];
NSException *myException = nil;
if (![self isPaid])
myException = [NSException validationExceptionWithFormat:
@"You can't remove an unpaid fee."];
if (superException && myException) {
return [NSException aggregateExceptionWithExceptions:
[NSArray arrayWithObjects:
superException, myException, nil];
} else if (superException) {
return superException;
} else if (myException) {
return myException)
}
return nil;
}
Note that the Java and Objective-C validation methods work slightly differently. The Java methods throw exceptions to indicate a validation failure whereas the Objective-C methods create and return exceptions for the Framework to raise.
The advantage of using one of the validateFor... methods is that they allow you to perform both "inter-" and "intra-" object validation before a particular operation occurs. This is useful if you have an enterprise object whose properties have interdependencies. For example, if you're saving rental data, you might want to confirm that the check out date precedes the return date before committing the changes.
In Java:
public void validateStreetAddress(String address)In Objective-C:
throws EOValidation.Exception
{
if (/** address is invalid */)
throw new EOValidation.Exception("Invalid address.");
}
- (NSException *)validateStreetAddress:(NSString *)addressIf a user tries to assign a value to streetAddress, the default implementation of validateValueForKey invokes the validateStreetAddress method. Based on the result, the operation is either accepted or refused.
{
if (/* address is invalid...*/)
return [NSException validationExceptionWithFormat:
@"Invalid address."];
return nil;
}
Prior to invoking your validateKey methods, the default implementation of validateValueForKey validates the property against constraints specified in your model (such as NULL constraints).
Note that the default implementations of validateForSave, validateForInsert, and validateForUpdate provided by EOCustomObject (NSObject in Objective-C) invoke validateValueForKey for each of an object's class properties.
Additionally, you can design your Application Kit and Java client applications so that the validation in your enterprise objects is performed as soon as a user attempts to leave an editable field in the user interface. There are two steps to doing this:
Note that in this scenario, validateValueForKey is invoked with values directly from the user interface. When validateValueForKey is invoked from validateForSave, validateForInsert, or validateForUpdate, it's invoked with values that are already in the object.
To create an instance of a custom Java enterprise object class, you invoke a constructor of the following form:
public MyCustomEO (Typically you invoke this constructor with null arguments as follows:
EOEditingContext anEOEditingContext,
EOClassDescription anEOClassDescription,
EOGlobalID anEOGlobalID)
rental = new MyCustomEO(null, null, null);You can provide real values, but most enterprise object classes don't make use of the arguments. The only common exception to this is EOGenericRecord, which requires the arguments to identify the object's class description. EOGenericRecord is handled in a different way, as described later in this section.
In Objective-C, you use alloc and init like you would with any other object, so you don't have to worry about providing arguments.
Once you create an object, you insert it into an EOEditingContext using EOEditingContext's insertObject method (insertObject: in Objective-C). For example, the following code excerpts for the Customer class create Fee and Rental enterprise objects as the by-product of a customer renting a unit at a video store. Once the objects have been created, they're inserted into the current enterprise object's EOEditingContext.
In Java:
public void rentUnit(Unit unit)In Objective-C:
{
EOEditingContext ec;
Fee fee;
Rental rental;
ec = editingContext();
// Create new objects and insert them into the editing context
fee = new Fee(null, null, null);
ec.insertObject(fee);
rental = new Rental(null, null, null);
ec.insertObject(rental);
// Manipulate relationships
rental addObjectToBothSidesOfRelationshipWithKey(fee, "fees");
rental.addObjectToBothSidesOfRelationshipWithKey(unit, "unit");
addObjectToBothSidesOfRelationshipWithKey(rental, "rentals");
}
- (void)rentUnit:(Unit *)unitEOEditingContext's insertObject method has the effect of registering the specified enterprise object with the editing context. In other words, it registers the object to be inserted in the database the next time the editing context saves changes. Inserting a new object into an editing context also has the side effect of further initializing the object, so you should always insert new objects into an editing context before modifying them (such as by manipulating their relationships). For more information, see the section "Setting Defaults for New Enterprise Objects".
{
EOEditingContext *ec;
Fee *fee;
Rental *rental;
ec = [self editingContext];
// Create new objects and insert them into the editing context
fee = [[[Fee alloc] init] autorelease];
[ec insertObject:fee];
rental = [[[Rental alloc] init] autorelease];
[ec insertObject:rental];
[rental addObject:fee
toBothSidesOfRelationshipWithKey:@"fees"];
[rental addObject:unit
toBothSidesOfRelationshipWithKey:@"unit"];
[self addObject:rental
toBothSidesOfRelationshipWithKey:@"rentals"];
}
You create an instances of EOGenericRecord differently from the way you create custom objects: To create a generic record, use EOClassDescription's createInstanceWithEditingContext method (createInstanceWithEditingContext:globalID:zone: in Objective-C) as follows:
String entityName; // Assume this exists.In Objective-C:
EOEditingContext editingContext; // Assume this exists.
EOClassDescription classDescription;
EOEnterpriseObject eo;
classDescription = EOClassDescription.classDescriptionForEntityName(entityN ame);
eo = classDescription.createInstanceWithEditingContext(
editingContext, null);
if (eo) editingContext.insertObject(eo);
NSString *entityName; // Assume this exists.The EOClassDescription method createInstanceWithEditingContext is preferable to using an EOGenericRecord constructor (or to using EOGenericRecord's init... method in Objective-C) because the same code works if you later use a custom enterprise object class instead of EOGenericRecord. Strictly speaking, it's better to use EOClassDescription's createInstanceWithEditingContext for all types of enterprise objects-EOGenericRecords as well as your custom classes. The createInstanceWithEditingContext method is the most general way to create an enterprise object-it works regardless of whether the object is represented by a custom class or an EOGenericRecord.
EOEditingContext *editingContext; // Assume this exists.
EOClassDescription classDescription;
id eo;
classDescription = [EOClassDescription
classDescriptionForEntityName:entityName];
eo = [classDescription
createInstanceWithEditingContext:editingContext
globalID:nil
zone:[editingContext zone]];
if (eo) [editingContext insertObject:eo];
addObjectToBothSidesOfRelationshipWithKey is used to manage reciprocal relationships, in which the destination entity of a relationship has a back reference to the source. For example, Fee and Unit both have back references to Rental, and Rental has a back reference to Customer. In other words, not only does the model define a relationship from Customer to Fee and Rental, it also defines a relationship from Rental back to Customer and from Fee to Rental. When you insert an object into a relationship (such as adding a new Rental object to Customer's rentals relationship property, which is an NSArray) and the inserted object has a back reference to the enterprise object, you need to be sure to add the object to both sides of the relationship. Otherwise, your object graph will get out of sync-your Customer object's rentals array will contain the Rental object, but the Rental object won't know the Customer who rented it.
You can update object references explicitly-that is, you can directly update the Rental object's customer property, which represents its relationship to the Customer object. But it's simpler to just use addObjectToBothSidesOfRelationshipWithKey. This method is safe to use regardless of whether the source relationship is to-one or to-many, whether the reciprocal relationship is to-one or to-many, or whether the relationship is reciprocal at all.
You should observe the following guidelines in working with relationships:
This method follows the same pattern as addObjectToPropertyWithKey. That is, it looks for a selector of the form removeFromProjects, and then for a property called projects. If neither of these can be found, it throws an exception. If it finds the property but it doesn't contain the specified object, this method simply returns.
The method removeObjectFromBothSidesOfRelationshipWithKey (removeObject:fromBothSidesOfRelationshipWithKey: in Objective-C) removes a specified object from both sides of a relationship property with the specified name. Like addObjectToBothSidesOfRelationshipWithKey, this method is safe to use regardless of whether the source relationship is to-one or to-many, or whether the reciprocal relationship is to-one or to-many.
To assign default values to newly created enterprise objects, you use the method awakeFromInsertionInEditingContext (awakeFromInsertionInEditingContext: in Objective-C). This method is automatically invoked immediately after your enterprise object class creates a new object and inserts it into the EOEditingContext.
The following implementation of awakeFromInsertion in the Member class sets the current date as the value of the memberSince property:
In Java:
public void awakeFromInsertion(EOEditingContext ec)In Objective-C:
{
super.awakeFromInsertion(ec);
// Assign current date to memberSince
if (memberSince == null)
memberSince = new NSGregorianDate();
}
- (void)You use the awakeFromInsertion method to set default values for enterprise objects that represent new data. For enterprise objects that represent existing data, the Enterprise Objects Framework provides the method awakeFromFetch (awakeFromFetchInEditingContext: in Objective-C), which is sent to an enterprise object that has just been created from a database row and initialized with database values. Your enterprise objects can implement this method to provide additional initialization. Because the Framework is still busy fetching when an enterprise object receives awakeFromFetch, the object must be careful about sending messages to other enterprise objects that might need to be faulted in. For more information about faulting, see the chapter "Behind the Scenes" and the EOFaulting interface specification in the Enterprise Objects Framework Reference.
awakeFromInsertionInEditingContext:(EOEditingContext *)ec
{
[super awakeFromInsertionInEditingContext:ec];
// Assign current date to memberSince
if (!memberSince)
memberSince = [[NSCalendarDate date] retain];
}
However you can take advantage of extra information available at the time your enterprise object is initialized. In Java, instances of custom classes must provide a constructor of the following form:
public MyCustomEO (The Framework uses such a constructor to create your objects, so you can use the provided arguments to influence the creation. In Objective-C, enterprise objects can be created with either init or initWithEditingContext:classDescription:globalID:. If an enterprise object implements the initWithEditingContext:classDescription:globalID: method, the Framework uses this method instead of init, allowing the object to affect its creation based on the data provided.
EOEditingContext anEOEditingContext,
EOClassDescription anEOClassDescription,
EOGlobalID anEOGlobalID)
In Java:
public void pay()In Objective-C:
{
setDatePaid(new NSGregorianDate());
// Notify the rental that this fee has been paid.
rental.feePaid();
}
- (void)payWhen you implement a method such as this in your enterprise object class, you don't have to be concerned with registering the changes or updating the values displayed in the user interface. An Enterprise Objects Framework application revolves around the graph of enterprise objects, and the Framework assumes responsibility for making sure that all parts of your application are notified when an object's value changes. Thus, all parts of your application have the same view of the data and remain in sync.
{
[self setDatePaid:[[NSCalendarDate calendarDate] retain];
// Notify the rental that this fee has been paid.
[rental feePaid];
}
Table of Contents Next Section