Error codes + error descriptions - ios

I have the following classes in my project:
Car
Truck
Bicycle
Plane
User initializes each class with input. For example, for Car, they initialize with model, make, etc.
I have validation functions that use key value validation to validate all of the properties in each model.
Then, for each validate function, I set the NSError input parameter for that function.
The problem is I have over 20 error codes matching to over 20 error descriptions. For example, if the user did not put a valid Car Model, they get an error code 1000 with error description, "Please input valid car model." Right now, I use a long switch statement inside a function in the parent class of all of these models to match each error code to an error description and create the appropriate nserror object for each sub class validation function. Is there a better and more manageable way to handle the mapping of over 20 error codes to error descriptions?
Also, another question, how specific should an error code be? For example, if car model is nil or car model is not a valid model (not nil, just not valid), should there be a difference in error code between the two.

I don't fully understand your situation, but you should be adding the error description to the at the same time you add the error code, i.e. when you create the NSError object.
[NSError errorWithDomain:#"aDomain" code:1 userInfo:#{NSLocalizedDescriptionKey: #"Please input valid car model."}];
Then when you need to present the error to the user, just grab the description from the error:
NSString *errorText = error.userInfo[NSLocalizedDescriptionKey];

You can use a dictionary. Convert the error codes into NSNumber instances and set the associated values to the error descriptions. Then, when you create the error just get the description from the dictionary.

Related

Puzzling validation error after upgrading from Grails 2.2.5 to 4.0.11

A method which was working for a long time in Grails 2.2.5 has broken after moving to 4.0.11 with a validation error on saving, and the error is a puzzle to me. I have a domain class 'Decline' which has one of its properties 'user', which is of domain class user. As part of the save process I assign the currently logged in user to this property:
Decline decline = new Decline()
decline.policy = policy
decline.declineTime = new Date()
decline.field = field
decline.cause = reason
decline.user = User.getUser()
decline.save(flush:true)
This was working fine in 2.2.5 but now I get the following validation error:
Field error in object 'myapp.pei.Decline' on field 'user.userType': rejected value [DIRECT_CLIENT]; codes [myapp.User.userType.nullable.error.myapp.pei.Decline.user.userType,myapp.User.userType.nullable.error.user.userType,myapp.User.userType.nullable.error.userType,myapp.User.userType.nullable.error.myapp.UserType,myapp.User.userType.nullable.error,user.userType.nullable.error.myapp.pei.Decline.user.userType,user.userType.nullable.error.user.userType,user.userType.nullable.error.userType,user.userType.nullable.error.myapp.UserType,user.userType.nullable.error,myapp.User.userType.nullable.myapp.pei.Decline.user.userType,myapp.User.userType.nullable.user.userType,myapp.User.userType.nullable.userType,myapp.User.userType.nullable.myapp.UserType,myapp.User.userType.nullable,user.userType.nullable.myapp.pei.Decline.user.userType,user.userType.nullable.user.userType,user.userType.nullable.userType,user.userType.nullable.myapp.UserType,user.userType.nullable,nullable.myapp.pei.Decline.user.userType,nullable.user.userType,nullable.userType,nullable.myapp.UserType,nullable]; arguments [userType,class myapp.User]; default message [Property [{0}] of class [{1}] cannot be null]
There are two things which are puzzling about this. Firstly, and more importantly, this appears to be an error saving the User object. But why is it even trying to save the User object? I have assigned an existing User object which it should be using. Secondly, the specific error is 'rejected value [DIRECT_CLIENT]' for field 'user.userType', but the error message is that this field cannot be null. So it's rejecting a value but telling me it cannot be null! The value, incidentally, is of a UserType enum defined thus:
public enum UserType {
ADMIN_USER,ADMIN_OWNER_USER,SUPER_USER,BROKER,DIRECT_CLIENT
}
I wonder what change from version 2.2.5 to 4 (or maybe 3) could have caused this?
It seems there was some change in behaviour of deepValidate between Grails 2.x and 4.x which is causing this, although I don't see why validation of the associated User object should be failing when it can actually be saved OK separately. But what got me past this issue was to set the following in the mapping block for Decline:
user cascadeValidate: 'none'
This ensures that when the Decline object is saved it does not attempt to validate the User as well.

How to find the distinct error messages of model state validation failure in asp.net mvc

I want to filter the Error Messages that gets populated as part of data annotation modelstate validation failure. As in if an array of objects comes as a part of class, and the validation fails for more than one object, I do not want the same message to be added again and again. Instead, I want to find the distinct error messages
string ValidationFailure= string.Join(";", actionContext.ModelState.Values.Distinct().Select(x.ErrorMessage));
But not able to get the required output.
It looks like your attempt is close, but you’re using Distinct on something that’s already unique (Values). Instead, try the following variation:
string ValidationFailure = string.Join(";", actionContext.ModelState.Values.Select(x => x.ErrorMessage).Distinct());
This ensures that you get a distinct list of ErrorMessages.

About saving data into grails databse

This my project code I want to save my data into database.
def save(){
List<Employee> list = Employee.findAllById(session.getAttribute("empId"))
Milestone milestone = new Milestone()
milestone.setMilestone_Date(params.milestone_Date)
milestone.setMilestone_Name(params.milestone_Name)
milestone.setMilestone_Description(params.milestone_Description)
milestone.save()
EmployeeMilestone employeeMilestone=new EmployeeMilestone()
Employee employee = list.get(0)
employeeMilestone.setEmployee(employee)
employeeMilestone.setMilestone(milestone)
employeeMilestone.save()
[employeeMilestones:employeeMilestone]
}
I am getting this error
Error 500: Internal Server Error URI /ProjectTrackerMain/milestone/save Class java.lang.IndexOutOfBoundsException Message Index: 0, Size: 0
You didn't actually ask a question, so this may be a bit vague!
An IndexOutOfBoundsException happens when you try to access something from a collection in a location where there is no "something". For example, maybe you asked for the tenth element in a list, but there are only two. In your case, you're asking for the zeroth (in plain English, "First") element on this line of code:
Employee employee = list.get(0)
and presumably the list is empty. Your error message says "Size: 0". You can't get the first element from a list that has zero elements in it, so that's an index out of bounds exception.
Why is your list 0? That's a different question. You might try printing out
session.getAttribute("empId")
to see if your employee ID is what you expected. You might also look at the data in your database to see if you actually managed to save an employee! One way or another, you're not getting the data you expected, and then you're trying to use it.
In general, using a debugger to look at your elements, or just using "println" along the way to look at values is helpful in debugging problems like this. That way, you'll find out on line 1 that your list of Employees is not what you expected, instead of several lines later when you try to use it!

How to get a meaningful error message back to the view controller with Core Data validation?

Edit: I want to let everyone know that in the month since I posted this I've continued to work on the problem. There's now a repo on github that shows how to easily accomplish this and remain compliant with KVC. There's no reason to avoid Core Data validation on iOS. It may be different than on Mac OS X but it's not arduous or difficult.
I'm in a view controller editing the properties for a Person object. Person is an NSManagedObject subclass. I'm doing early (before save) validation with Core Data. I'm using the documented validateValue:forKey:error: method like this...
NSError *error;
BOOL isValid = [person validateValue:&firstNameString forKey:#"firstName" error:&error];
if (!isValid) {
...
}
And I've got min and max values set in Core Data's model editor in Xcode. When I validate firstName and it's too short I get an error like this...
Error Domain=NSCocoaErrorDomain Code=1670 "The operation couldn’t be completed. (Cocoa error 1670.)" UserInfo=0x8f44a90 {NSValidationErrorObject=<Event: 0xcb41a60> (entity: Event; id: 0xcb40d70 <x-coredata://ADB90708-BAD9-47D8-B722-E3B368598E94/Event/p1> ; data: {
firstName = B;
}), NSValidationErrorKey=firstName, NSLocalizedDescription=The operation couldn’t be completed. (Cocoa error 1670.), NSValidationErrorValue=B}
But there's nothing there that I could display to the user. But the error code is there so I could do something like this...
switch ([error code]) {
case NSValidationStringTooShortError:
errorMsg = #"First name must be at least two characters.";
break;
case NSValidationStringTooLongError:
errorMsg = #"First name is too long.";
break;
// of course, for real, these would be localized strings, not just hardcoded like this
}
This is good in concept but firstName, and other Person properties, is editable on other view controllers so that switch would have to be implemented again on whatever view controller edits firstName. There must be a better way.
Looking at the Core Data docs for Property-Level Validation reveals this...
If you want to implement logic in addition to the constraints
you provide in the managed object model, you should not override
validateValue:forKey:error:. Instead you should implement methods
of the form validate<Key>:error:.
So I implemented validateFirstName:error: in Person.m. And handily, it executes via the existing validateValue:forKey:error: method in the view controller, just as the docs say it will.
But inside validateFirstName:error:, error is still nil even when firstName is too short. When I continue and control returns to the view controller there is an error like at the top of this question but, again, that's too late. I was hoping that by the time control reached validateFirstName:error:, firstName would have been validated against the constraints specified in the model editor and the filled in error object would be passed in via the error parameter. But it's not.
I'm left with two ideas that might lead to a good home for the switch statement...
In Person.m implement a custom method like firstNameValidationForValue:error:. The view controller would call that method. In firstNameValidationForValue:error: call validateValue:forKey:error:. When it returns with an error, construct a meaningful error message, using the switch, create a new NSError object and return that to the view controller for consumption. This works but it deviates from the standard KVC approach.
Remove all the validation from the Core Data model editor in Xcode and perform all of the validation in the methods like validateFirstName:error:. Based on the results, construct a meaningful error message, using the switch, create a new NSError object and return that to the view controller for consumption. This has the advantage that the constraints and the error messages are in the same method. And, unlike the first idea, continues to follow the standard KVC approach.
What would you do? Is there another way?
Edit: Additional detail on the edit cycle...
The edit cycle begins with the user tapping Add. A new Person object is inserted into the MOC. The view displays a form for editing and Cancel and Done buttons appear on the nav bar. The user starts entering data in fieldA, finishes and taps fieldB. Assume fieldA must be valid before continuing. Before fieldB becomes the first responder the validation for fieldA runs. It fails. The view controller displays the error message returned from the validation and fieldA remains the first responder. The user fixes the problem, and taps on fieldB again. Again the validation runs and this time it passes. fieldB becomes the first responder. This "add data/tap another field or tap Next/validate/move to next field or not" process continues.
It's important to know that at any time the user can tap Cancel. In that case, in terms of the MOC, all I have to do is [myMOC rollback];.
#ImHuntingWabbits: If I invoke save instead of validateValue:forKey:error: there is a problem with that approach. Assume the user is entering data in fieldA. The user taps fieldB and the validation for fieldA runs. This validation uses the "save and then parse the error" method. But assume it passes and all the other fields also pass. So now the MOC has been saved. But the user has not tapped Done and could very well press Cancel. If the user taps Cancel then the save must be undone. That might be relatively easy if the model was very simple but could be really complex. In my particular case I would not want to take this approach.
Another Edit
Can we all reconvene here at github: github.com/murraysagal/CoreDataValidationWithiOS I've got a sample app there and perhaps a better description of the problem in the readme. The sample app shows that validation, in general, is not at all difficult on iOS. The app demonstrates how it's possible to get a meaningful error message back to the VC and remain fully KVC compliant.
And it discusses two possible enhancements to Core Data that I'd like some feedback on before putting them on radar.
I would not use Core Data validation for the UI on iOS. Core Data validation was designed for the desktop with bindings and not for iOS.
You will have a far easier time doing the validation in your view controllers and using Core Data validation as a back up instead of trying to wire Core Data validation to the User Interface.
Update
Could you explain why you think it will be easier to implement validation on the View Controller level. While proper validation and error handling is never easy, I can't see a reason why the Core Data level validation should be more complex (besides the issue that validations are possibly performed more than one time, even when it is not required). You also don't answer the case where there is no VC, or when there are more than one VCs handling objects. Also, certain validations can't be performed on VC level (e.g. check for uniqueness of certain properties, which is a pain anyway).
Core Data validation was added for OS X back when there was a fairly tight coupling between Core Data and the user interface. That coupling was called bindings. With bindings, entry into a text field was immediately and "automatically" added to the Core Data entity associated with that field.
Further, when that data got updated, Core Data could "respond" with validation back to the text field so that the text field could reject the data entry and explain why it was rejected.
Those bindings do not exist on iOS. They need to be written, by hand, for every data entry point.
Since we are writing those check points anyway, there is no reason to try and hook into a generic system like Core Data's validation when we can write far more focused validation directly into the view controllers and save ourselves a level of abstraction.
In the case of data importing, we again have a controller handling the import. There is no direct wiring between the import controller and Core Data anyway so again there in reason to try and plug into a generic system like Core Data's validation when we can write focused code to solve the issue.
Generic systems leak edge cases. If someone has taken the time to cover most of those edge cases (as in the case of Core Data and bindings on OS X) then go ahead and use them. But if you must cover those edges or the connective code yourself anyway, there is very little value in integrating into a generic system. This is especially true of the case where the generic system was not designed to handle the use case (such as Core Data validation and iOS).
There are several parts of Core Data that pre-date iOS and do not fit into iOS very well. Validation is one of them.
Core Data generates validation errors which can be used to automatically compose human readable error messages. It just requires some effort.
The basic structure and the contained information in these errors is consistent, so that we can automate the process.
In order to demonstrate a solution, let's start with a contrived example to understand what Core Data errors are about:
Given:
A managed object model with an entity "User" with the following attributes, types and constraints defined in the model:
User:
name: NSString optional:no
email: NSString optional:no, min_length:3
A managed object subclass User:
#interface User : NSManagedObject
+ (User*) createWithParameters:(NSDictionary*)parameters
inManagedObjectContext:(NSManagedObjectContext*)context;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain, readonly) NSString * user_id;
#property (nonatomic, retain) NSString * email;
#end
We can use a NSDictionary, representing input values for a managed object subclass "User", and save the managed object context:
For example:
NSDictionary* user = #{
#"name": #"John Appleseed",
#"user_id": #"634aa621-c63d-4085-aa27-bc3f3b02bcda",
#"email": #"e
};
Note:
With the values above, we expect to get a validation error for attribute "email", since we have a constraint which requires the string to be at least 3 characters long.
When we try to save and print the error,
NSError* error;
if (![context save:&error]) {
NSLog(#"ERROR: %#", error);
}
we get something like this (prettified for readability):
Error Domain=NSCocoaErrorDomain
Code=1670
"The operation couldn't be completed. (Cocoa error 1670.)"
UserInfo=0x2917570 {
NSValidationErrorObject=<User: 0x28bbc20> (...),
NSValidationErrorKey=email,
NSLocalizedDescription=The operation couldn\U2019t be completed. (Cocoa error 1670.),
NSValidationErrorValue=e
}"
This error contains the following pieces of information:
The error domain (error.domain), which equals always NSCocoaErrorDomain for Core Data related errors.
The error code (error.code), which equals 1670. This code indicates the actual kind of error (see below).
A (useless) error description, obtainable via error.userInfo[NSLocalizedErrorDescriptionKey]
The actual managed object (error.userInfo[NSValidationObjectErrorKey])
The name of the attribute (error.userInfo[NSValidationKeyErrorKey])
The value of this attribute (error.userInfo[NSValidationValueErrorKey])
As we can see, the error description "The operation couldn't be completed. (Cocoa error 1670.)" is quite useless for a meaningful error description which can be presented to a user. A user would ask, "What actually did go wrong!?"
This important detail is hidden on the error code, which is in this case 1670.
We can look up the meaning of this error code in header <CoreData/CoreDataErrors.h>:
enum {
NSManagedObjectValidationError = 1550, // generic validation error
NSValidationMultipleErrorsError = 1560, // generic message for error containing multiple validation errors
NSValidationMissingMandatoryPropertyError = 1570, // non-optional property with a nil value
NSValidationRelationshipLacksMinimumCountError = 1580, // to-many relationship with too few destination objects
NSValidationRelationshipExceedsMaximumCountError = 1590, // bounded, to-many relationship with too many destination objects
NSValidationRelationshipDeniedDeleteError = 1600, // some relationship with NSDeleteRuleDeny is non-empty
NSValidationNumberTooLargeError = 1610, // some numerical value is too large
NSValidationNumberTooSmallError = 1620, // some numerical value is too small
NSValidationDateTooLateError = 1630, // some date value is too late
NSValidationDateTooSoonError = 1640, // some date value is too soon
NSValidationInvalidDateError = 1650, // some date value fails to match date pattern
NSValidationStringTooLongError = 1660, // some string value is too long
NSValidationStringTooShortError = 1670, // some string value is too short
NSValidationStringPatternMatchingError = 1680, // some string value fails to match some pattern
...
}
So, error code 1670 means "some string value is too short".
In order to exploit this information, we need to setup our own error description table, possibly localized for different languages. I don't want to go into detail how this can be accomplished - it may depend on how you present and compose error messages, but you should get the idea.
For example, we retrieve the following localized error description for error code 1670:
`#"The string for attribute '%#' is too short."`
Then, the human readable error description can be composed as follows:
NSString* descFormat = [CoreDataValidationErrorHelper errorDescriptionForCode:error.code];
NSString* desc = [NSString stringWithFormat:descFormat, error.user[NSValidationKeyErrorKey]];
which should print:
Error: "The string for attribute 'email' is too short."
You can tailor this basic approach for your own needs.
Maybe you've already considered this, but it seems you could you override NSManagedObject's - (BOOL)validateForUpdate:(NSError **)error method in your Person class, and call super's implementation to have the MOC validation performed (i.e. have Core Data figure out if your properties fit the restrictions of your data model), and if that returns an error you could do some quick logic to narrow down exactly what the error is and include your own, more verbose, NSError object for use by your view controller in the result.
Discussion
NSManagedObject’s implementation iterates through all of
the receiver’s properties validating each in turn. If this results in
more than one error, the userInfo dictionary in the NSError returned
in error contains a key NSDetailedErrorsKey; the corresponding value
is an array containing the individual validation errors. If you pass
NULL as the error, validation will abort after the first failure.
Important: Subclasses should invoke super’s implementation before
performing their own validation, and should combine any error returned
by super’s implementation with their own (see “Managed Object
Validation”).
Based on Apple's documentation I believe this would only get called on a MOC save, but I'm sure you could call it manually to perform your validation ahead of the save as well.
Downsides of this approach are that validateUpdate: would get called a minimum of twice for a normal save - once manually and once during the MOC save - so it's a bit inefficient. Also individual pieces of validation logic would be duplicated as well - once by super's implementation and the other by your custom logic figuring out what custom error to return (i.e. you'd be re-checking that firstName was indeed what failed validation).
Edit: Also, there's nothing in the documentation to suggest this works, but have you tried calling [super validateFirstName:] in your subclass' implementation to see if that performs Core Data's validation and gives you the opportunity to return a better NSError? My guess is this won't work but it's worth a shot since it'd be quick to test.
This is possible but you'll have to write some fairly sophisticated code to unroll the NSError that comes back from -save:
But it is possible to use that to look up the object in your view controller (or use the one that comes back in the error) and generate a suitable error message. You can also use the key in the error to show UI around the exact field that is causing the issue.
Validation errors have a detailed NSError object that can be used to generate human readable error messages.
2014-01-16 10:24:04.983 Untitled[35743:507] Problem: Required
Object ID: 0x7fc173c20170 <x-coredata:///Entity/tA9E1ADE0-CFDF-49D6-AAAA-38CDC935C0982>
Value: (null)
Here's the code I used to generate that in CodeRunner:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
NSEntityDescription *entity = [[NSEntityDescription alloc] init];
[entity setName:#"Entity"];
NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
[attr setName:#"Required"];
[attr setAttributeType:NSStringAttributeType];
[attr setOptional:NO];
[entity setProperties:#[attr]];
[model setEntities:#[entity]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:psc];
NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:#"Entity" inManagedObjectContext:moc];
NSError *e = nil;
//the process is the same for validating without save, i.e.
//if ([obj validateForInsert:&e] == NO) ... display error
[moc save:&e];
NSLog(#"Problem: %#\nObject ID: %#\nValue: %#", [[e userInfo] objectForKey:#"NSValidationErrorKey"], [[[e userInfo] objectForKey:#"NSValidationErrorObject"] objectID], [[e userInfo] objectForKey:#"Required"]);
}
}
Your switch statement could simply pick apart the error object based on the type of error that occurred, you'll have to make sure you cover the multiple error case as well.
FWIW the difference between doing this pre-save yourself or by calling -save is semantic. You'll be implementing the same iterative code that NSManagedObjectContext uses to validate your object graph.
You are halfway there. Getting en error message back would just need some changes.
There is a post here about Custom Attribute Validation that explain how to move the validation inside the entity definition. The viewController will then call the validation
Entity
You overwrite the validate<Attribute>:error method and create the error message and the error code. In this example the error core is -1 for missing, to short and to long. And there is only one message ("Not valid"). Of corse you can assign separate codes and messages as needed.
For the FirstName it would look similar to the following.
#interface Person
extern NSString* const _kErrorDomain
-(BOOL)validateFirstName:(NSString **)value error:(NSError **)error;
#end
#implementation Person
NSString* const _kErrorDomain = #"ourAppDomain";
-(BOOL)validateFirstName:(NSString **)value error:(NSError **)error {
if (value == nil || [value length] == 0 || [value length] > toLong) {
// validation fails
// create userInfo Dictionary
NSDictionary *userInfoDict = #{NSLocalizedDescriptionKey :
#"Not valid"]; // <= text message for User
// set NSError
if (error != NULL)
*error = [[NSError alloc] initWithDomain:_kErrorDomain // <= our code created the NSError
code:-1 // <= code for app logic
userInfo:userInfoDict];
return NO; // fails validation
}
return YES; // validated ok
}
Unless you would want to get fancy there is little need to override validateForUpdate:&error in your entity
ViewController
In my code my viewController calls validateForUpdate:&error and will validate the complete Person. You might want to call validateFirstName:error, the validation should be the same. [*]
// ViewController
-(void)checkPerson:(Person *)myPerson {
NSError *error;
if ([myPerson validateForUpdate:&error]) {
// all is well
error = nil; // zap any previous error
} else {
// validation failed
if ([[error domain] isEqualToString:_kErrorDomain]) { // error is from our code
// display error
NSLog(#"Error message received: %#", [error localizedDescription ]);
// handle the errorCode
if ([error errorCode] == -1) // do something
else // do something else
} else {
// error is not our domain. If needed, then take care of the others, too
}
} // end if/then validateForUpdate
}
The above will keep the validation logic with the entity, while the viewController concentrates on the user. The validation method can be recycled for multiple viewControllers
[*]:
If you do call validateForUpdate:&error and more than one attribute is failing validation (NSValidationMultipleErrorsError) then you will receive an array of errors (NSDetailedErrorsKey)
[This started as a comment on Marcus's answer, but got too long.]
It would help to understand how validation is different on OS X when using bindings, in order to understand why it's different on iOS. With bindings, a typical logic flow would be something like:
User types data into a text field
Text field uses its bindings to see if what the user typed is valid
If the value is valid, no problem. The value can be set on the managed object.
If the value is not valid, alter the UI appearance to indicate this, and never set this invalid value on the managed object.
This all happens with no code. A key detail here is that this never leads to a validation failure when saving changes, because it's all checked in advance. By the time you call save:, you know you at least have valid data.
The point of validation, when designed, was to dynamically validate user input against the model to ensure that you never attempted to save invalid data. Invalid data will prevent saving, but that's not really the way validation was meant to be used. Although validation values are stored in the model, using those values is a controller-level step.
When you're on iOS you don't have UI bindings, so if you need to validate values, you need to implement your own code to check every update as it happens. If you don't, and you get invalid input, you won't be able to save changes. You'll get an NSError that you have to figure out how to untangle to find out what went wrong. This gets very messy, fast, and is not guaranteed to lead to a useful result.
So, what to do? Check the values on your own, in your view controller. You could use the standard validation methods if you are extremely careful to always duplicate the process described above. Don't let validation wait until you're saving changes-- do it immediately after you get user input. Don't ever set an invalid value on one of your managed objects. If you detect one, advise the user, and don't pass the value through to the model. Basically, make damn sure that you're doing the validation in a controller instead of relying on the model to handle it.
It's safer and less error prone to handle this outside of the standard validation methods. As soon as you implement one of those, you're risking a failed save from validation checks. Implementing your own custom validation methods avoids that, though you still need to be careful to call those methods at the right times.
If it's possible to edit the same value in more than one place, then yeah, either of these options leads to code duplication. That's life-- you don't want to duplicate code, but in practice it's sometimes necessary. Leaving validation until you save is asking for failed saves for confusing reasons.
Many of the posters here have a misconception about model validation.
If you have a required field that should be longer than 3 letters you should expect to receive two different validation errors for the same field.
Now error for required field would be a nil value, which is a programmer's mistake and if user entered less than 3 letters, this is user input error. My point is that validation error is not meant to be used in user interface directly, instead it should provide a foundation to produce an error that would make sense for user.
So what you want is to implement a helper method that takes a validation error and produces a list of meaningful NSErrors, grouped by field name, ready for presentation in user interface. Based on context you may want to present slightly different messages.
Luckily CoreData returns a lot of information along with validation error, so it's just a matter of extra effort to extract information from userInfo dictionary.
Please check the method setValidationPredicates in NSPropertyDescription (which is the super class for NSAttributeDescription). You may specify the validation criteria and the corresponding warnings for various attributes for entities.
From the Apple documentation:
The validationPredicates and validationWarnings arrays should contain the same number of elements, and corresponding elements should appear at the same index in each array.
Instead of implementing individual validation methods, you can use this method to provide a list of predicates that are evaluated against
the managed objects and a list of corresponding error messages (which
can be localized).
Special Considerations This method raises an exception if the receiver’s model has been used by an object graph manager.
I am about to implement this, and shall update more on this after testing this approach.

validation before attribute setters can type cast

I have an object with an attribute called value which is of type big decimal. In the class definition i have validates_numericality_of.
However if i:
a.value = 'fire'
'fire' ends up getting typecast to the correct type before the validation fires so:
a.valid? => true
How do get the validation to fire before the typecast?
Thanks
Dan
From ActiveRecord::Base docs:
Sometimes you want to be able to read
the raw attribute data without having
the column-determined typecast run its
course first. That can be done by
using the <attribute>_before_type_cast
accessors that all attributes have.
For example, if your Account model has
a balance attribute, you can call
account.balance_before_type_cast or
account.id_before_type_cast.
This is especially useful in
validation situations where the user
might supply a string for an integer
field and you want to display the
original string back in an error
message. Accessing the attribute
normally would typecast the string to
0, which isn’t what you want.
A new gem has been created to help validate types in rails.
An explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code could be:
class SomeObject < ActiveRecord::Base
validates_type :value, :big_decimal
end
This will throw an exception when anything except a float is assigned to value instead of quietly casting the value to a BigDecimal and saving it.

Resources