AlexDenisov / iActiveRecord

ActiveRecord for iOS without CoreData, only SQLite
http://alexdenisov.github.com/iActiveRecord/
MIT License
354 stars 50 forks source link

NSArray SQL representation #14

Closed pwightman closed 12 years ago

pwightman commented 12 years ago

Hey,

First of all, thanks for the great library. I plan on contributing some, I've started to make a set of methods I've found useful in my project that I'll pull request at some point so you can take a look.

I have a question about SQL representation. You have this in the wiki:

@implementation NSString (sqlRepresentation)

+ (ActiveRecord *)fromSql:(NSString *)sqlData{
    return sqlData;
}

- (NSString *)toSql {
    return self;
}

+ (const char *)sqlType {
    return "text";
}

@end 

You have fromSql returning ActiveRecord, would it not return NSString? Am I misunderstanding how this works?

I'm setting up sql representation for NSArray. All array will just contain BOOLs, something like this:

NSArray *values = @[ @YES, @NO, @NO, @YES ];

My storage technique is just to serialize the data as JSON and back again. Here's the code:

@implementation NSArray (BBExtension)

+ (id)fromSql:(NSString *)sqlData{
    return [NSJSONSerialization JSONObjectWithData:[sqlData dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
}

- (NSString *)toSql {
    return [NSString stringWithUTF8String:[[NSJSONSerialization dataWithJSONObject:self options:0 error:nil] bytes]];
}

+ (const char *)sqlType {
    return "text";
}

@end

I've tested the serialization/deserialization to verify that when I manually do it, it works fine. But when creating the record like so:

BBGroove *groove = [[BBGroove alloc] init];

...

[groove save]; // This returns YES

NSLog(@"Groove: %@", [BBGroove allRecords]);

After the NSLog runs, I get this in the console:

2012-07-29 19:54:55.896 BeatBuilder[3479:c07] SELECT "BBGroove"."id","BBGroove"."updatedAt","BBGroove"."beatUnit","BBGroove"."beats","BBGroove"."tempo","BBGroove"."createdAt","BBGroove"."voices" FROM "BBGroove"  LIMIT -1 
2012-07-29 19:54:55.897 BeatBuilder[3479:c07] Couldn't retrieve data from database: no such table: BBGroove
2012-07-29 19:54:55.923 BeatBuilder[3479:c07] Groove: (null)

My other models work just fine, but this one model has the custom ARRepresentationProtocol for arrays and doesn't work, so I can't help but think I might be doing something wrong? Do you see something I'm overlooking?

I'll keep looking into it, but wondered if you had some insight. Thanks for the great work!

pwightman commented 12 years ago

Oh, as an aside I'm running Mac OSX 10.8/Xcode 4.5.

pwightman commented 12 years ago

Ah, I think I've figured it out. I didn't see that I was getting errors even earlier:

2012-07-29 20:23:27.493 BeatBuilder[3661:c07] Couldn't execute query create table "BBGroove"(id integer primary key unique , "voices" text, "tempo" (null), "beatUnit" (null), "beats" (null), "updatedAt" INTEGER, "createdAt" INTEGER) : near "(": syntax error

I think because I have non-object properties:

@property (nonatomic, assign) NSUInteger tempo;
@property (nonatomic, assign) BBGrooverBeat beatUnit;
@property (nonatomic, assign) NSUInteger beats;

BBGrooverBeat is an enum. Are non-object properties just not supported since you can't add a class extension to them?

pwightman commented 12 years ago

Some progress, but here's what I'm getting now. My class setup is below:

@interface BBGroove : ActiveRecord

@property (nonatomic, strong) NSArray *voices;
@property (nonatomic, strong) NSNumber *tempo;
@property (nonatomic, strong) NSNumber *beatUnit;
@property (nonatomic, strong) NSNumber *beats;

...

@end

And the .m file:

@implementation BBGroove

ignore_fields_do(
                 ignore_field(voices)
)

...

@end

So all that should be saved is the 3 NSNumber objects, but this code:

BBGroove *groove = ...;

if(![groove save]) {
        NSLog(@"FAILED: %@", [groove errors]);
    }

NSLog(@"Groove: %@", groove);

gives me this:

2012-07-29 20:38:14.342 BeatBuilder[4042:c07] Groove: BBGroove
tempo => 120;beatUnit => 4;beats => 4;id => (null);updatedAt => 2012-07-30 02:38:12 +0000;createdAt => 2012-07-30 02:38:12 +0000;

Now only id is null, though updateAt/createdAt are set and save is returning YES? Still investigating...

pwightman commented 12 years ago

Fixed! I ... my BBGroove creation, which was exactly the problem! alloc/init instead of newRecord. All is well now!

AlexDenisov commented 12 years ago

Hi, thanks for feedback. Yes, the problem is at alloc/init methods. Scalar types are not supported yet, but it should be soon. Also, note that NSArray already have implemented toSql method, it useful with WHERE statements

NSArray *ids = [NSArray arrayWithObjects:
           [NSNumber numberWithInt:1],
           [NSNumber numberWithInt:15], 
           nil];
NSString *username = @"john";
ARLazyFetcher *fetcher = [User lazyFetcher];
[fetcher where:@"'user'.'name' = %@ or 'user'.'id' in %@", 
               username, ids, nil];

If you want to store NSArray as as table field you should try that trick

Describe your NSArray as ignored and add your NSArray representation

@interface Model : ActiveRecord
@property (nonatomic, retain) NSArray *fuuBar;
@property (nonatomic, copy) NSString *fuuBarRepresentation;
@end
...
@implementation Model
ignore_fields_do(
             ignore_field(fuuBar)
)
...
- (BOOL)save {
    //   convert fuuBar array to fuuBarRepresentation
    return [super save];
}
@end

Or just use KVO and convert fuuBar <-> fuuBarRepresentation on each changes.

And last variant that I see - use another record instead array.

pwightman commented 12 years ago

Ah, I like the save override. Good call, I think I'll switch to that. Thanks!