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.
Using pods:
pod 'daikiri'
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
#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.
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;
}
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];
}
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;
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);
}
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
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.
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"
};
}];
}
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];
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?