Skip to content

BadChoice/daikiri

Repository files navigation

Daikiri

Description

Its a lighweight and simple library to work with models allowing the JSON to Model to Core Data both ways. Really useful for server / app synchronization.

Installation

Using pods:

pod 'daikiri'

Usage

Create a model that inherts from Daikiri and add the properties you want to be automatically converted Note that submodels will automatically be converted if they also inhert from Daikiri

JSON

#import "Daikiri.h"
#import "Headquarter.h"

@interface Hero : Daikiri

@property (strong,nonatomic) NSString* name;
@property (strong,nonatomic) NSNumber* age;
@property (strong,nonatomic) Headquarter* headquarter;

@end

Then you can do:

NSDictionary* d = @{
    @"name" : @"Batman",
    @"age"  : @10,
    @"headquarter":@{
        @"address" : @"patata",
        @"isActive" : @1,
        @"vehicles"   : @[
            @{@"model" : @"Batmobile"},
            @{@"model" : @"Batwing"},
            @{@"model" : @"Tumbler"},
        ]
    }
};

Hero * model = [Hero fromDictionary:d];    

And convert it back

NSDictionary* modelToDict = [model toDictionary];
NSLog(@"Model to dict: %@",modelToDict);

You can also convert the arrays to its class, for doing so you need to create the method -(Class)property_DaikiriArray where property is the name of the NSArray property.

In the previous case we have the model Headquarter like this

@interface Headquarter : Daikiri

@property(strong,nonatomic) NSString* address;
@property(strong,nonatomic) NSNumber* isActive;
@property(strong,nonatomic) NSArray*  vehicles;

@end

with the following method

-(Class)vehicles_DaikiriArray{
    return [Vehicle class];
}

And vehicles will be converted automatically.

CORE DATA

Setup

Daikiri Comes with a CoreData manager. It creaes the managedObjectContents and connects to the database named yourprojectname.sqlite at applicationDocumentsDirectory.

You can change the project name by setting the property databaseName of the DaikiriCoreData manager

[DaikiriCoreData manager].databaseName = @"youdatabasename";

You should place this call before any other CoreData call so it's recomended to do it at didFinishLaunchingWithOptions.

The only thing you need to do is to add a call to [[DaikiriCoreData manager] saveContex] in your app delegate -(void)applicationWillTerminate:(UIApplication *)application to save the context even if there is a crash.

However, you can also use you custom CoreData manager by overridin the +(NSManagedObjectContext*)managedObjectContext function in your model.

+(NSManagedObjectContext*)managedObjectContext{
    NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    return context;
}
Testing

Daikiri offers a really easy method to setup the testing database as a full clean one in each test so you can fully do unint testing without any problem. It also uses transactions in each tests so everything is rolled back and the databse is clean in every test.

just add these on your setUp and tearDown methods in the test

    - (void)setUp {
        [super setUp];
        [[DaikiriCoreData manager] useTestDatabase:YES];
        [[DaikiriCoreData manager] beginTransaction];
    }

    - (void)tearDown {
        [super tearDown];
        [[DaikiriCoreData manager] rollback];
    }

Core data models

With a Daikiri model we can work with coredata in an active recod like way. You just need to name the model the same way it is in the database .xcdatamodeld

A Daikiri model comes with an id property that is the primary key used for all the following methods

Then you can do the following

[model create] //It creates a new record in the database needs to have the id

model.name = "Bruce wayne";
model.age  = @10;

[model save]    //Updates the record saved in the database (if it doesn't exists, it will create it)

We can also

// Get an specific hero
Hero* batman = [Hero find:@10]; //Search the model in the database
[batman delete];                //Deletes it from the database

// Get all heros
NSArray* allHeros = [Hero all];    

If you want, there are the convenience methods to to those basic actions directly from a dictionary

+(bool)createWith:(NSDictionary*)dict;
+(bool)updateWith:(NSDictionary*)dict;    
+(bool)deleteWith:(NSNumber*)id;

Relationships

Alongs with the find and all When your models are in the database you have diferent ways to acces their relationships

belongsTo, hasMany and belongsToMany.

Check the examples below to understand them

//Add models to database
Hero * batman               = [Hero createWith:@{@"id":@1, @"name":@"Batman"         ,@"age":@49}];
Hero * spiderman            = [Hero createWith:@{@"id":@2, @"name":@"Spiderman"      ,@"age":@19}];
Hero * superman             = [Hero createWith:@{@"id":@3, @"name":@"Superman"       ,@"age":@99}];

Enemy* luxor                = [Enemy createWith:@{@"id":@1, @"name":@"Luxor"          ,@"age":@32}];
Enemy* greenGoblin          = [Enemy createWith:@{@"id":@2, @"name":@"Green Goblin"   ,@"age":@56}];
Enemy* joker                = [Enemy createWith:@{@"id":@4, @"name":@"Joker"          ,@"age":@45}];

Friend* robin               = [Friend createWith:@{@"id":@1, @"name":@"Robin"         ,@"hero_id":batman.id}];
Friend* maryJane            = [Friend createWith:@{@"id":@2, @"name":@"Mary Jane"     ,@"hero_id":spiderman.id}];
Friend* blackCat            = [Friend createWith:@{@"id":@3, @"name":@"Black cat"     ,@"hero_id":spiderman.id}];

EnemyHero* luxorBatman      = [EnemyHero createWith:@{@"id":@1, @"hero_id":batman.id      ,@"enemy_id":luxor.id, @"level":@7}];
EnemyHero* luxorSuperman    = [EnemyHero createWith:@{@"id":@2, @"hero_id":superman.id    ,@"enemy_id":luxor.id, @"level":@5}];
EnemyHero* jokerBatman      = [EnemyHero createWith:@{@"id":@3, @"hero_id":batman.id      ,@"enemy_id":joker.id, @"level":@10}];
EnemyHero* greenGoblinSpider= [EnemyHero createWith:@{@"id":@4, @"hero_id":spiderman.id   ,@"enemy_id":greenGoblin.id, @"level":@10}];


NSLog(@"Robin's hero is: %@",robin.hero.name);      //Belongs to

for(Friend* friend in spiderman.friends){           //has many
    NSLog(@"Spiderman friend: %@",friend.name);
}

for(Enemy* enemy in batman.enemies){                //Belongs to many
    NSLog(@"Batman enemy: %@ with level: %@",enemy.name, ((EnemyHero*)enemy.pivot).level);
}

Query builder

We have a QueryBuilder to create custom queries, you can do things like

EnemyHero * enemyHero = [[EnemyHero.query
                            where:@"hero_id"  is:batman.id]
                            where:@"enemy_id" is:joker.id]
                        .first;

NSArray * heroes = [[Hero.query 
                        where:@"id" operator:@">" value:@2] 
                        orderBy:@"age"]
                    .get;

for(Hero * hero in heroes){
    NSLog(@"Hero: %@",hero.name);
}

If you class names use a prefix (two chars) and your entities don't, you can override the function usesPrefix to return true. This will remove the prefix when fetching to the DB

Factory

Daikiri comes with a Laravel Like factory class for tests. You can have you factories to create tests objects for you and making each test look very neat.

Usage

First create a factory class with a simple registerFactories method and create the factories giving a simple dict

@implementation HeroFactory

+(void)registerFactories{

    [DKFactory define:Hero.class builder:^NSDictionary *{
        return @{
            @"name": @"Batman",
            @"age" : @"49"
        };
    }];

    [DKFactory define:Enemy.class builder:^NSDictionary *{
        return @{
            @"name": @"Luxor",
            @"age" : @"32"
        };
    }];
}
Factory states

You can have some diferent types of you class you can define with another name (it will merge the default one and the new one)

+(void)registerFactories{

    [DKFactory define:Hero.class builder:^NSDictionary *{
        return @{
            @"name": @"Batman",
            @"age" : @"49"
        };
    }];

    [DKFactory define:Hero.class name:@"old" builder:^NSDictionary *{
        return @{
            @"age" : @"100"
        };
    }];
}

Then on your testClass setup method call the [Yourfactory registerFactories].

After that on your tests you can instantiate any class with a simply call to

    Hero* testHero   = [factory(Hero.class)  make];
    Enemy* testEnemy = [factory(Enemy.class) create];

Note that make just creates the object without storing it to the database but create does store it to the database.

You can use the non macro constructor to have more options

    NSArray* oldHerosArray = [[DKFactory factory:Hero.class name:@"old" count:4] make];
Relationships

The cool thing in the factory is that you can use callbacks to create relationships so you can do something like this:

    [DKFactory define:Headquarter.class builder:^NSDictionary *{
        return @{
            @"name": @"Star tower",
            @"hero_id" : ^{
                return ((Daikiri*)[factory(Hero.class) create]).id;
            }
        };
    }];

And the Hero will be only created when insantiating the object in the make or create, isn't it cool?

About

Json <-> Model <-> CoreData made easy

Resources

License

Stars

Watchers

Forks

Packages

No packages published