Skip to content

Commit

Permalink
Rewrote some things to make the framework more universally useful.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joachim Garth committed Mar 4, 2012
1 parent e84d8a3 commit d4ec380
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 151 deletions.
12 changes: 9 additions & 3 deletions CPDate-MCResourceAdditions.j
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var MCDateTimeRegExp = new RegExp(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([-+])(\d{2}):(\d{2})/);
var MCDateTimeRegExp = new RegExp(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(([-+])(\d{2}):(\d{2})|Z)/);

// Inspired by http://delete.me.uk/2005/03/iso8601.html
var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; }
Expand All @@ -12,11 +12,17 @@ var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; }
if(!aDateTime || [aDateTime isKindOfClass:[CPNull class]])
return nil;

var dateParts = aDateTime.match(MCDateTimeRegExp),
var dateParts = aDateTime.match(MCDateTimeRegExp),
date = new Date(dateParts[1], dateParts[2] - 1, dateParts[3], dateParts[4], dateParts[5], dateParts[6]),
timeZoneOffset = (Number(dateParts[8]) * 60 + Number(dateParts[9])) * (dateParts[7] === '-' ? -1 : 1);
timeZoneOffset;

if(dateParts[7] != 'Z')
timeZoneOffset = (Number(dateParts[9]) * 60 + Number(dateParts[10])) * (dateParts[8] === '-' ? -1 : 1);
else
timeZoneOffset = -2 * date.getTimezoneOffset();

self = new Date(date.getTime() + (timeZoneOffset + date.getTimezoneOffset()) * 60 * 1000);

return self;
}

Expand Down
16 changes: 2 additions & 14 deletions Constants.j
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,6 @@ MCGenerateShortRandom = function() {

// Messages & Strings

// German
// MCValidationRequiredFieldErrorMessage = @"Bitte fülle dieses Feld aus";
// function MCValidationMinLengthErrorMessage(minLength) {return [CPString stringWithFormat:@"Bitte trage mehr als %d Zeichen ein", minLength];};
// function MCValidationMaxLengthErrorMessage(maxLength) {return [CPString stringWithFormat:@"Bitte trage weniger als %d Zeichen ein", maxLength];};
// function MCValidationGreaterThanErrorMessage(greaterThan) {return [CPString stringWithFormat:@"Bitte gib einen Wert größer als %d an", greaterThan];};
// function MCValidationMaxValueErrorMessage(maxValue) {return [CPString stringWithFormat:@"Bitte gib einen Wert kleiner als %d an", maxValue];};
// MCValidationOnlyIntegerErrorMessage = @"Bitte gib eine ganze Zahl an";
// MCValidationMinChildrenErrorMessage = @"sind zu wenige";
// MCValidationMaxChildrenErrorMessage = @"sind zu viele";
//
// MCResourceMonthNames = ['Januar', 'Februar', 'März', 'April',
// 'Mai', 'Juni', 'Juli', 'August', 'September',
// 'Oktober', 'November', 'Dezember'];

// English
MCValidationRequiredFieldErrorMessage = @"This information is required";
function MCValidationMinLengthErrorMessage(minLength) {return [CPString stringWithFormat:@"Please enter more than %d characters", minLength];};
Expand All @@ -32,6 +18,8 @@ function MCValidationMaxValueErrorMessage(maxValue) {return [CPString stringWith
MCValidationOnlyIntegerErrorMessage = @"Please enter an integer";
MCValidationMinChildrenErrorMessage = @"are too few";
MCValidationMaxChildrenErrorMessage = @"are too many";
MCResourceGeneralErrorMessage = @"We're sorry, but there was an error!"
MCResourceGeneralErrorDetailedMessage = @"An error was encountered performing one or more requests. Please see the error console for details."

MCResourceMonthNames = ['January', 'February', 'March', 'April',
'May', 'June', 'July', 'August', 'September',
Expand Down
2 changes: 1 addition & 1 deletion MCHasManyAssociation.j
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@

if(_shallow)
{
URL = MCResourceServerURL + "/" + lastURLPart;
URL = MCResourceServerURLPrefix + "/" + lastURLPart;
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion MCHasOneAssociation.j
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

if(_shallow)
{
URL = MCResourceServerURL + "/" + [URLBuildingObject _constructResourceURL];
URL = MCResourceServerURLPrefix + "/" + [URLBuildingObject _constructResourceURL];
}
else
{
Expand Down
10 changes: 5 additions & 5 deletions MCResource.j
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@import "CPArray-MCResourceAdditions.j"
@import "MCValidation.j"

MCResourceServerURL = @"/admin";
MCResourceServerURLPrefix = @"";

// Options dictionary key names
MCResourceAssociationClassKey = @"MCResourceAssociationClassKey";
Expand Down Expand Up @@ -1065,7 +1065,7 @@ var AllResourcesByTypeAndId = [CPDictionary dictionary];

+ (CPString)resourceURL
{
return MCResourceServerURL + "/" + [self _constructResourceURL];
return MCResourceServerURLPrefix + "/" + [self _constructResourceURL];
}

- (void)setResourceURL:(CPString)anURL
Expand All @@ -1089,7 +1089,7 @@ var AllResourcesByTypeAndId = [CPDictionary dictionary];
}
else
{
prefix = MCResourceServerURL + "/";
prefix = MCResourceServerURLPrefix + "/";
}

_resourceURL = prefix + [self _constructResourceURL];
Expand Down Expand Up @@ -1348,11 +1348,11 @@ var AllResourcesByTypeAndId = [CPDictionary dictionary];
{
if(!_MCResourceErrorAlertIsShowing)
{
var alert = [CPAlert alertWithMessageText:@"Tut uns leid, etwas ist schief gelaufen!"
var alert = [CPAlert alertWithMessageText:MCResourceGeneralErrorMessage
defaultButton:@"Okay"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"Wir konnten keine Verbindung zum Server herstellen und deine Änderungen wurden möglicherweise nicht gespeichert.\n\nFalls das Problem dauerhaft besteht, wende dich bitte an: support@cornerstoreapp.com"];
informativeTextWithFormat:MCResourceGeneralErrorDetailedMessage];

[alert setDelegate:self];
[alert beginSheetModalForWindow:[CPApp mainWindow]];
Expand Down
139 changes: 12 additions & 127 deletions Readme.markdown
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MCResource

A library to interact with a RESTful [Rails](rubyonrails.org) backend in [Cappuccino](cappuccino.org) extracted from [Cornerstore](www.cornerstoreapp.com) - a cloud-based online shop (only available in Germany).
A library to interact with a RESTful [Rails](http://www.rubyonrails.org) backend in [Cappuccino](http://www.cappuccino.org) extracted from [Cornerstore](http://www.cornerstoreapp.com) - a cloud-based online shop (only available in Germany).

## Short overview

Expand All @@ -15,10 +15,14 @@ MCResource aims to provide the following features transparently for application

MCResource is designed around asynchronous programming concepts. All loading and saving methods on resources and associations take a delegate and selector argument, and send out KVO-notifications where applicable. Also, convention over configuration was followed, meaning association names and classes and such are usually inferred from their plain english names.

## Simple example
## Example usage

### 1. Setup

Most importantly, MCResource **does not work without type signatures enabled** in Objective-J! So either set the correct preprocessor flag (Preprocessor.Flags.IncludeTypeSignatures) or edit Preprocessor.js to always include type signatures.

First, clone MCResource into your frameworks folder. Then, add the following to your AppController.j:

// Import MCResource code
@import "Frameworks/MCResource/MCResource.j"

Expand All @@ -30,7 +34,7 @@ If you need authentication:
// Set as delegate to deal with authentication challenges
[MCHTTPRequest setDelegate:self];

// And add authorizationCredentials method
// And add authorizationCredentials method to the delegate
- (CPString)authorizationCredentials
{
return [MCResource encodeCredentials:@"testuser" password:@"testpassword"];
Expand All @@ -53,76 +57,15 @@ That's it! You can now use that model as you please. Note that standard attribut
### 3. Setting up the backend

Obviously you need to setup a rails backend first :) This is covered in so many places I don't want to include a how-to here.
Out-of-the box JSON from Rails 2.x/3 is supported, through default routes. You can also include associations nested in parent resources' JSON if you want to.

MCResource expects the backend to act like this:
JSON from Rails 2.x/3 is supported **after you enable ActiveRecord::Base.include\_root\_in\_json** , through default routes. You can also include associations nested in parent resources' JSON if you want to.

Class "Post", request to GET /posts should yield:

[
{
"post":
{
"title": "a post",
"content": "some content",
"rating":
{
"aggregate": 2.5,
"some_other_method": 3.1
},
"created_at": "2012-01-12T17:30:58+01:00",
"updated_at": "2012-01-12T17:30:58+01:00",
"published_at": "2012-01-12T17:30:58+01:00",
"id": 1,
}
},
{
"post":
{
"title": "another post",
"content": "some more content",
"rating":
{
"aggregate": 1.9,
"some_other_method": null
},
"created_at": "2012-01-12T17:30:58+01:00",
"updated_at": "2012-01-12T17:30:58+01:00",
"published_at": "2012-01-12T17:30:58+01:00",
"id": 2,
}
},
]

JSON for saving is constructed using FormData objects (transparent for rails if you just use the _params_ hash).
This is really convenient if you decide to upload files, MCResource does not care. Also, some work is done by the browser instead of its JS engine.

Saving a comment attached to a post via hasMany-Association will create a PUT request to /posts/1/comments/3

------WebKitFormBoundaryAFOJVz3sALr2LK78
Content-Disposition: form-data; name="comment[title]"

A changed title.
------WebKitFormBoundaryBXIxhz5Rl8Qsjwtj
Content-Disposition: form-data; name="comment[content]"

Some changed content.
It's nice to debug with WebKit's inspector
------WebKitFormBoundaryBXIxhz5Rl8Qsjwtj
Content-Disposition: form-data; name="variant[rating][aggregate]"

6.9
------WebKitFormBoundaryBXIxhz5Rl8Qsjwtj--

and will arrive in Rails like this:

Parameters: {"post_id"=>"1", "id"=>"3", "comment"=>{"title"=>"A changed title.", "content"=>"Some changed content.", "rating" => {"aggregate" => 6.9}}}
For specifics on JSON in-/output see the wiki page.

### 4. Fetching resources from the backend

- (void)aMethod
{
[Post findWithDelegate:self andSelector:@selector(didLoadPosts:)];
[Post find:nil withDelegate:self andSelector:@selector(didLoadPosts:)];
}

- (void)didLoadPosts:(CPArray)allPosts
Expand All @@ -145,72 +88,14 @@ and will arrive in Rails like this:
{
if([aPost hasErrors])
{
// React to errors if you need to.
CPLog.debug(@"Errors on post (%@): %@!", aPost, [aPost errors]);
}
else
{
// Do something if you need to.
CPLog.debug(@"Did save post: %@", aPost);
}
}

## Some more advanced examples

### Associations

Let's stick to the established example of posts and comments. You might do the following:

#### Models
@implementation Post : MCResource
{
CPString title;
CPString content;
BOOL published;
}

+ (void)initialize
{
[self hasMany:@"comments"];
[self hasOne:@"author"];
}
@end

@implementation Comment : MCResource
{
CPString content;
}

+ (void)initialize
{
[self hasMany:@"ratings"];
[self belongsTo:@"post"]; // Gives the ability to call [[aComment associationForName:@"post"] associatedObject]
// even if that Comment was not loaded through the association on Post;
}
@end

#### Controller
var aTextField;

- (void)someMethod
{
aTextField = [CPTextField labelWithTitle:@""];
}

- (void)postsDidLoad:(CPArray)posts
{
[aTextField bind:CPValueBinding toObject:aPost withKeyPath:@"comments.@count" options:nil];
}


You can pass an options CPDictionary to hasMany:/hasOne: with the following keys supported at this time:


MCResourceAssociationObjectClassKey // Set a custom class if it cannot be inferred from the association name
MCResourceAssociationAutosaveKey // YES will save the association along everytime the owning resource is saved
MCResourceAssociationShallowKey // YES will construct the resource URL directly under the application root
MCResourceAssociationNestedOnlyKey // YES will mark the association as being "nested" under its parent resource's JSON in/output
MCResourceAssociationSortDescriptorsKey // Array of sort descriptors for has many associations;



## Contribute

Expand Down

0 comments on commit d4ec380

Please sign in to comment.