Commit 70abdd07 authored by Ezequiel R. Aguerre's avatar Ezequiel R. Aguerre

Merge branch '3-mb-add_nested_resources' into 'master'

Add nested resources

Add the ability of nest resources in a intuitive way:

```swift
api.resource( "users" ).member( 1 ).resource( "likes" ).list() { likes: [ Like ] in // Do something with the likes }
api.resource( "users" ).member( "me" ).resource( "profile" ).get() { profile: Profile in // Do something }
```

Closes #3

See merge request !3
parents fa33c109 5698cafc
......@@ -22,7 +22,7 @@ public class Api {
}
// RESOURCE
public func resource<T>( path: String ) -> Resource<T> {
public func resource( path: String ) -> Resource {
return Resource( api: self, path: path )
}
......
......@@ -19,7 +19,7 @@ public enum ApiError: ErrorType {
case Unknown( error: NSError )
}
public class Resource<T: JSONSerializable> {
public class Resource {
public let api: Api
public let path: String
......@@ -28,7 +28,7 @@ public class Resource<T: JSONSerializable> {
self.path = path
}
public func item( id: AnyObject, parameters: [ String: String ]? = nil,
public func item<T: JSONSerializable>( id: AnyObject, parameters: [ String: String ]? = nil,
headers: [ String: String]? = nil, completion: ( ( T?, error: ApiError? ) -> Void ) )
{
api.get( resourceMemberPath( id ), parameters: parameters, headers: headers ) { response in
......@@ -36,7 +36,7 @@ public class Resource<T: JSONSerializable> {
}
}
public func create( item: T, parameters: [ String: String ]? = nil,
public func create<T: JSONSerializable>( item: T, parameters: [ String: String ]? = nil,
headers: [ String: String]? = nil, encoding: ParameterEncoding = .JSON,
completion: ( ( T?, error: ApiError? ) -> Void ) )
{
......@@ -48,7 +48,9 @@ public class Resource<T: JSONSerializable> {
}
}
func handleItemResult( response: Response<AnyObject, NSError>, handler: ( T?, error: ApiError? ) -> Void ) {
func handleItemResult<T: JSONSerializable>( response: Response<AnyObject, NSError>,
handler: ( T?, error: ApiError? ) -> Void )
{
switch(response.result) {
case .Success(let JSON):
handler( T( fromJSON: JSON as! [String : AnyObject] ), error: nil )
......@@ -69,7 +71,21 @@ public class Resource<T: JSONSerializable> {
return .Unknown( error: error )
}
public func member( memberID: AnyObject ) -> Resource {
let memberPath = resourceMemberPath( memberID )
return Resource( api: api, path: memberPath )
}
public func resource( resourcePath: String ) -> Resource {
let nestedPath = nestedResourcePath( resourcePath )
return Resource( api: api, path: nestedPath )
}
func resourceMemberPath( memberId: AnyObject ) -> String {
return "\(path)/\(memberId)"
}
}
\ No newline at end of file
func nestedResourcePath( nestedResourcePath: String ) -> String {
return "\(path)/\(nestedResourcePath)"
}
}
......@@ -25,7 +25,7 @@ class ApiTests: QuickSpec {
let api = Api( baseURL: baseURL, apiPath: apiPath )
describe( "resource" ) {
let resource : Resource<Item> = api.resource( "items" )
let resource : Resource = api.resource( "items" )
it( "should have the correct api" ) { expect( resource.api ) == api }
it( "should have the correct path" ) { expect( resource.path ) == "items" }
......
......@@ -33,4 +33,25 @@ extension Item: Equatable {}
func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name
}
struct Component {
let id: Int
let name: String
}
extension Component: JSONSerializable {
init(fromJSON json: [String : AnyObject]) {
self.id = json[ "id" ] as! Int
self.name = json[ "name" ] as! String
}
func toJSON() -> [String : AnyObject] {
var json = [ String: AnyObject ]()
if id > 0 { json[ "id" ] = id }
json[ "name" ] = name
return json
}
}
\ No newline at end of file
......@@ -17,6 +17,7 @@ class ResourceTests: QuickSpec {
// Register stubs
stub( matchApiPath( "items/1" ), builder: json( [ "id": 1, "name": "Item 1" ] ) )
stub( matchApiPath( "items/1/components/1" ), builder: json( [ "id": 1, "name": "Component 1" ] ) )
stub( matchApiPath( "items", method: .POST ), builder: buildItemFromRequest( 2 ) )
stub( matchApiPath( "failedItems", method: .POST ), builder: http( 422 ) )
}
......@@ -25,7 +26,7 @@ class ResourceTests: QuickSpec {
let api = Api( baseURL: baseURL, apiPath: apiPath )
describe( "Resource" ) {
let resource: Resource<Item> = api.resource( "items" )
let resource: Resource = api.resource( "items" )
describe( "item" ) {
context( "when the request is successful" ) {
......@@ -58,9 +59,9 @@ class ResourceTests: QuickSpec {
}
context( "when there's an error" ) {
let failResource : Resource<Item> = api.resource( "failedItems" )
let failResource : Resource = api.resource( "failedItems" )
let itemToCreate = Item( id: 0, name: "Item 2" )
it( "should call the correct endpoint and return the created item" ) {
it( "should call the correct endpoint and return the correct error" ) {
waitUntil { done in
failResource.create( itemToCreate ) { ( item: Item?, error: ApiError? ) in
expect( item ).to( beNil() )
......@@ -72,6 +73,37 @@ class ResourceTests: QuickSpec {
}
}
}
describe( "member" ) {
let member = resource.member( 1 )
it( "should add the correct path" ) {
expect( member.api ).to( equal( resource.api ) )
expect( member.path ).to( equal( "\(resource.path)/1" ) )
}
}
describe( "resource" ) {
let nestedResource = resource.resource( "components" )
it( "should add the correct path" ) {
expect( nestedResource.api ).to( equal( resource.api ) )
expect( nestedResource.path ).to( equal( "\(resource.path)/components" ) )
}
}
context( "when getting a nested resource item" ) {
let nestedResource = resource.member( 1 ).resource( "components" )
it( "should call the correct endpoint and return correct the item" ) {
waitUntil { done in
nestedResource.item( 1 ) { ( component: Component?, error: ApiError? ) in
expect( component?.id ) == 1
expect( component?.name ) == "Component 1"
done()
}
}
}
}
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment