ActiveResourceKit  v1.2 (498.0)
 All Classes Files Functions Variables Typedefs Enumerator Properties Macros Pages
ARService+Private.m
Go to the documentation of this file.
1 // ActiveResourceKit ARService+Private.m
2 //
3 // Copyright © 2011, 2012, Roy Ratcliffe, Pioneering Software, United Kingdom
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EITHER
16 // EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
18 // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
19 // OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 // DEALINGS IN THE SOFTWARE.
22 //
23 //------------------------------------------------------------------------------
24 
25 #import "ARService+Private.h"
26 #import "ARConnection.h"
27 
29 #import <ActiveModelKit/ActiveModelKit.h>
30 #import <ActiveSupportKit/ActiveSupportKit.h>
31 
32 NSString *const ARFromKey = @"from";
33 NSString *const ARParamsKey = @"params";
34 
35 NSString *ARQueryStringForOptions(NSDictionary *options)
36 {
37  return options == nil || [options count] == 0 ? @"" : [NSString stringWithFormat:@"?%@", [options toQueryWithNamespace:nil]];
38 }
39 
40 @implementation ARService(Private)
41 
42 - (id<ARFormat>)defaultFormat
43 {
44  return [ARJSONFormat JSONFormat];
45 }
46 
47 - (NSString *)defaultElementName
48 {
49  return [[[AMName alloc] initWithClass:[self class]] element];
50 }
51 
52 - (NSString *)defaultCollectionName
53 {
54  return [[ASInflector defaultInflector] pluralize:[self elementNameLazily]];
55 }
56 
57 - (NSString *)defaultPrimaryKey
58 {
59  return @"id";
60 }
61 
62 - (NSString *)defaultPrefixSource
63 {
64  return [[self site] path];
65 }
66 
67 - (void)findEveryWithOptions:(NSDictionary *)options completionHandler:(void (^)(ARHTTPResponse *response, NSArray *resources, NSError *error))completionHandler
68 {
69  NSString *path;
70  NSDictionary *prefixOptions;
71  NSString *from = [options objectForKey:ARFromKey];
72  if (from && [from isKindOfClass:[NSString class]])
73  {
74  prefixOptions = nil;
75  path = [NSString stringWithFormat:@"%@%@", from, ARQueryStringForOptions([options objectForKey:ARParamsKey])];
76  }
77  else
78  {
79  NSDictionary *queryOptions = nil;
80  [self splitOptions:options prefixOptions:&prefixOptions queryOptions:&queryOptions];
81  path = [self collectionPathWithPrefixOptions:prefixOptions queryOptions:queryOptions];
82  }
83  [self get:path completionHandler:^(ARHTTPResponse *response, id object, NSError *error) {
84  if ([object isKindOfClass:[NSArray class]])
85  {
86  completionHandler(response, [self instantiateCollection:object prefixOptions:prefixOptions], nil);
87  }
88  else
89  {
90  completionHandler(response, nil, [NSError errorWithDomain:ARErrorDomain code:ARUnsupportedRootObjectTypeError userInfo:nil]);
91  }
92  }];
93 }
94 
95 - (NSArray *)instantiateCollection:(NSArray *)collection prefixOptions:(NSDictionary *)prefixOptions
96 {
97  NSMutableArray *resources = [NSMutableArray array];
98  for (NSDictionary *attributes in collection)
99  {
100  [resources addObject:[self instantiateRecordWithAttributes:attributes prefixOptions:prefixOptions]];
101  }
102  return [resources copy];
103 }
104 
105 - (ARResource *)instantiateRecordWithAttributes:(NSDictionary *)attributes prefixOptions:(NSDictionary *)prefixOptions
106 {
107  Class aClass = NSClassFromString(ASInflectorCamelize([self elementNameLazily], YES));
108  if (aClass == nil || ![aClass isSubclassOfClass:[ARResource class]])
109  {
110  aClass = [ARResource class];
111  }
112  ARResource *resource = [[aClass alloc] initWithService:self attributes:attributes persisted:YES];
113  [resource setPrefixOptions:prefixOptions];
114  return resource;
115 }
116 
117 - (NSSet *)prefixParameters
118 {
119  NSMutableSet *parameters = [NSMutableSet set];
120  NSString *prefixSource = [self prefixSourceLazily];
121  for (NSTextCheckingResult *result in [[NSRegularExpression regularExpressionWithPattern:@":\\w+" options:0 error:NULL] matchesInString:prefixSource options:0 range:NSMakeRange(0, [prefixSource length])])
122  {
123  [parameters addObject:[[prefixSource substringWithRange:[result range]] substringFromIndex:1]];
124  }
125  return [parameters copy];
126 }
127 
128 - (void)splitOptions:(NSDictionary *)options prefixOptions:(NSDictionary **)outPrefixOptions queryOptions:(NSDictionary **)outQueryOptions
129 {
130  NSSet *prefixParameters = [self prefixParameters];
131  NSMutableDictionary *prefixOptions = [NSMutableDictionary dictionary];
132  NSMutableDictionary *queryOptions = [NSMutableDictionary dictionary];
133  [options enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
134  [[prefixParameters member:key] != nil ? prefixOptions : queryOptions setObject:obj forKey:key];
135  }];
136  if (outPrefixOptions)
137  {
138  *outPrefixOptions = [prefixOptions copy];
139  }
140  if (outQueryOptions)
141  {
142  *outQueryOptions = [queryOptions copy];
143  }
144 }
145 
146 //------------------------------------------------------------------------------
147 #pragma mark HTTP Requests
148 //------------------------------------------------------------------------------
149 
150 // Under Rails, the HTTP request methods belong to a separate connection class,
151 // ActiveResource::Connection. That class acts as a shim in-between active
152 // resources and the Net::HTTP library, handling authentication and encryption
153 // if the connection requires such features. The Objective-C implementation
154 // obviates the connection class by delegating to the underlying Apple
155 // NSURLConnection implementation for the handling of authentication and
156 // encryption.
157 
158 - (void)get:(NSString *)path completionHandler:(ARServiceRequestCompletionHandler)completionHandler
159 {
160  [self requestHTTPMethod:ARHTTPGetMethod path:path body:nil completionHandler:completionHandler];
161 }
162 
163 - (void)delete:(NSString *)path completionHandler:(ARServiceRequestCompletionHandler)completionHandler
164 {
165  [self requestHTTPMethod:ARHTTPDeleteMethod path:path body:nil completionHandler:completionHandler];
166 }
167 
168 - (void)put:(NSString *)path body:(NSData *)data completionHandler:(ARServiceRequestCompletionHandler)completionHandler
169 {
170  [self requestHTTPMethod:ARHTTPPutMethod path:path body:data completionHandler:completionHandler];
171 }
172 
173 - (void)post:(NSString *)path body:(NSData *)data completionHandler:(ARServiceRequestCompletionHandler)completionHandler
174 {
175  [self requestHTTPMethod:ARHTTPPostMethod path:path body:data completionHandler:completionHandler];
176 }
177 
178 - (void)head:(NSString *)path completionHandler:(ARServiceRequestCompletionHandler)completionHandler
179 {
180  [self requestHTTPMethod:ARHTTPHeadMethod path:path body:nil completionHandler:completionHandler];
181 }
182 
183 - (void)requestHTTPMethod:(NSString *)HTTPMethod path:(NSString *)path body:(NSData *)data completionHandler:(ARServiceRequestCompletionHandler)completionHandler
184 {
185  ARConnection *connection = [self connectionLazily];
186  NSMutableURLRequest *request = [connection requestForHTTPMethod:HTTPMethod path:path headers:[[self headers] copy]];
187  if (data)
188  {
189  [request setHTTPBody:data];
190  }
191  [connection sendRequest:request completionHandler:[self decodeHandlerWithCompletionHandler:completionHandler]];
192 }
193 
194 - (ARConnectionCompletionHandler)decodeHandlerWithCompletionHandler:(ARServiceRequestCompletionHandler)completionHandler
195 {
196  // This response handler exists for two main purposes: (1) to cast the
197  // generalised URL response to a protocol-specific HTTP response; (2) to
198  // decode the body. Purpose number two presumes that all responses arriving
199  // here require format-specific decoding. If this is not true, do not use
200  // this handler. Instead, roll your own.
201  //
202  // Sends -copy to the block thereby transferring the block from the local
203  // stack to the heap. Copy for use after the destruction of this
204  // scope. Compiling for iOS raises a warning unless you copy the block, but
205  // not so for OS X.
206  return // What if the response body proves empty? This happens for// responses to DELETE requests. Never attempt to decode a// zero-length data body. Zero-length data is valid for some// operations. It does not necessarily signal an// error. Instead, pass the result up through to the// higher-level software layers as success. Let higher-level// software make a decision about validity.//// Rails answers a single space for DELETE responses, in// fact for all responses where your action controller uses// the "head" method. Explicitly, the action controller head// method assigns a single space for the response// body. Unfortunately, this fails to successfully// decode. White space does not correctly decode a JSON// object. Instead it produces an error. Work around is// issue by trimming white space.// decoding error// response error// connection error// type error[^(ARHTTPResponse *response, NSError *error) {
207  if (response)
208  {
209  if ([response body])
210  {
211  error = [ARConnection errorForResponse:response];
212  if (error == nil)
213  {
214 
215 
216 
217 
218 
219 
220 
221 
222 
223 
224 
225 
226 
227 
228 
229 
230  NSData *data = [response body];
231  NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
232  if (string)
233  {
234  data = [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] dataUsingEncoding:NSUTF8StringEncoding];
235  }
236  id object = data && [data length] ? [[self formatLazily] decode:data error:&error] : nil;
237  if (error == nil)
238  {
239  completionHandler(response, object, nil);
240  }
241  else
242  {
243 
244  completionHandler(response, nil, error);
245  }
246  }
247  else
248  {
249 
250  completionHandler(response, nil, error);
251  }
252  }
253  else
254  {
255 
256  completionHandler(response, nil, error);
257  }
258  }
259  else
260  {
261 
262  completionHandler(nil, nil, error ? error : [NSError errorWithDomain:ARErrorDomain code:ARResponseIsNotHTTPError userInfo:nil]);
263  }
264  } copy];
265 }
266 
267 @end