Stephan Sloth Stephan Sloth - 5 months ago 50
Objective-C Question

Realm.io - Compound primary key in objective-c

I'm struggling to find a way of combining multiple properties into a primary key, using Realm.io and Objective-c.

Take this object as an example:

@interface Beacon : RLMObject

@property NSString *uuid;
@property int major;
@property int minor;

@end

RLM_ARRAY_TYPE(Beacon)


How would I then combine e.g. uuid, major, and minor into a single primary key?

Answer

Here's an Objective-C solution. It makes use of the fact that only ignoredProperties can be overridden. It's a bit more verbose than the Swift version, however, it works, so if you need a compound key in ObjC, this is likely your best bet. One likely limitation is that KVO for the public properties won't work, however, since Realm expects primary keys to remain constant after the object is added to a Realm, this limitation is likely minor. If you did want KVO, you could likely get around it by registering the dependent keys as described in this document from Apple.

In CompositeKeyObject.h:

@interface CompositeKeyObject : RLMObject

@property (nonatomic, strong) NSString* partOne;
@property (nonatomic, strong) NSString* partTwo;

@end

In CompositeKeyObject.m:

@interface CompositeKeyObject ()

@property (nonatomic, strong) NSString* partOneValue;
@property (nonatomic, strong) NSString* partTwoValue;
@property (nonatomic, strong) NSString* compositeKey;

@end

@implementation CompositeKeyObject

- (instancetype)initWithValue:(id)value
{
    // Need to make a copy and set the correct "value" properties.
    // Otherwise, the object won't be created properly. 
    NSMutableDictionary* valueCopy = [value mutableCopy];
    if(valueCopy[@"partOne"] != nil) {
        valueCopy[@"partOneValue"] = valueCopy[@"partOne"];
    }
    if(valueCopy[@"partTwo"] != nil) {
        valueCopy[@"partTwoValue"] = valueCopy[@"partTwo"];
    }

    self = [super initWithValue:[valueCopy copy]];
    if(self != nil) {
        // Make sure primary key is in sync.
        [self updatePrimaryKey];
    }
    return self;
}

- (void)setPartOne:(NSString *)partOne
{
    self.partOneValue = partOne;
    [self updatePrimaryKey];
}

- (void)setPartTwo:(NSString *)partTwo
{
    self.partTwoValue = partTwo;
    [self updatePrimaryKey];
}

- (NSString*)partOne
{
    return self.partOneValue;
}

- (NSString*)partTwo
{
    return self.partTwoValue;
}

- (void)updatePrimaryKey
{
    self.compositeKey = [NSString stringWithFormat:@"%@ <><><> %@", self.partOne, self.partTwo];
}

+ (NSString *)primaryKey
{
    return @"compositeKey";
}

+ (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
    predicateFormat = [predicateFormat stringByReplacingOccurrencesOfString:@"partOne" withString:@"partOneValue"];
    predicateFormat = [predicateFormat stringByReplacingOccurrencesOfString:@"partTwo" withString:@"partTwoValue"];
    return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}

+ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args {
    predicateFormat = [predicateFormat stringByReplacingOccurrencesOfString:@"partOne" withString:@"partOneValue"];
    predicateFormat = [predicateFormat stringByReplacingOccurrencesOfString:@"partTwo" withString:@"partTwoValue"];
    return [self objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}

@end

And a unit tests:

- (void)testCompositeObject
{
    CompositeKeyObject* object = [[CompositeKeyObject alloc] init];
    object.partOne = @"ONE";
    object.partTwo = @"TWO";

    RLMRealm* realm = [RLMRealm defaultRealm];
    [realm transactionWithBlock:^{
        [realm addObject:object];
    }];

    XCTAssertNotNil([CompositeKeyObject objectForPrimaryKey:@"ONE <><><> TWO"]);

    object = [[CompositeKeyObject alloc] init];
    object.partOne = @"ONE";
    object.partTwo = @"TWO";

    [realm transactionWithBlock:^{
        XCTAssertThrows([realm addObject:object]);
    }];
}

- (void)testCompositeObject2
{
    CompositeKeyObject* object = [[CompositeKeyObject alloc] initWithValue:@{@"partOne": @"ONE", @"partTwo": @"TWO"}];

    RLMRealm* realm = [RLMRealm defaultRealm];
    [realm transactionWithBlock:^{
        [realm addObject:object];
    }];

    XCTAssertNotNil([CompositeKeyObject objectForPrimaryKey:@"ONE <><><> TWO"]);
    object = [[CompositeKeyObject alloc] initWithValue:@{@"partOne": @"ONE", @"partTwo": @"TWO"}];

    [realm transactionWithBlock:^{
        XCTAssertThrows([realm addObject:object]);
    }];
}