Chapter 6. Modeling Data in Xcode

WHAT'S IN THIS CHAPTER?

  • Defining entities and their attributes

  • Expressing the relationships between entities

  • Creating fetched properties and fetch requests using the predicate builder

  • Generating custom subclasses of NSManagedObject from your model

In the previous chapter, you learned the fundamentals of working with Core Data. In this chapter, you explore the Xcode Data Modeling tool and learn how to create data models graphically.

In this chapter, you learn how to use the Xcode Data Modeling tool to graphically create your Core Data model. The tool is easy to use and can dramatically speed the development time of your next data-driven application. Think of the Data Modeling tool as Interface Builder for your data.

You can use the tool to model the entities and attributes that you will use in your application. You can also use the tool to define relationships between entities, create fetched properties, and even build fetch requests.

MODELING YOUR DATA

In Chapter 2, you learned about the importance of modeling your data before you begin work on your application. I showed you how I use Omni Graffle to create my Entity-Relationship Diagrams. Then, you explored how to turn those diagrams into an SQLite database. In this chapter, you learn how to use the Xcode Modeling tool to model your data and turn the model into Core Data objects.

The Xcode Modeling tool uses a multi-pane interface, as shown in Figure 6-1. There are three panes at the top of the tool. They are (from left to right) the Entities pane, the Properties pane, and the Detail pane. The Entities pane provides a list of all of the entities in your model. The Properties pane lists the properties of the currently selected entity, and the Detail pane shows details related to whatever is currently selected, either an entity or a property. The bottom pane, called the Diagram view, displays a graphical representation of your model. As you walk through the creation of the various aspects of a data model, you will learn how each pane is used.

The Xcode data modeler

Figure 6.1. The Xcode data modeler

Defining Entities and Their Attributes

In order to show all of the features of the data modeler, I will walk you through the creation of a data model for a catalog application like the one you created in Chapters 1 through 3. Then, at the end of the chapter, you will create an extended version of the Task data model from Chapter 5, which you will use in the example code in Chapter 7.

Figure 6-2 shows the Entity-Relationship Diagram that you developed in Chapter 2. If you recall, this diagram represents the data that you needed to build a catalog application. The application presented a company's product catalog and contained information about the products, manufacturers, and countries of origin of the products. This chapter presents the creation of the catalog data model as a tutorial, but you do not necessarily need to follow along — you will not be using it in code. I am just walking you through the process to demonstrate all of the capabilities of the data-modeling tool. At the end of this chapter, you will create another data model for a task application that you will use going forward into Chapter 7.

Catalog database Entity-Relationship Diagram

Figure 6.2. Catalog database Entity-Relationship Diagram

The main entity in the catalog application is the Product. So, let's create a Product entity in the tool. There are a couple of ways to create entities. You can right-click in the Diagram view and select Add Entity from the contextual menu. You can also create an entity from the menu bar by selecting Design

Catalog database Entity-Relationship Diagram

After you create a new entity, the Diagram view shows the entity as selected: A selected entity is blue and has resize handles. Because you have selected the entity, you can see its details in the Detail pane.

Entity Details

The Detail pane has four tabs — General, User Info, Configurations, and Synchronization — as you can see in Figure 6-3.

Entity detail tabs

Figure 6.3. Entity detail tabs

The General tab contains general information about the selected entity. In the Name field, you can change the name of your entity. Change the name of this entity from Entity to Product.

The class field displays the name of the class that represents the entity in code. The tool sets the class field to NSManagedObject by default. Later in the chapter, when you create custom subclasses of NSManagedObject, you will see the name change to the name of the custom subclass.

Using the parent field, you can implement an inheritance hierarchy in your model between entities like those that you would implement between classes in code. Suppose that all of your products shared common attributes such as Name, Price, and Quantity, but each category of product had different sub-attributes. You could design your model such that different data entities would represent different categories. For example, if your company was selling screws, a screw entity might have attributes such as head type, thread pitch, and length. An entity to model hammers would not need the attributes of screws, but might need attributes such as weight and claw length. You could define the hammer and screw as entities in your model with a parent type of Product. That way, the hammer and screw classes would both inherit all of the attributes and relationships of the parent class, Product, while still being able to define their own unique relationships and attributes.

Create two new entities in the model and call them Hammer and Screw. For each of these new entities, set the Parent in the Detail pane to Product. You will see an inheritance arrow drawn from the subclasses to the superclass, as in Figure 6-4. You can tell Core Data that an entity is abstract by selecting the abstract checkbox. You cannot instantiate abstract classes and can use them only as base classes.

Subclassed entities

Figure 6.4. Subclassed entities

The next tab in the Detail pane is the User Info tab. The NSManagedObject class that you create to represent your Core Data entity in code has a property called entity that returns an NSEntityDescription. The NSEntityDescription has a userInfo property that returns an NSDictionary containing user-defined key-value pairs. The User Info tab allows you to create key-value pairs and associate them with the current entity. At runtime, you can access these values using the userInfo property of the NSEntityDescription. Use the plus at the bottom of the window to add a new key-value pair to the entity and use the minus to remove the selected pair.

The third tab is the Configurations tab. Configurations enable you to name various groups of entities in your data model. You create a configuration using the plus button at the bottom of the tab and delete configurations with the minus button. Once you create a configuration, that configuration is available for all entities. Then you can go through each entity individually and assign it to one or more configurations. At runtime, you can retrieve the entities in a configuration using the entitiesForConfiguration method on the NSManagedObjectModel class. This method will return an NSArray of entities in the chosen configuration. You use Configurations to split the entities in a model across multiple data stores. When developing applications for the iPhone, you will generally use only one data store, so you may not have much use for configurations. However, it is useful to know that you can group entities in the modeler and retrieve your groups at runtime.

The final tab in the Detail pane is the Synchronization tab. You use the Synchronization tab to configure entities for use with Sync Services. You can use Sync Services to synchronize data between a mobile application and a desktop application. Developing a desktop application and using Sync Services is beyond the scope of this book, but if you want more information on using Sync Services, refer to the Apple Sync Services Programming Guide available at http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/SyncServices/SyncServices.pdf.

Adding Attributes

Now that you have created the Product entity, you need to add the attributes of the entity. The Product should have the attributes defined in the Entity-Relationship Diagram, as shown previously in Figure 6-2.

Select the Product entity in the data model. With the entity selected, click the plus icon at the bottom of the Properties pane. A pop-up menu opens, allowing you to add various properties to the Product entity. Select Add Attribute from this pop-up menu to add a new attribute to the entity. The Detail pane tabs that are available when you select an attribute are slightly different from those available when you select an entity, as you can see in Figure 6-5.

Attribute detail tabs

Figure 6.5. Attribute detail tabs

The General tab in the Detail pane allows you to define the name and type of your attribute. Change the name of the attribute that you just added to name. You will store the name of the product in this attribute. Change the type of the name attribute to String.

Depending on the data type that you select for your attribute, the dialog will display different fields that allow you to set additional options for the attribute.

For the numeric types, Integer 16, Integer 32, Integer 64, Decimal, Double, Float, and Date, the pane allows you to enter minimum and maximum values to use for validation. If the user attempts to save an entity with a value that is outside of the specified range, Core Data will raise an error to notify you that a validation rule has been broken. You can also enter a default value for any of the numeric types.

For a String, the pane presents you with Minimum and Maximum length constraints. Again, if the user attempts to save an entity with a string that violates either the minimum or maximum length, Core Data will raise an error to notify you that a validation rule has been broken. There is also a field for entering a default string value.

There is also a field that allows you to build a regular expression that the framework will use to validate the String. Regular expressions are special strings that define a set of rules. For example, you can write a regular expression that constrains the String to be all capital letters, or to allow only certain characters. The framework compares the String that the user enters for the attribute at runtime with the regular expression. If the String fails the comparison, meaning that it does not conform to the rules defined by the regular expression, Core Data will raise an error indicating that a validation rule has failed. A detailed look at regular expressions is beyond the scope of this book.

For Boolean attributes, the only option that you will see in the pane is for the default value. Your options here are YES, NO, or None.

The Binary data type does not have any options. Use this type if you plan to store binary data such as an image in the attribute.

The final data type is Transformable. Transformable attributes enable you to store non-standard data types such as your own custom classes or C structs. Behind the scenes, Core Data converts the attribute into an NSData object using the NSValueTransformer class. This binary data is then stored in the persistent store.

There are three checkboxes below the Name of your attribute. The Optional checkbox allows you to indicate if the attribute is optional or required. If the attribute is not optional, you should provide a default value. If you attempt to save an entity with a non-optional field that is blank, Core Data will raise an error indicating that a validation rule has failed.

You use the Transient checkbox to indicate that you do not want the attribute saved in the persistent store. You use this to retain data in memory that will not be necessary to restore later or data that you compute dynamically at runtime.

The Indexed checkbox sets whether or not the attribute should be indexed in the persistent store. This is particularly important when your backing store is the SQLite database. Selecting this box will cause Core Data to create a database index on the attribute. Proper use of indexes can greatly speed up queries that filter or sort on the indexed attribute. You need to be aware, however, that creating too many indices can reduce performance. You should carefully consider which fields you need indexed. Generally, if you are searching or sorting based on an attribute, you should index on the attribute.

Now that you know how to create attributes, go ahead and create the remaining attributes for the Product entity.

Adding Relationships Between Entities

Now that you have modeled the Product entity, it is time to add the related entities. Create two new entities named Manufacturer and Country. Feel free to add any attributes that make sense for the two new entities. I have added attributes for the name, address, and preferred provider status for the Manufacturer. I have also added attributes for name and region to the Country entity.

If you examine the Entity-Relationship Diagram in Figure 6-2, you will notice that the Product entity and Manufacturer entity are related. Each Product has a Manufacturer. A Manufacturer can have many products. You need to express this relationship in your Core Data model. Including this information in the model will allow Core Data to enforce the relationship and will provide additional functionality when you go to build an application that uses the model. It is important to include as much information as you can when building your model. Core Data will use all of this information at runtime to ensure that the data in your application is consistent with the design expressed in the model.

Select the Product entity. In the Properties pane, click the plus icon and select Add Relationship. This will add a new relationship property to the Product entity. The General tab of the Detail pane now displays options related to a relationship, as you can see in Figure 6-6. In the Name field, change the name of the relationship to "manufacturer."

General tab for relationship details

Figure 6.6. General tab for relationship details

The Optional and Transient checkboxes have the same functionality as for attributes.

The Destination drop-down allows you to select the entity on the receiving end of the relationship. In this case, the manufacturer relationship should point to the Manufacturer entity. So in the destination drop-down, select Manufacturer. Once you set the Destination, you will notice that the tool draws a line between the Product and Manufacturer entities in the Diagram view. The arrowhead on the line points to the destination entity.

You use the Inverse drop-down to set an inverse relationship. In your case, you want to create an inverse relationship between manufacturers and products. To specify this relationship, you must first define a relationship for the Manufacturer entity. In the Manufacturer entity, add a new relationship called products. Make the Destination for this new relationship Product. You will see that there is now a line in the Diagram view pointing from Manufacturer to Product. Select the Product entity and change the Inverse dropdown from "No Inverse Relationship" to "products." The two lines that were in the diagram change to one line with arrowheads on each end. If you select the products relationship in the Manufacturer entity, you can see that the tool has automatically set its inverse to manufacturer. You have established a two-way relationship between these entities.

Selecting the next checkbox defines the relationship as a To-Many Relationship. Let's assume that only one manufacturer makes a specific product, but a manufacturer can make many different products. In the Product entity, you should leave the To-Many checkbox unchecked for the manufacturer relationship because there is only a single manufacturer for a given product. However, you should check the checkbox for the products relationship in the Manufacturer entity because one manufacturer can build many products. Select the Manufacturer entity and then the products relationship. Then, select the To-Many Relationship checkbox. You will see that the arrow that points to the Product entity now has two arrowheads indicating that the Product entity is the destination of a To-Many relationship. Your diagram should now look like Figure 6-7.

Relationships in the Diagram view

Figure 6.7. Relationships in the Diagram view

The next fields in the Detail pane, the Min Count and Max Count, allow you to optionally set the minimum and maximum number of entities contained in the relationship. For the manufacturer relationship, these fields are both set to 1 because the relationship of manufacturers to products is 1 to 1. You cannot change these fields unless you have declared a To-Many relationship. If you select the products relationship in the Manufacturer entity, you will see that these fields default to none. This means the number of products that can be associated with a manufacturer is unlimited.

The final field is the Delete Rule drop-down. You use this to specify what happens when you try to delete the source object in a relationship. The options are No Action, Nullify, Cascade, and Deny.

No Action means that the framework does nothing to the destination object in the relationship. Using No Action is discouraged because this option leaves maintenance of the integrity of the model up to you. Part of the power of Core Data is its ability to manage the integrity of the data model. In the example in this chapter, imagine that you set the manufacturer relationship in the Product entity to No Action. Now, if you deleted a Product entity, its related Manufacturer object will still appear related to the now non-existent product. It would be up to you to remove the now defunct product from the Manufacturer entity. This functionality is automatic when you choose one of the other Delete Rule options.

The Nullify option is the default. This option nulls out the inverse relationship for you automatically. This only works for relationships where the inverse relationship is optional. Consider the previous example with the Delete Rule changed from No Action to Nullify. In this case, if you delete a Product, that product will no longer appear in the Manufacturer's products relationship.

The Cascade option cascades deletes from the source to the destination of the relationship. In this case, let's examine the products relationship of the Manufacturer object. If this relationship had its Delete Rule set to Cascade, deleting a manufacturer would delete all of the products made by that manufacturer.

Finally, the Deny option prevents the deletion of source objects if there are objects at the destination. In the case of the products relationship of the Manufacturer object, if you attempted to delete a manufacturer that still had products, Core Data would raise a validation error. In this case, you would have to manually delete all products associated with a manufacturer before you could delete the manufacturer.

The last aspect of relationships that you will look at is the many-to-many relationship. Imagine that many different countries produce a product and that a country could make many different products. Create a countries relationship in the Product entity and point it to the Country entity. Mark this new relationship as a To-Many relationship. Now, create a products relationship in the Country entity, mark it as a To-Many relationship, and point it to the Product entity. Now, set the inverse relation to products. This may cause you some concern if you are an experienced database developer because in an SQL database, you would have to create a join table to implement this design. Core Data automatically creates the join table behind the scenes in the SQLite database so that you don't need to worry about creating it in your model. You can simply create a many-to-many relationship and let Core Data handle the implementation details for you.

You can also express reflexive relationships in Core Data. A reflexive relationship is a relationship where the source and destination entities are the same. For instance, suppose that you want to keep track of all of the other Product entities related to a specific Product entity. You could create a relatedProducts relationship in the Product entity that has a destination of Product since both the source and destination entities in the relationship are Product entities. For example, suppose that you have a Screw entity and wanted to create a relationship that maps a particular Screw to all other screws of the same type. You could create a relatedScrews relationship that has a Screw entity as both its source and destination. You will see the arrow coming out of the Product entity and then circle around and point right back to itself.

Creating Fetched Properties and Fetch Request Templates

In the previous section, you learned how to create relationships between your entities. Fetched properties work similarly to relationships. An important difference is that you can apply predicates to filter the entities returned by a Fetched Property. For instance, the Product entity has a manufacturer relationship. You must add a Manufacturer to a Product for the relationship to have any meaning. The framework can calculate a Fetched Property without your having to specify which entities meet the criteria. You can use Fetch Request Templates to prepare a fetch request in your model and include variables for resolution at runtime. You can think of Fetch Request Templates like stored queries.

Fetched Properties

Suppose that you wanted to always be able to find a list of cheap products, say products that sold for under $1. You can use a Fetched Property to add a property called cheapProducts to the Products entity. You then specify a predicate that the framework applies to filter your search results. Add a Fetched Property to the Product entity, and call it cheapProducts. Set the destination entity to Product because the Fetched Property should query all of the Product entities. Click the Edit Predicate button to bring up the predicate builder, which you can use to specify your search criteria. Modify the predicate to indicate that you want to retrieve items whose price is less than $1, as shown in Figure 6-8.

Predicate Builder

Figure 6.8. Predicate Builder

In your code, when you have a Product and retrieve the cheapProducts property, you will get back an NSArray of products that cost less than $1.

This predicate works fine for a constant value such as $1. However, suppose that you wanted to be able to get back a list of products that are cheaper than the current product. You can accomplish this by using a variable in your predicate. Change the name of the Fetched Property that you just created to cheaperProducts. Next, click the Edit Predicate button to edit the predicate. You are still going to be comparing against the price field, so leave the left-hand operand alone. If you Control-click (or right-click) in the row, you will get a pop-up menu that displays more options for defining the right-hand operand in your predicate. In this case, you are interested in the top three options: Constant, Variable, and Key.

The field is currently set at a constant of 1.00. However, you do not want to compare to a constant; you want to compare the prices of two different entities. To do this, you might think that you would want to use a Key type of price and compare price versus price. This is incorrect. The Key type always refers to the values contained in the current object under test for inclusion. So, the predicate price<price will never be true. In this situation, you need to use a variable.

Core Data has a special variable called $FETCH_SOURCE, which is a reference to the object executing the Fetched Property. Your predicate should compare the price of the object under consideration to the price of the object executing the property. So, change the field type to Variable and in the Variable text box, type FETCH_SOURCE.price. Be sure to leave off the $ in the beginning of the variable name, as the tool will add it for you. Your Fetched Property should now look like Figure 6-9.

Fetched property with a variable

Figure 6.9. Fetched property with a variable

The predicate builder is not limited to building only simple, one-line filters. You can use Boolean operators such as AND and OR to combine criteria and generate complex predicates. You can add Boolean operators to your predicate by clicking on the plus sign to the right of a line in the predicate builder. It doesn't make much sense, but suppose that you wanted mid-priced products made in China by Manufacturer A or Manufacturer B. You would construct this predicate as in Figure 6-10. The text representation of this predicate is:

((price < 5 OR price > 1) AND ANY countries.name == "China") AND
(manufacturer.name ==
"Manufacturer A" OR manufacturer.name == "Manufacturer B").
Complex predicate

Figure 6.10. Complex predicate

You can also use predefined expressions in your predicate. For instance, you can use the @avg expression to determine the average of a series of numbers or @sum to calculate the sum. You use the expression in the key path of the object that you want to use for your calculation. So, a Fetched Property to return products with below-average prices would use the predicate price < [email protected].

Fetch Request Templates

If you plan to execute the same fetch repeatedly, only changing the value of a variable, you can predefine a Fetch Request template in the data model. You can think of a Fetch Request template as a stored query or view. You can pass variable values into the template at runtime that the framework applies in the predicate.

To create a Fetch Request Template, click the plus icon in the Properties pane and select Fetch Request. The Properties pane will change its filter and show you only fetch requests so you will be able to see the new fetch request that you created.

In the Detail pane, you will see the name of your new fetch request along with the predicate used to define the fetch. Clicking the Edit Predicate button will bring up the Predicate Editor that you explored in the last section.

Suppose that you needed a screen in your application that allowed the user to specify a certain weight and then you wanted to display all hammers of that weight. You could build this screen by fetching all of the hammers and then filter the ones that don't meet your criteria as you create your table cells. However, this would waste memory, as you would be bringing objects into memory that you aren't going to use. Another option would be to define your predicate in code and apply that to the fetch request. That is a perfectly viable option; in fact, that is what a stored fetch request does. Using a stored fetch request can simplify your code because you can define the fetch request in the model instead of having to do it in code.

To add a Fetch Request Template to your Hammer entity, select the Hammer entity and then click the plus icon at the bottom of the Properties pane. Select Add Fetch Request to create the new fetch request. In the Detail pane, change the name of the fetch request to getHammersByWeight. Next, click the Edit Predicate button to create the criteria for the fetch request. In the first drop-down, select the weight property. Select the equal sign as your operator. Right-click next to the last field and change it to a Variable type. You want to define the field as a variable because you will pass the value into the fetch request at runtime. Make the variable name WEIGHT. Click OK to close the dialog. Your predicate will look like this: weight == $WEIGHT.

In your code, you will retrieve the fetch request using the fetchRequestFromTemplateWithName:substitutionVariables: method of the NSManagedObjectModel object. This method accepts the name of your Fetch Request Template and an NSDictionary of key-value pairs used to substitute for the variables in your template.

CREATING CUSTOM NSMANAGEDOBJECT SUBCLASSES

In the previous chapter, you learned how to get data out of an entity retrieved by Core Data using the NSManagedObject class. All instances of entities returned by a fetch request are instances of NSManagedObject. If you remember, you accessed the data inside of your managed object by using key-value coding. Therefore, to access the name field in a Product entity, you would use the valueForKey: method on the NSManagedObject instance that you got back from your fetch request. This method is perfectly fine, but a problem occurs, however, if you incorrectly type the name of the key. At runtime, Core Data would not be able to find the key that you specified and an error would occur. It would be nice if you could have a compile-time check of your field names. This is possible by subclassing NSManagedObject.

There are several advantages to creating custom subclasses of NSManagedObject to represent your data objects. The first is that the subclass provides access to your data fields using properties. If you use the properties to access your data instead of key-value coding, the compiler can verify that the properties exist at compile time. Xcode will also provide code completion help by displaying a list of the properties for an object after you type the period key.

Another advantage to creating a subclass is that you can extend the functionality of your data object beyond simply providing access to your data. Your data objects can implement complex validation rules for single or multiple fields. You could also implement intelligent default values that you cannot express by specifying a default string or number in the data modeler. You can also define calculated fields that are not stored in the data store. You would then write code to calculate the values for these fields at runtime in your custom subclass.

Creating a custom subclass of NSManagedObject for an entity in your model is simple. In your object model, select the entity for which you want to generate a custom subclass. Next, from the Xcode pull-down menu, choose File

CREATING CUSTOM NSMANAGEDOBJECT SUBCLASSES
Managed Object Class Generation dialog

Figure 6.11. Managed Object Class Generation dialog

If you look in the Xcode browser, you will see two new files in your project, Product.h and Product.m. The managed object class generation tool generated these classes for the Product entity based on your data model.

The following is the header code for the Product entity class. Note that the order of the properties in you header may differ from what's shown here.

#import <CoreData/CoreData.h>


@interface Product :  NSManagedObject
{
}

@property (nonatomic, retain) NSNumber * quantityOnHand;
@property (nonatomic, retain) NSString * details;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * image;
@property (nonatomic, retain) NSDecimalNumber * price;
@property (nonatomic, retain) NSManagedObject * manufacturer;
@property (nonatomic, retain) NSSet* relatedProducts;
@property (nonatomic, retain) NSSet* countries;

@end


@interface Product (CoreDataGeneratedAccessors)
- (void)addRelatedProductsObject:(Product *)value;
- (void)removeRelatedProductsObject:(Product *)value;
- (void)addRelatedProducts:(NSSet *)value;
- (void)removeRelatedProducts:(NSSet *)value;

- (void)addCountriesObject:(NSManagedObject *)value;
- (void)removeCountriesObject:(NSManagedObject *)value;
- (void)addCountries:(NSSet *)value;
- (void)removeCountries:(NSSet *)value;

@end
                                                         
Managed Object Class Generation dialog

You can see from the header that the tool defines properties to implement all of the attributes in your model entity. Notice that the relationships return instances of NSSet, so the entities returned from a relationship are unordered. You can also see that the tool generated interface methods to allow you to add and remove entities from the relatedProducts and countries relationships.

The following is the code for the Product implementation:

#import "Product.h"


@implementation Product

@dynamic quantityOnHand;
@dynamic details;
@dynamic name;
@dynamic image;
@dynamic price;
@dynamic manufacturer;
@dynamic relatedProducts;
@dynamic countries;

@end
                                                         
Managed Object Class Generation dialog

The tool uses the @dynamic keyword instead of @synthesize to define properties. @synthesize instructs the compiler to create the getter and setter methods for you, whereas @dynamic tells the compiler that you will create the getter and setters. You don't use @synthesize here because you don't want the compiler to generate these methods for you at compile time; Core Data takes care of generating the getters and setters for you at runtime.

Implementing Validation Rules

Aside from exposing your entity's attributes as properties, another benefit to creating subclasses of NSManagedObject is the ability to add complex custom validation rules. Earlier in the chapter, I covered how to add a simple validation range to an attribute using the modeler. Most data types allow you to set simple validation rules on the general tab of the Properties pane. These rules are limited to min/max values for number and date types, and min/max length or a regular expression for a string type. These options are okay for general use, but they do not give you much flexibility when trying to express complex business logic.

Suppose that your application allowed you to create new products on the device. You might want to be able to prevent the user from entering certain words as part of the name of the product, to avoid embarrassment when showing the catalog off to a client. You could implement a custom validation function that checks the input against a list of inappropriate words before insertion into the database.

The implementation of validation rules for a single field is very straightforward. Core Data will automatically call a method validateNnnn:error:, where Nnnn is the name of your attribute, for every attribute in your class. If the method does not exist, it is not a problem. You are free to implement as many or few of these methods as you want. The method should return a BOOL with a value of YES if the validation is successful and NO if it fails. If the validation fails, it is common practice to return an NSError object to provide further information about why the validation failed.

In the case described, you would implement the function -(BOOL)validateName:(id *)ioValue error:(NSError **)outError. You should notice a couple of things in this method. First, it accepts the input parameter as an id pointer. Because you are receiving a pointer to the input, you could conceivably change the value that you received. You should avoid this, as it is bad design and introduces a side effect to validating a piece of data. Additionally, changing the value within the validation method will cause Core Data to try to validate the value again, possibly causing an infinite loop.

The NSError object that you return is a pointer to a pointer. This is a consequence of the fact that Objective-C passes all objects by value. If you passed the NSError pointer as a regular pointer, you would only be able to modify the NSError that you passed into the method. Accepting a pointer to a pointer allows you to pass back a completely different NSError object than the one that you passed into the method. Therefore, instead of changing the values in the object that you passed in, you can create a new NSError with your own values to return.

Implementing validateNnnn:error: methods work when you are trying to validate the value for one field. Suppose, however, that you need to validate multiple fields simultaneously because they depend on each other's value. Imagine that you wanted to make the details attribute required only when the price of a product was greater than $1.

The framework calls two methods after all of the single field validation methods are completed. These are validateForInsert: for inserting new objects and validateForUpdate: for updates. As in the single field validation, these functions return a BOOL indicating whether validation is successful. You could write a function to check the price field and return NO if the price is greater than $1 and the details field is empty. Then, you could put a call to this function in the validateForInsert: and validateForUpdate: methods so that the rule runs any time a new object is inserted into Core Data or when an existing object is modified.

Implementing Default Values

As you have seen with custom validation rules, it is possible to code rules that are more complex than what you can create in the modeling tool. You can apply the same principle to implementing default values for the attributes of your entity.

Most of the data types allow you to hard-code in a default value in the modeling tool. However, suppose that you want to dynamically determine the default value at runtime. You could also use this technique for setting the default value for Transformable objects because the tool does not allow you to specify a default transformable object.

To implement custom default values, you override the awakeFromInsert method. The framework calls this method after code inserts the object into the context but before the object becomes available for use. You could use this method to set the current date as the default in date fields or for any other purpose where you need to determine your default value at runtime.

CREATING THE TASKS MODEL

Now that you are familiar with Core Data and using the modeler to create a data model, you will build the model for the application that you will code in the next chapter. The application will be a task manager like the one that you built in the previous chapter, but it will have more features to demonstrate some of the functionality of Core Data that you learned about in this chapter.

The Tasks application will implement a feature where you will be able to assign locations to your tasks so that you know what you have to do when you are in a specific location. Tasks will also have due dates that will be used to indicate if a task is overdue. You will also give the task a priority attribute so that you can mark and filter tasks based on priority.

To begin, start a new Xcode Navigation–based project called Tasks. Ensure that you select the "Use Core Data for storage" checkbox. This will generate your template application and the associated data model.

Open up the Tasks.xcdatamodel file to begin editing the data model. Delete the Event entity because you won't be using it in this example. You will be creating your entities from scratch.

Click the plus icon at the bottom of the Entities pane to create a new entity. Rename the new entity "Task."

Next, you will add the attributes for your Task entity. Create a dueDate attribute using the Date data type. You can leave the dueDate marked as optional. To go along with the dueDate, create a Boolean isOverdue attribute. Mark this attribute as Transient as you will dynamically compute its value at runtime and it won't be stored in the data store.

Now, add a required priority attribute of type Integer 16. You will use this to store the priority of the task. Set the minimum value to 0, which you will use to indicate that there is no priority set, and the maximum to 3, indicating high priority. Set the default value to 0.

Last, add a String attribute called text that will be used to store the text of your task. Clear the Optional checkbox and set the Default Value to "Text." Remember that it is wise to specify a default value when defining required attributes.

The next item that you will add to the Task entity is a highPriTasks Fetched Property. You will use this property in the application to get a list of all tasks that are marked as high priority. So, add a new Fetched Property to the Task entity called highPriTasks. Set the destination entity to Task. Edit the predicate so that your filter criteria is priority == 3.

You now need to add a new entity to store the locations where you will perform the task. Add a new entity called Location. Add a string attribute to the Location entity called name. Clear the Optional checkbox because a location without a name would be useless in the application.

Now, you will add a relationship to the Task entity to store the location where the user will perform a task. Select the Task entity, click the plus button below the Properties pane, and select Add Relationship. Set the relationship name to "location." Set the Destination to Location.

Next, you will create an inverse relationship in the Location entity to hold a list of tasks for a particular location. Select the Location entity and add a new relationship called tasks. Set the Destination for this relationship to Task and set the Inverse relationship to location. Also, check the To-Many Relationship checkbox as one location could have many tasks assigned to it.

The last thing that you will do is to add a Fetch Request template to the Task entity to retrieve the list of tasks due sooner than the selected task. Select the Task entity and create a new fetch request using the plus icon at the bottom of the Properties pane. Call the fetch request tasksDueSooner. Click the Edit Predicate button to define the predicate to use in the fetch request. Select the dueDate field from the first drop-down list. Change the equal sign in the middle drop-down to less than (<) because you want to return items with a due date less than the one passed into the fetch request.

Before you set the value in the right-hand pane that you want to test for, you need to change its type from a constant to a variable. Remember that the Predicate Editor defaults the right-hand side to a constant. In this case, however, you will not be testing against a constant. You want to be able to pass in the due date at runtime and return the tasks that occur before that date. To change the type, right- (or Control-) click to the right of the text box to bring up the context menu for the type. Select variable from the pop-up menu. You should see a text label that says Variable next to the text box after you change the type to Variable. In the variable text box, type DUE_DATE. This is the name that you will use in code to replace the variable in the model with an actual location name. Press OK to accept your changes and close the Predicate Editor. Your predicate should be dueDate < $DUE_DATE.

You are finished editing the model. The model should look like Figure 6-12. You can now save the model and quit out of Xcode.

Tasks data model

Figure 6.12. Tasks data model

MOVING FORWARD

In this chapter, you learned how to express your application's data model graphically using the Xcode data modeling tool. You have also laid the framework for building a complete task management application.

By creating custom subclasses of NSManagedObject from your model, you now have the ability to access the data in your object model as properties of an Objective-C class. You also now know how to use the predicate builder to easily generate complex queries on the data in your model.

You are now ready to combine the knowledge that you learned from the previous chapter on the Core Data architecture with what you learned in this chapter about defining your data model to build a fully featured Core Data application. You will write the code to implement your Task model in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset