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?