magicalpanda/MagicalRecord — pull request #237 by ecordell (2012-08-15 23:24:40+00:00)
Title: Dynamic findBy, findAllBy, has, create

This pull request adds a new category to NSManagedObject so that any subclass can call methods of the form

`[MyManagedObject findByName:@"Foo"]`

and

`[MyManagedObject findAllByUuid:@"F_b_o_a_o_r"]`

It also works for any subclass of NSManagedObject, and returns nil if the property does not exist in the class.

It also adds some other ActiveRecord-like features. There are two new ways to create records: with a dictionary and with a block.

Record *newRecord = [Record create:@{@"name" : @"Squash", @"type": @"Vegetable"}];

And with a block (requires LLVM >=3.1):

Record *newRecord = [Record createWithBlock:^(Record *n) {
n.name = @"Onion";
n.type = @"Vegetable";
}];

There's a new save method that you can call on an instance of a class rather than the class:

[newRecord save:&error];

In addition to the dynamic findBy and findAllBy methods, there are now find and findAll methods that take an NSDictionary:

Record *record = [Record find:@{ @"name" : @"Apple", @"type" : @"Fruit" }];

These methods will hopefully help build dynamic findBy methods with multiple paramaters.

There's also a findOrCreate, which returns an object with the given properties if one exists, and creates one if it does not

Record *newRecord = [Record findOrCreate:@{ @"name" : @"Mango", @"type" : @"Fruit" }];

I've also added dynamic hasProperty methods:

Record *newRecord = [Record create:[NSDictionary dictionaryWithObjectsAndKeys:@"Squash", @"name", nil]];
NSLog(@"%@", [newRecord hasName] ? @"YES" : @"NO"); //Prints YES
NSLog(@"%@", [newRecord hasType] ? @"YES" : @"NO"); //Prints NO

***But Why?***
ActiveRecord is awesome, and I felt that MagicalRecord didn't quite capture the magic, no pun intended.

Originally I wrote this as an extension of RestKit's Core Data extensions, but those were mostly copied/pasted from this project anyway.

***How it Works***
It's actually pretty cool: any time a selector is sent to a class that the class doesn't recognize, `resolveInstanceMethod:` or `resolveClassMethod:` gets called.

You can't just "override" a method in a category, though. To get around this, I swizzle the methods so that my implementation of resolveClassMethod gets called instead. And I'm using JRSwizzle to do this, so everything is fine with inheritance and whatnot. If the method exists already (or doesn't start with findBy or findAllBy), I swizzle 'em back and pass the selector back to the normal resolveClassMethod:.

***What's left to do***

* Find a way to quiet the "might not respond to selector" warnings. (This seems to be impossible at the moment - I think simply adding declarations for the methods that you use will fix it.)
* Allow uppercase properties. Right now it assumes a camel-cased property with a lowercase first letter.
* Allow multiple parameters, i. e. `[MyManagedObject findByCategory: @"Fruit" color:@"Red"];`
* Test it. I've tested on the simulator in an actual app, but I haven't yet written unit tests.

Since I wrote this originally for RestKit, I'm unfamiliar with the conventions for this project. I assume everything should be unit tested? I assume I need to prefix everything with MR_? I wanted to go ahead and send a pull request with these ideas to see if this is something that should even be added to the project. If so, I'll tie up all of the loose ends.

NOTE: I haven't actually tried using this code in MagicalRecord. I suspect it doesn't work out of the box. As I said, just wanted to put out some feelers here and see if I should finish it up. (And it *did* work in RestKit)