Search code examples
objective-crealm

Realm Migration and Optional Properties


I recently upgrade my Realm library from 0.92 (I think) to 0.96.2, but am having some trouble with the new support for ‘optional’ properties. I also need to do a migration for the first time, which is also complicating matters.

The new scheme needed to add a single field to one of the data types, so I coded up a migration that establishes this new property on existing objects:

RLMRealmConfiguration* config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration* migration, uint64_t oldSchemaVersion)
{
    [migration enumerateObjects:Foo.className
                          block:^(RLMObject* oldObject, RLMObject* newObject) {
                              if (oldSchemaVersion < 1)
                              {
                                newObject[@"user"] = @"";
                              }
                          }];
};
[RLMRealmConfiguration setDefaultConfiguration:config];

However, as soon as the code tries to open a Realm, I get an error message about optional property types:

'Migration is required for object type 'Person' due to the following errors:
- Property 'name' has been made optional.
- Property ‘company’ has been made optional.
- Property 'title' has been made optional.
- Property 'phone' has been made optional.
- Property 'email' has been made optional.
- Property 'homeAddress' has been made optional.'

Question #1 - Since the model is going from ‘required’ properties to ‘optional’, there’s guaranteed to be a value already present for existing objects; so I’m struggling to see why a migration is required.

Question #2 - I’d still like to migrate the objects, and nil out the properties if the string is empty, so I coded up a migration:

RLMRealmConfiguration* config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration* migration, uint64_t oldSchemaVersion)
{
    NSLog(@"RUNNING REALM MIGRATION");

    // ...basic migration of adding a new property (above)

    [migration enumerateObjects:Person.className
                          block:^(RLMObject* oldObject, RLMObject* newObject) {
                              if (oldSchemaVersion < 1)
                              {
                                  if ([oldObject[@"name"] length] == 0)
                                      newObject[@"name"] = nil;
                                  else
                                      newObject[@"name"] = oldObject[@"name"];

                                  // … repeat for other properties
                }
                          }];
};
[RLMRealmConfiguration setDefaultConfiguration:config];

However, the migration doesn’t appear to be run; a breakpoint inside the if (oldSchemaVersion < 1) block is never hit, and the "RUNNING REALM MIGRATION" message never prints.

The outer block — setting up the RLMRealmConfiguration — is hit inside application:didFinishLaunchingWithOptions:


Solution

  • The issue seems to be that I'm setting up the default RLMRealmConfiguration with my migration info, but [RLMRealm realmWithPath:] ignores the default configuration.

    Instead of using realmWithPath:, you can copy the default configuration (including your migrationBlock and schemaVersion), set the path property, and pass it to [RLMRealm realmWithConfiguration:error:]

    RLMRealmConfiguration* config = [RLMRealmConfiguration defaultConfiguration];
    config.path = file;
    
    NSError* error = nil;                    
    RLMRealm* realm = [RLMRealm realmWithConfiguration:config
                                                 error:&error];