So I was asked to add few features to the web-app, that was in production and running PostgreSQL. Well, I didn't want to install the PostgreSQL on my local machine. So I decided to have development version and test version to run in SQLite, while keeping production version unchanged.

So I came up to few hacks.

database.yml

First, edit your config/database.yml and change it so development and test enviroment will use SQLite

1
2
3
4
5
6
7
8
9
10
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000
test: &test
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

And don't forget to run rake db:reset and rake db:seed to set your new DB and seed it with data.

On one of projects I was involved in, we were implementing Captive Login technology, to automatically log in networks that have Captive Portal via WISPr technology. Everything went quite fine, except that on iOS, you can't do auto-login to WiFi network without user's permission. And the CaptiveLogin support is quite limited.

Then we suddenly spotted an app, that could do all that cool things. That was the start of my small research.

The Beginning Is the End Is the Beginning

For sake of convinience, let's call tha app that we'r interested in iApp. We downloaded the app from AppStore, renamed .ipa to .zip, and started looking inside.

First of all, we opened Info.plist. And found a quite interesting string network-authentication for Required background modes. Google gave us 0 results.

Next thing, I checked iApp.entitlements file. And found there a nice key com.apple.developer.CaptiveNetworkPlugin saying YES. That was something.

Let me tell you, dear reader, how I simplify my life by writing small and effective unit tests for iOS projects.

I'm quite lazy person, so if I can simplify my life and work - I do so. Of course you agree, that writing tests is cool. Having tests means you have less bugs in code, and if someone changes something, he can check if he's patch breaks anything in existing implementation. And of course, writing unit tests makes you thinking about better architecture, and making smaller, better methods. You know beforehand, that testing a method that is few hundreds of lines of code is a big pain in the ass…

But why people often avoid writing tests? Well, as I see often - because they think it's a lot of additional code to write.

So I'll try to tell from my experience, how to write less test code :)

Asserts

Let's start with simple thing, thing that you can spot in many test samples:

1
STAssertNotNil(objects, @"Could not load objects");

Well, if it's one line in code - it's kind of OK. But if you need to write these STAssers-s over 9000 times… you know what I mean. It's getting even worse if you want to compare simple types like int or float.

So my number one pick is a small library called Expecta. Just add to your Podfile (I think you'r using cocoapods, don't you?) pod 'Expecta' and you'll have a simple and nice assert tests like

1
2
3
4
5
6
7
expect(objects).toNot.beNil();
expect(objects.count).to.equal(3);
expect(evaluated).to.equal(YES);

// or more fancy stuff
NSDictionary *dict2 = @{@"id" : @"articleId", @"name" : @"name", @"title" : @"title"};
expect(dict).to.equal(dict2);

Imagine we have a Mongoid document, where we have two embedded documents. And now we want to make some relation between them. What you can think of at first is to use

1
has_many :acts, :foreign_key => 'event_id'

But that won't work. Mongoid is not capable at this moment to support this between embedded documents :(

But we can try to implement our own, custom relationship.

WARNING: This post is only my attempt to make it happen. It can contain bugs, or there can be other ways how to make it work easier. If you found some - let me know.

If you are a pro developer for Apple stuff (ios, mac), you may have a situation, when you are in different teams, and sometimes, the team name can be same. For example, there can be a team Company for AppStore and team Company for Enterprise. So I was bored gessing which one to choose and decided to make a Greesemonkey script for that.

In this small post I'll show how I use RSpec to test and generate docs for my API.

Full documentation for the gem and it's DSL you can find on it's homepage rspec_api_documentation

First of all, you will need nice gem rspec_api_documentation, so edit your Gemfile and add

1
gem 'rspec_api_documentation', :group => [:test, :development], :git => "git://github.com/zipmark/rspec_api_documentation.git"

Having problems looking your TODO list in TextMate2? Run this script to have a fix.

1
cd ~/Library/Application\ Support/TextMate/Managed/Bundles/TODO.tmbundle/Support/lib && rm -rf settings.rb && curl -O http://ebundles.googlecode.com/svn-history/r216/trunk/Bundles/TODO.tmbundle/Support/lib/settings.rb

In this short post I'll show how I use RestKit library in my iOS projects.

First of all, I want to say that for managing all my libraries in iOS projects I use CocoaPods (kind of Gemfile bundler, but for iOS), which I strongly recommend using.

So, RestKit… open up your Podfile and add

1
2
3
4
5
dependency 'RestKit/Network'
dependency 'RestKit/UI'
dependency 'RestKit/ObjectMapping'
dependency 'RestKit/ObjectMapping/CoreData'
dependency 'RestKit/ObjectMapping/JSON'

I also use Injective dependency a lot, so I add dependency 'Injective' to my Podfile <!– more –>

Next, run pod install which will download latest RestKit (In my case - version 0.10.0) and integrate it to your project.

The code

Examples I'll provide will use RM class prefix, and API I'll refer is from my start-up rabat.me. First I'll write examples without data persistancy, than I'll provide examples how to use CoreData. Also, I add #import <RestKit/RestKit.h> and #import "IJContext.h" to my App-Prefix.pch

You can find full source code of the project on GitHub https://github.com/xslim/RabatMe

Initialization

For initializing Injective library, edit your AppDelegate.m

1
2
3
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
    [IJContext setDefaultContext:[[IJContext alloc] init]];

What I also like doing, is initializing RestKit-s logging, so I can use it in my app and changing verbocity level from enviroment variables.

1
2
3
// This goes to application:didFinishLaunchingWithOptions:
RKLogInitialize();
RKLogConfigureFromEnvironment();

Create class RMApiConnector which we will use for all our RestKit bootstrapping and network connections.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSString  const RMErrorDomain = @"org.API.ErrorDomain";

@interface RMApiConnector ()
- (void)fireErrorBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock onErrorInResponse:(RKResponse )response;
@end

@implementation RMApiConnector

injective_register_singleton(RMApiConnector)

- (id)init {
    self = [super init];
    if (self) {
        [self setupConnector];
        [self setupMapping];
    }
    return self;
}

injective_register_singleton will add RMApiConnector to singleton collection on DI, so we will be able to access it like

1
RMApiConnector *connector = [RMApiConnector injectiveInstantiate]

Setting up Object Loader

1
2
3
4
5
6
7
8
9
10
11
12
- (void)setupConnector {
    NSString *baseUrl = @"http://example.org/api/"
    RKObjectManager *manager = [RKObjectManager sharedManager];

    if (!manager) {
        manager = [RKObjectManager objectManagerWithBaseURL:[RKURL URLWithString:baseUrl]];
        manager.client.serviceUnavailableAlertEnabled = YES;
        manager.requestQueue.showsNetworkActivityIndicatorWhenBusy = YES;
    } else {
        manager.client.baseURL = [RKURL URLWithString:baseUrl];
    }
}

Setting up Object Mapping

1
2
3
4
5
6
7
8
9
10
11
12
- (void)setupMapping {
    RKObjectMappingProvider *omp = [RKObjectManager sharedManager].mappingProvider;

    RKObjectMapping *productMapping = [RMProduct mapping];
    [omp addObjectMapping:productMapping];
    [omp setObjectMapping:productMapping forResourcePathPattern:@"/products"];

    RKObjectMapping *shopMapping = [RMShop mapping];
    [omp addObjectMapping:shopMapping];
    [omp setObjectMapping:shopMapping forResourcePathPattern:@"/shops/:id"];
    [omp setObjectMapping:shopMapping forResourcePathPattern:@"/shops/near/:lat/:lng/:radius"];
}

The mapping itself I store with Models like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@class RKObjectMapping;

@interface RMShop : NSObject

@property (nonatomic, strong) NSString itemId;
@property (nonatomic, strong) NSNumber points;
@property (nonatomic, strong) NSString name;
// Some other properties

@property (nonatomic, strong) NSArray products;
@property (nonatomic, strong) NSArray rewards;

+ (RKObjectMapping )mapping;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation RMShop

@synthesize itemId, points; // and others

+ (RKObjectMapping *)mapping {
    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[self class] usingBlock:^(RKObjectMapping *mapping) {
        [mapping mapAttributes:@"name", @"address", @"distance", @"location", @"points", nil];
        [mapping mapKeyPathsToAttributes:
         @"id", @"itemId",
         @"description", @"descriptionText",
         @"distance_unit", @"distanceUnit",
         nil];
    }];
    [objectMapping hasMany:@"products" withMapping:[RMProduct mapping]];
    [objectMapping hasMany:@"rewards" withMapping:[RMReward mapping]];
    return objectMapping;
}

@end

Creating API requests

Let's create few requests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)loadNearbyShopsForLocation:(CLLocation )location onLoad:(RKObjectLoaderDidLoadObjectsBlock)loadBlock onError:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
    NSString params = [NSDictionary dictionaryWithKeysAndObjects:
                        @"lat", [NSNumber numberWithDouble:location.coordinate.latitude],
                        @"lng", [NSNumber numberWithDouble:location.coordinate.longitude],
                        @"radius", [NSNumber numberWithInt:20], // later change this to accuracy
                        nil];

    NSString url = [@"/shops/near/:lat/:lng/:radius" interpolateWithObject:params];

    RKObjectManager manager = [RKObjectManager sharedManager];
    [manager loadObjectsAtResourcePath:url usingBlock:<span class="p">(RKObjectLoader loader) {
        loader.onDidLoadObjects = loadBlock;
        loader.onDidFailWithError = failBlock;
        loader.onDidFailLoadWithError = failBlock;
        loader.onDidLoadResponse = <span class="p">(RKResponse response) {
            [self fireErrorBlock:failBlock onErrorInResponse:response];
        };
    }];
}
What we can see here, is that I'm using block-based API of RestKit. loadBlock will get array of loaded objects. failBlock will get NSError object on error. Also I'm using fireErrorBlock:onErrorInResponse: helper to get error message from my API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)fireErrorBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock onErrorInResponse:(RKResponse *)response {

    if (![response isOK]) {
        id parsedResponse = [response parsedBody:NULL];
        NSString *errorText = nil;
        if ([parsedResponse isKindOfClass:[NSDictionary class]]) {
            errorText = [parsedResponse objectForKey:@"error"];
        }
        if (errorText) failBlock([self errorWithMessage:errorText code:[response statusCode]]);
    } else {
        //id parsedResponse = [response parsedBody:NULL];
        //RKLogTrace(@"response: [%@] %@\n%@", [parsedResponse class], parsedResponse, [response bodyAsString]);
    }
}

What if we want to make a POST call? Easy. Just tell the loader loader.method = RKRequestMethodPOST; and pass NSDictionary POST params to loader.params

1
2
3
4
5
6
7
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager loadObjectsAtResourcePath:@"/stamps" usingBlock:^(RKObjectLoader *loader) {
    loader.method = RKRequestMethodPOST;
    loader.params = [NSDictionary dictionaryWithKeysAndObjects:
                         @"code", clientCode,
                         @"products", productsToSend,
                         nil];

What if we don't need to load any objects? Let's say we want to make client Authentication.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)authenticateWithLogin:(NSString *)login password:(NSString *)password onLoad:(RKRequestDidLoadResponseBlock)loadBlock onFail:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
    [[RKClient sharedClient] post:@"/login" usingBlock:^(RKRequest *request) {
        request.params = [NSDictionary dictionaryWithKeysAndObjects:
                          @"employee[email]", login,
                          @"employee[password]", password,
                          nil];
        request.onDidLoadResponse = ^(RKResponse *response) {

            id parsedResponse = [response parsedBody:NULL];
            NSString *token = [parsedResponse valueForKey:@"authentication_token"];
            //NSLog(@"response: [%@] %@", [parsedResponse class], parsedResponse);

            if (token.length > 0) {
                NSLog(@"response status: %d, token: %@", response.statusCode, token);
                [[RKClient sharedClient] setValue:token forHTTPHeaderField:@"X-Rabatme-Auth-Token"];

                if (loadBlock) loadBlock(response);
            }

            [self fireErrorBlock:failBlock onErrorInResponse:response];
        };
        request.onDidFailLoadWithError = failBlock;
    }];
}

Integrating CoreData

Great. What about CoreData? Change RMApiConnector's init method

1
2
3
4
5
if (self) {
    [self setupConnector];
    [self setupDB];
    [self setupMapping];
}

1
2
3
4
- (void)setupDB {
    RKObjectManager *manager = [RKObjectManager sharedManager];
    manager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:@"HotSpots.sqlite"];
}

For mapping Managed Objects you do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)setupMapping {
    RKObjectManager manager = [RKObjectManager sharedManager];
    RKObjectMappingProvider omp = [RKObjectManager sharedManager].mappingProvider;

    RKManagedObjectMapping *hotSpotMapping = [RKManagedObjectMapping mappingForClass:[HotSpot class] inManagedObjectStore:manager.objectStore];
    [hotSpotMapping mapAttributes:@"name", @"city", @"country", @"latitude", @"longitude", @"type", @"zipcode", nil];
    [hotSpotMapping mapKeyPathsToAttributes:
     @"address", @"street",
     @"description", @"details",
     @"id", @"hotSpotID",
     @"openinghours", @"openingHours",
     nil];
    hotSpotMapping.primaryKeyAttribute = @"hotSpotID";

    [omp addObjectMapping:hotSpotMapping];
    [omp setObjectMapping:hotSpotMapping forResourcePathPattern:@"app/location"];

Now if Object Mapper will see that the object he's mapping is a NSManagedObject he will check the DB if the'r already object with same primaryId. If yes - he'll update it with new values, if no - he'll create one for you.