2011年9月23日 星期五

Lala's Program Note 實作記錄: 28. Core Data Custom Validation

since: 2011/09/23
update: 2011/09/24

參考: Core Data Programming Guide: Managed Object Validation

備註: 已不採用此方式, 僅供參考.
         目前使用: 此方式

A. 說明:

   1. 需要先從 data model 產生出繼承自 NSManagedObject 的類別.
       這已在 Lala's Program Note 實作記錄: 20. search criteria 中的 B. 建立用來表現
       managed object 的客製物件: 建好了 NoteBook entity 與 NoteArticle entity 的類別.

----------------------------------------------------------------------------------------------------------

   2. Property-Level Validation:
       以下三種自行覆寫的方法, 由於 (id *)ioValue / (id *)value 是一個物件參照的指標,
       允許對輸入的值作更改, 因此在 Apple 的文件中也不建議使用, 因為可能造成
       記憶體管理的問題.
      - (BOOL)validate<AttributeName>:(id *)ioValue error:(NSError **)outError;
      - (BOOL)validateValue:(id *)value forKey:(NSString *)key error:(NSError **)error;
      - (BOOL)validateValue:(id *)ioValue forKeyPath:(NSString *)inKeyPath error:(NSError **)outError;
     
----------------------------------------------------------------------------------------------------------

   3. Inter-Property validation:
      a. 在此採用覆寫以下三個方法來做資料驗證的工作.(目前僅處理: InsertUpdate)
         - (BOOL)validateForInsert:(NSError **)error;
         - (BOOL)validateForUpdate:(NSError **)error;
         - (BOOL)validateForDelete:(NSError **)error;

      b. 在覆寫的方法內, 首先要先呼叫 superclass 的實作, 以取得合適的 Validation.
          ex: BOOL valid = [super validateForInsert:error];

      c. 當發現到有錯誤時, 可利用 errorFromOriginalError:error: 方法來組合所有的錯誤,
          以避免覆寫掉 superclass validation 裡的任何錯誤.
          Apple 預設的 errorFromOriginalError:error: 方法, 有需要也可以自行覆寫.

      d. 預設情況下, validation 要一直到對 managed object 執行 save 時, 才會發生作用,
         如果想提前呼叫的話, 可手動呼叫.
         ex:   NSError *validationError;
                 if (![newNoteBook validateForInsert:&validationError]) {
                     // Invalidate
                 } else {
                     // contine to save ....
                 }


B. 對 NoteBook 資料的 Validate:
   1. 開啟 NoteBook.h 檔案, 新增 method 如下:
//@add
- (BOOL)validateBookName:(NSError **)error;
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError;


   2. 開啟 NoteBook.m 檔案, 新增 method 如下
//@add
- (BOOL)validateForInsert:(NSError **)error {
    BOOL superValid = [super validateForInsert:error];
    BOOL bookNameValid = [self validateBookName:error];
   
    return (superValid && bookNameValid);
}

----------------------------------------------------------------------------------------------------------

//@add
- (BOOL)validateForUpdate:(NSError **)error {
    BOOL superValid = [super validateForUpdate:error];
    BOOL bookNameValid = [self validateBookName:error];
   
    return (superValid && bookNameValid);
}

----------------------------------------------------------------------------------------------------------

//@add
- (BOOL)validateBookName:(NSError **)error
{
    BOOL valid = YES;
    // p.s. name is NoteBook model's attribute
    if (self.name != nil) {
        if (([self.name length] < 1) || ([self.name length] > 20)) {
            valid = NO;
            if (error != NULL) {
                NSBundle *myBundle = [NSBundle bundleForClass:[self class]];

                NSString *bookNameErrorString = [myBundle localizedStringForKey:@"StringLengthError" value:@"Book name length is too long or 0." table:@"BookNameErrorStrings"];
               
                NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];

                [userInfo setObject:bookNameErrorString forKey:NSLocalizedFailureReasonErrorKey];

                [userInfo setObject:self forKey:NSValidationObjectErrorKey];
               
                NSError *bookNameError = [NSError errorWithDomain:@"NoteBook" code:NSManagedObjectValidationError userInfo:userInfo];
               
                // if there was no previous error, return the new error
                if (*error == nil) {
                    *error = bookNameError;
                }
                // if there was a previous error, combine it with the existing one
                else {
                    *error = [self errorFromOriginalError:*error error:bookNameError];
                }
            }
        }
    }
   
    return valid;
}

----------------------------------------------------------------------------------------------------------

//@add
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError
{
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    NSMutableArray *errors = [NSMutableArray arrayWithObject:secondError];
   
    if ([originalError code] == NSValidationMultipleErrorsError) {
        [userInfo addEntriesFromDictionary:[originalError userInfo]];
        [errors addObjectsFromArray:[userInfo objectForKey:NSDetailedErrorsKey]];
    }
    else {
        [errors addObject:originalError];
    }
   
    [userInfo setObject:errors forKey:NSDetailedErrorsKey];
   
    return [NSError errorWithDomain:NSCocoaErrorDomain
                               code:NSValidationMultipleErrorsError
                           userInfo:userInfo];
}


   3. 當輸入的 NoteBook 名稱長度大於 20 或者沒有輸入時, Debug area 就會出現
      以下的錯誤訊息, 並且程式會終止(crash? 以後再作錯誤訊息的處理)
      "The operation couldn't be completed. Book name length is too long or 0."
       "NSLocalizedFailureReason = "Book name length is too long or 0.";


C. 對 NoteArticle 資料的 Validate:
   1. 開啟 NoteArticle.h 檔案, 新增 method 如下:
//@add
- (BOOL)validateArtitleTitle:(NSError **)error;
- (BOOL)validateArtitleContent:(NSError **)error;
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError;

   2. 開啟 NoteArticle.m 檔案, 新增 method 如下
//@add
- (BOOL)validateForInsert:(NSError **)error {
    BOOL superValid = [super validateForInsert:error];
    BOOL artitleTitleValid = [self validateArtitleTitle:error];
    BOOL artitleContentValid = [self validateArtitleContent:error];
   
    return (superValid && artitleTitleValid && artitleContentValid);
}

----------------------------------------------------------------------------------------------------------

//@add
- (BOOL)validateForUpdate:(NSError **)error {
    BOOL superValid = [super validateForUpdate:error];
    BOOL artitleTitleValid = [self validateArtitleTitle:error];
    BOOL artitleContentValid = [self validateArtitleContent:error];
   
    return (superValid && artitleTitleValid && artitleContentValid);
}

----------------------------------------------------------------------------------------------------------

//@add
- (BOOL)validateArtitleTitle:(NSError **)error
{
    BOOL valid = YES;
   
    // p.s. title is NoteArtitle model's attribute
    if (self.title != nil) {
        if (([self.title length] < 1) || ([self.title length] > 20)) {
            valid = NO;
            if (error != NULL) {
                NSBundle *myBundle = [NSBundle bundleForClass:[self class]];
               
                NSString *artitleTitleErrorString = [myBundle localizedStringForKey:@"StringLengthError" value:@"Artitle title length is too long or 0." table:@"ArtitleTitleErrorStrings"];
               
                NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
               
                [userInfo setObject:artitleTitleErrorString forKey:NSLocalizedFailureReasonErrorKey];
               
                [userInfo setObject:self forKey:NSValidationObjectErrorKey];
               
                NSError *artitleTitleError = [NSError errorWithDomain:@"NoteArtitle" code:NSManagedObjectValidationError userInfo:userInfo];
               
                // if there was no previous error, return the new error
                if (*error == nil) {
                    *error = artitleTitleError;
                }
                // if there was a previous error, combine it with the existing one
                else {
                    *error = [self errorFromOriginalError:*error error:artitleTitleError];
                }
            }
        }
    }
   
    return valid;
}

----------------------------------------------------------------------------------------------------------

//@add
- (BOOL)validateArtitleContent:(NSError **)error
{
    BOOL valid = YES;
   
    // p.s. content is NoteArtitle model's attribute
    if (self.content != nil) {
        if ([self.content length] < 1) {
            valid = NO;
            if (error != NULL) {
                NSBundle *myBundle = [NSBundle bundleForClass:[self class]];
               
                NSString *artitleContentErrorString = [myBundle localizedStringForKey:@"StringLengthError" value:@"Artitle content length is 0." table:@"ArtitleContentErrorStrings"];
               
                NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
               
                [userInfo setObject:artitleContentErrorString forKey:NSLocalizedFailureReasonErrorKey];
               
                [userInfo setObject:self forKey:NSValidationObjectErrorKey];
               
                NSError *artitleContentError = [NSError errorWithDomain:@"NoteArtitle" code:NSManagedObjectValidationError userInfo:userInfo];
               
                // if there was no previous error, return the new error
                if (*error == nil) {
                    *error = artitleContentError;
                }
                // if there was a previous error, combine it with the existing one
                else {
                    *error = [self errorFromOriginalError:*error error:artitleContentError];
                }
            }
        }
    }
   
    return valid;
}

----------------------------------------------------------------------------------------------------------

//@add
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError
{
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    NSMutableArray *errors = [NSMutableArray arrayWithObject:secondError];
   
    if ([originalError code] == NSValidationMultipleErrorsError) {
        [userInfo addEntriesFromDictionary:[originalError userInfo]];
        [errors addObjectsFromArray:[userInfo objectForKey:NSDetailedErrorsKey]];
    }
    else {
        [errors addObject:originalError];
    }
   
    [userInfo setObject:errors forKey:NSDetailedErrorsKey];
   
    return [NSError errorWithDomain:NSCocoaErrorDomain
                               code:NSValidationMultipleErrorsError
                           userInfo:userInfo];
}

   3. 當輸入的 NoteArtitle 標題長度大於 20 或者沒有輸入時, Debug area 就會出現
      以下的錯誤訊息, 並且程式會終止(crash? 以後再作錯誤訊息的處理)
      "The operation couldn't be completed. Artitle title length is too long or 0."
       "NSLocalizedFailureReason = "Artitle title length is too long or 0.";

      而當沒有輸入 NoteArtitle 的內容時, Debug area 就會出現以下的錯誤訊息,
      並且程式會終止(crash? 以後再作錯誤訊息的處理)
      "The operation couldn't be completed. Artitle content length is 0."
       "NSLocalizedFailureReason = "Artitle content length is 0.";

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。