KEYShadow 0.2.0

KEYShadow 0.2.0

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Dec 2014

Maintained by Oliver Staats.



KEYShadow 0.2.0

  • By
  • olistaats

About

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.

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.

Example

    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]; 

How it works

  • Create NSManagedObject subclasses, via XCode
  • import "KEYAll.h", to add shadowing via the KEYDef macro. This is just is a very simple wrapper to hide the implemenatation of the shadow object.

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]).

How to get it (PODS)

pod 'KEYShadow'

Example Project

Theres an example project included, KEYShadowExampleIOS. This also uses the KORData framework, to simplify NSFetchedResultsControllers.

Todo

Lots! But, including the following:

Class level method + Category

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]];

Aggregates and group by

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.

Enhanced order clause

  • @"[c]:" will add case insensitive.
  • @"[cd]:" will add diacritical case insensitive.
  • @"[n]: uses localizedStandardCompare
  • @"desc:" will sort in descending order, can have a bracket suffix (e.g. @"desc[n]:")

Arrays

// You can do NSArray *queryResult = [query fetchFrom:array];

  • Currently, this ignores the select clause, so theres no grouping or aggregates

Dictionaries

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"