Create a gist now

Instantly share code, notes, and snippets.

@ddb /entity.m
Created Feb 10, 2011

What would you like to do?
//
// ObjCEntity.m
// Entity
//
// Created by dbrown on 2/10/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "ObjCEntity.h"
@interface ObjCEntity (private)
+ (NSArray*)findFromContext:(NSManagedObjectContext*)moc
withEntity:(NSString*)ename
withPredicate:(NSPredicate*)pred
options:(NSDictionary*)opts;
+ (NSPredicate*)extractPredicate:(NSDictionary*)opts;
@end
@implementation ObjCEntity
NSString* entityName = @"";
+ (void)initialize {
entityName = NAMEOF([self class]);
}
+ (NSArray*)findFirstWithContext:(NSManagedObjectContext*)moc
options:(NSDictionary*)opts {
NSPredicate* pred = [self extractPredicate:opts];
NSMutableDictionary* newOpts = [opts mutableCopy];
[newOpts setValue:[NSNumber numberWithInt:1] forKey:@"limit"];
return [self findFromContext:moc
withEntity:entityName
withPredicate:pred
options:newOpts];
}
+ (NSArray*)findAllWithContext:(NSManagedObjectContext*)moc
options:(NSDictionary*)opts {
NSPredicate* pred = [self extractPredicate:opts];
return [self findFromContext:moc
withEntity:entityName
withPredicate:pred
options:opts];
}
+ (NSArray*)findFromContext:(NSManagedObjectContext*)moc
withEntity:(NSString*)ename
withPredicate:(NSPredicate*)pred
options:(NSDictionary*)opts {
NSFetchRequest* fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:ename
inManagedObjectContext:moc];
[fetchRequest setEntity:entityDescription];
[fetchRequest setPredicate:pred];
NSNumber* offset = [opts objectForKey:@"offset"];
NSNumber* limit = [opts objectForKey:@"limit"];
if (offset) {
[fetchRequest setFetchOffset:[offset intValue]];
}
if (limit) {
[fetchRequest setFetchLimit:[limit intValue]];
}
NSError* error;
return [moc executeFetchRequest:fetchRequest error:&error];
}
+ (NSPredicate*)extractPredicate:(NSDictionary*)opts {
NSMutableArray* preds = [NSMutableArray array];
NSMutableArray* pred_opt = [NSMutableArray array];
NSMutableArray* byLines = [NSMutableArray array];
for (NSString* key in [opts allKeys]) {
if ([key hasPrefix:@"by_"]) {
NSString* searchKey = [key substringFromIndex:3];
[preds addObject:[NSString stringWithFormat:@"%@ = %%@", searchKey]];
[pred_opt addObject:[opts valueForKey:key]];
[byLines addObject:key];
} else if ([key isEqualToString:@"conditions"]) {
NSArray* cond = [opts objectForKey:key];
[preds addObject:[cond objectAtIndex:0]];
[pred_opt addObject:[cond subarrayWithRange:NSMakeRange(1, [cond count] - 1)]];
[byLines addObject:key];
}
}
if ([preds count] == 0) {
return nil;
}
NSString* predString = [preds componentsJoinedByString:@" AND "];
NSPredicate* result = [NSPredicate predicateWithFormat:predString argumentArray:pred_opt];
[opts removeObjectsInArray:byLines];
return result;
}
@end

I'm thoroughly impressed; that's some very nice code, and it'll give me grist to learn more Objective C, sort of like a rosetta stone to the MacRuby code. :) So you'd call this with something like:

cats = [Category findAllWithContext:context
                    options:[NSDictionary dictionaryWithObjectsAndKeys:categoryName, @"by_name",
                    [NSNumber numberWithInt:20], @"limit",
                    [NSNumber numberWithInt:10], @"offset",
                    nil]];

I wonder if you could tighten it up even more with forwardInvocation magic?

Owner
ddb commented Feb 11, 2011

Personally, as someone who has had to maintain too much "magical" code, I would prefer to leave things more explicit, rather than using forwardInvocation. Remember that you read code MUCH more than you write code, so I like to err on the side of explicitness.

Since it's a straightforward transliteration of your original code, I'm not surprised you think it's good. ;)

ObjC and the Cocoa library are certainly much more wordy than Ruby, but autocomplete helps a lot, and I like the fact that you can tell pretty much exactly what's going to happen by the name of the method.

Owner
ddb commented Feb 11, 2011

I'm also unsure if the NAMEOF stuff is going to work like you need. It can be done, but I do not think it's correct as written.

Yeah, I'm going to guess you'd have to push the NAMEOF stuff down into #findFromContext and only use it if a member variable is null/blank.

Years of Rails coding have inured me to magic; while it's true that you read code much more often than you write it, folks almost never read the source to their libraries after the libraries reach a plateau of stability, even ones they wrote for themselves. If the touch of magic makes an interface that much more beautiful, I generally account that as a win.

I'm guessing that

[pred_opt addObject:[cond subarrayWithRange:NSMakeRange(1, [cond count] - 1)]];
will add an NSArray object, rather than append all the elements of the cond subarray onto the pred_opt array; probably
[pred_opt addObjectsFromArray:[cond subarrayWithRange:NSMakeRange(1, [cond count] - 1)]];
would do the right thing.

I have to spend a lot of time in Java which has made me uncomfortable with excessive verbosity, as oddly opposed to my time in Rails making me comfortable with 'magic'. While I very strongly find Objective C nicer than Java, I really like the cleanliness of

cats = Category.find_all(context, by_name:category_name, limit:20, offset:10)

Still, your code has the twin substantial advantages of simplifying Core Data compared to how I've normally approached it in the past, and running on pre-10.6.5 versions of Mac OS X, as well as iOS.

Owner
ddb commented Feb 11, 2011

I admit to being unsure how to translate from the original, so I ended up with the subArrayWithRange: stuff. Yes, I think your version would do the trick.

I think we've got a basic difference in preference here. I come from a Lisp and Smalltalk background -- the way that the arguments are distributed through the method name allows me to make complete sentences, rather than mapping everything into a prefix form. I can appreciate what you are saying, I know that I just tend to prefer my own code. Go figure. ;)

Thank you for the compliments. But remember to TEST THE CODE. I have not run this, it was a straightforward translation of your ruby code. I wanted to back up my "I don't see any reason this needed to be in Ruby" comment with an example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment