TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Dec 2014 |
Maintained by Oliver Staats.
KEYShadow lets you use Core Data in a SQL / LINQ inspired, typesafe, way.
KEYShadow creates keys and keypaths. With an input of object.property1.propery2, it creates a keyPath of @"property1.propery2". You don't have to define any strings. To demonstrate why this is useful, lets look closer at Core Data.
KEYShadow lets you query core data in a sane way, while still providing all the power of NSPredicate. To do this it creates and uses, shadows of the NSManagedObject subclasses. You don't need to modify or extend the Managed Objects subclasses.
Queries are formed by providing triples which are parsed to build predicates. There are no attribute path strings, strewn throughout your queries.
With very little effort on your part, queries are readable, type-safe and have refactorable managed object paths.
KEYQueryBuilder *queryBuilder;
NSArray *animalTypes = @[@"Cow", @"Dog"];
KEYDef(Animal, a$); // Shadow the Animal managed object subclass
queryBuilder = [KEYQueryBuilder select:a$
where:@[a$.name, @"contains[c]", @"Micky",
@"and not",
a$.species.simpleName, @"in", animalTypes]
order:@[a$.species.simpleName, a$.name]];
NSError *error;
NSArray *animals = [queryBuilder fetchFrom:context error:&error];
It's often more convenient to bind the ManagedObjectContext and error handling into the query as follows
KEYQueryContextBuilder *queryBuild = [[KEYQueryContextBuilder alloc] initWithManagedObjectContext:context
errorBlock:^(NSError *error) {
NSLog(@"error:%@", error);
}];
KeyQueryContext *query;
KEYDef(Animal, a$); // Shadow the Animal class
query = [queryBuild select:a$
where:@[a$.name, @"contains[c]", @"Micky",
a$.name, @"!=", @"Micky Duck"]
order:a$.name
limit:0
batch:10];
query.fetchRequest; // you can simply access the fetch request
NSArray *animals = [query fetch]; // or you can get the list of animals
// or you query against another managed object context
query.managedObjectContext = tmpContext
NSArray *tmpAnimals = [query fetch];
The shadow provides the entity name, attribute names and/or path names to the query function. The query function builds a NSFetchRequest (which you can obtain, to further manipulate) using the NSPredicates formed by the triples you provide in the array (NOTE: you can also provide an array of four elements for "ANY" type predicates).
The connectives allowed are "and", "and not", "or", "or not" and "not" . The default connective is "and", so you can leave it out, if you like.
You can nest arrays of triples to structure the connectives in the query. This is instead of using brackets.
For descendng order add @"desc:" before the attribute (or use [KEYSort descendingDescriptor:]).
If the lowercase string "nil" is provided on the right side of a triple, it is treated as nil (or use [NSNull null]).
pod 'KEYShadow'
Theres an example project included, KEYShadowExampleIOS. This also uses the KORData framework, to simplify NSFetchedResultsControllers.
Lots! But, including the following:
Theres a category NSObject+KEYShadow that has a single class level method called "key_$". This lets you, optionally, work inline via:
// Experimenting with a category on NSObject with a class method key_$
KEYQueryBuilder *qb;
qb = [KEYQueryBuilder select:Animal.key_$
where:nil
order:@[@"desc:", Animal.key_$.name]];
Something like:
// format of select clause is -
// entity: a$
// group by attributes: a$.species
// aggregate attributes (in pairs): @"min:", a$.$dob
// when run, this creates an ordered array of dictionaries
KEYDef(Animal, a$);
[KEYQueryBuilder select:@[a$, a$.species, @"min:", a$.dob]
where:@[a$.farm.age, @"!=", @"nil"]
order:@[@"desc[n]:", a$.species]];
Add "having" as a connective in the where clause to allow having clauses. The only problem there is, the only having clause, that appears to work is one using "sum:". Theres no documentation indicating why.
// You can do NSArray *queryResult = [query fetchFrom:array];
Its normal to use strings as dictionary keys, but a little frustating if you mistype the string. You could use const strings, but they are not so modular and only support paths by convention. Instead, lets say you have a People class with various properties of various types. You can just use this class as a keypath as follows:
KEYDef(People, people$); // Shadow the People class
NSDictionary *dictionary = @{
people$.name : @"Joe Bloggs",
people$.address.street: @"Downing St"
};
NSString *whatsTheStreet = dictionary[people$.address.street]; // @"Downing St"