ActiveResourceKit  v1.2 (498.0)
 All Classes Files Functions Variables Typedefs Enumerator Properties Macros Pages
ARResource.m
Go to the documentation of this file.
1 // ActiveResourceKit ARResource.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 "ARResource.h"
26 #import "ARResource+Private.h"
27 
28 // for -[ARService splitOptions:prefixOptions:queryOptions:]
29 // (This makes you wonder. If other classes need to import the private
30 // interface, should not the so-called private methods really become public, not
31 // private?)
32 #import "ARService+Private.h"
33 
34 // for ARRemoveRoot(object)
35 #import "ARFormatMethods.h"
36 
37 // for AMName
38 #import <ActiveModelKit/ActiveModelKit.h>
39 
40 // for ASInflectorUnderscore
41 #import <ActiveSupportKit/ActiveSupportKit.h>
42 
43 // continuation class
44 @interface ARResource()
45 {
46  NSMutableDictionary *__strong _attributes;
47 }
48 
49 @end
50 
51 @implementation ARResource
52 
53 // designated initialiser
54 - (id)init
55 {
56  self = [super init];
57  if (self)
58  {
59  _attributes = [[NSMutableDictionary alloc] init];
60  }
61  return self;
62 }
63 
64 - (id)copyWithZone:(NSZone *)zone
65 {
66  ARResource *copy = [[[self class] allocWithZone:zone] init];
67  [copy setService:[self service]];
68  // Copy attributes using the standard getter and setter. This does not
69  // deep-copy the objects and thereby assumes a simple set of primitive
70  // attributes.
71  [copy setAttributes:[self attributes]];
72  [copy setPrefixOptions:[self prefixOptions]];
73  [copy setPersisted:[self persisted]];
74  return copy;
75 }
76 
77 + (ARService *)service
78 {
79  ARService *service = [[ARService alloc] init];
80  if ([self respondsToSelector:@selector(site)])
81  {
82  [service setSite:[self performSelector:@selector(site)]];
83  }
84  if ([self respondsToSelector:@selector(elementName)])
85  {
86  [service setElementName:[self performSelector:@selector(elementName)]];
87  }
88  else if (self != [ARResource class] && [self isSubclassOfClass:[ARResource class]])
89  {
90  [service setElementName:[[[AMName alloc] initWithClass:self] element]];
91  }
92  return service;
93 }
94 
95 - (id)initWithService:(ARService *)service
96 {
97  // This is not the designated initialiser. Sends -init to self not super.
98  self = [self init];
99  if (self)
100  {
101  [self setService:service];
102  }
103  return self;
104 }
105 
106 - (id)initWithService:(ARService *)service attributes:(NSDictionary *)attributes
107 {
108  self = [self initWithService:service];
109  if (self)
110  {
111  [self loadAttributes:attributes removeRoot:NO];
112  }
113  return self;
114 }
115 
116 - (id)initWithService:(ARService *)service attributes:(NSDictionary *)attributes persisted:(BOOL)persisted
117 {
118  self = [self initWithService:service attributes:attributes];
119  if (self)
120  {
121  [self setPersisted:persisted];
122  }
123  return self;
124 }
125 
126 - (NSDictionary *)optionsForSubelement
127 {
128  return [NSDictionary dictionaryWithObject:[self ID] forKey:[[self service] foreignKey]];
129 }
130 
131 //------------------------------------------------------------------------------
132 #pragma mark Base
133 //------------------------------------------------------------------------------
134 
135 @synthesize service = _service;
136 
138 {
139  ARService *service = [self service];
140  if (service == nil)
141  {
142  [self setService:service = [[self class] service]];
143  }
144  return service;
145 }
146 
147 //------------------------------------------------------------------------------
148 #pragma mark Attributes
149 //------------------------------------------------------------------------------
150 
151 // Store attributes using a mutable dictionary. Take care, however, not to
152 // expose the implementation. The interface only exposes an immutable dictionary
153 // when answering -attributes. The attributes getter makes an immutable copy of
154 // the mutable dictionary.
155 
156 - (NSDictionary *)attributes
157 {
158  return [_attributes copy];
159 }
160 
161 - (void)setAttributes:(NSDictionary *)attributes
162 {
163  // Remove all objects in order to maintain "setter" semantics. Otherwise,
164  // setting really means merging attributes.
165  [_attributes removeAllObjects];
166  [self mergeAttributes:attributes];
167 }
168 
169 - (void)mergeAttributes:(NSDictionary *)attributes
170 {
171  // Take care not to use -[NSObject setValuesForKeysWithDictionary:] because
172  // it filters out all those attributes with null values. By design,
173  // resources preserve all attributes, even those with no values.
174  for (NSString *attributeName in attributes)
175  {
176  id attributeValue = [attributes objectForKey:attributeName];
177  [_attributes setObject:attributeValue forKey:attributeName];
178  }
179 }
180 
181 - (void)loadAttributes:(NSDictionary *)attributes removeRoot:(BOOL)removeRoot
182 {
183  NSDictionary *prefixOptions = nil;
184  ARService *service = [self serviceLazily];
185  [service splitOptions:attributes prefixOptions:&prefixOptions queryOptions:&attributes];
186  [self setPrefixOptions:prefixOptions];
187  if ([attributes count] == 1)
188  {
189  removeRoot = [[service elementNameLazily] isEqualToString:[[[attributes allKeys] objectAtIndex:0] description]];
190  }
191  if (removeRoot)
192  {
194  }
195  [self setAttributes:attributes];
196 }
197 
198 // Supports key-value coding. Returns attribute values for undefined keys. Hence
199 // you can access resource attributes on the resource itself rather than
200 // indirectly via the attributes property.
201 //
202 // The “key” argument specifies the key using Cocoa conventions of property
203 // keys, namely camel-cased with an initial lower-case letter. The method
204 // implementation converts this to Rails conventions for attribute names, namely
205 // underscored.
206 - (id)valueForUndefinedKey:(NSString *)key
207 {
208  return [_attributes objectForKey:[[ASInflector defaultInflector] underscore:key]];
209 }
210 
211 - (void)setValue:(id)value forUndefinedKey:(NSString *)key
212 {
213  [_attributes setObject:value forKey:[[ASInflector defaultInflector] underscore:key]];
214 }
215 
216 - (void)setNilValueForKey:(NSString *)key
217 {
218  [self setValue:[NSNull null] forKey:key];
219 }
220 
221 /*!
222  * @brief Transfers values from the resource attributes to a dictionary,
223  * answering the dictionary.
224  * @details Accesses the values by sending @c -valueForKey:aKey to @c self,
225  * where @c aKey conforms to key-value coding requirements, i.e. lower
226  * camel-case. This invokes @c -valueForUndefinedKey:aKey on @c self which
227  * performs the @c aKey to @c a_key translation to Rails resource attribute
228  * name.
229  */
230 - (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys
231 {
232  NSMutableDictionary *keyedValues = [NSMutableDictionary dictionary];
233  for (NSString *key in keys)
234  {
235  [keyedValues setObject:[self valueForKey:key] forKey:key];
236  }
237  return [keyedValues copy];
238 }
239 
240 - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues
241 {
242  for (NSString *key in keyedValues)
243  {
244  [self setValue:[keyedValues objectForKey:key] forKey:key];
245  }
246 }
247 
248 //------------------------------------------------------------------------------
249 #pragma mark Prefix Options
250 //------------------------------------------------------------------------------
251 
252 @synthesize prefixOptions = _prefixOptions;
253 
254 //------------------------------------------------------------------------------
255 #pragma mark Schema and Known Attributes
256 //------------------------------------------------------------------------------
257 
258 - (NSDictionary *)schema
259 {
260  NSDictionary *schema = [[self serviceLazily] schema];
261  return schema ? schema : [self attributes];
262 }
263 
264 - (NSArray *)knownAttributes
265 {
266  NSMutableSet *set = [NSMutableSet set];
267  [set addObjectsFromArray:[[self serviceLazily] knownAttributes]];
268  [set addObjectsFromArray:[[self attributes] allKeys]];
269  return [set allObjects];
270 }
271 
272 //------------------------------------------------------------------------------
273 #pragma mark Persisted
274 //------------------------------------------------------------------------------
275 
276 @synthesize persisted = _persisted;
277 
278 - (BOOL)isNew
279 {
280  return ![self persisted];
281 }
282 
283 - (BOOL)isNewRecord
284 {
285  return [self isNew];
286 }
287 
288 //------------------------------------------------------------------------------
289 #pragma mark Primary Key
290 //------------------------------------------------------------------------------
291 
292 - (NSNumber *)ID
293 {
294  id ID = [_attributes objectForKey:[[self serviceLazily] primaryKeyLazily]];
295  return ID && [ID isKindOfClass:[NSNumber class]] ? ID : nil;
296 }
297 
298 - (void)setID:(NSNumber *)ID
299 {
300  [_attributes setObject:ID forKey:[[self serviceLazily] primaryKeyLazily]];
301 }
302 
303 //------------------------------------------------------------------------------
304 #pragma mark RESTful Services
305 //------------------------------------------------------------------------------
306 
307 - (void)saveWithCompletionHandler:(void (^)(ARHTTPResponse *response, NSError *error))completionHandler
308 {
309  if ([self isNew])
310  {
311  [self createWithCompletionHandler:completionHandler];
312  }
313  else
314  {
315  [self updateWithCompletionHandler:completionHandler];
316  }
317 }
318 
319 - (void)destroyWithCompletionHandler:(void (^)(ARHTTPResponse *response, NSError *error))completionHandler
320 {
321  [[self serviceLazily] delete:[self elementPathWithOptions:nil] completionHandler:^(ARHTTPResponse *response, id object, NSError *error) {
322  completionHandler(response, error);
323  }];
324 }
325 
326 - (void)existsWithCompletionHandler:(void (^)(ARHTTPResponse *response, BOOL exists, NSError *error))completionHandler
327 {
328  [[self serviceLazily] existsWithID:[self ID] options:nil completionHandler:^(ARHTTPResponse *response, BOOL exists, NSError *error) {
329  completionHandler(response, exists, error);
330  }];
331 }
332 
333 - (NSData *)encode
334 {
335  ARService *service = [self service];
336  return [[service formatLazily] encode:[NSDictionary dictionaryWithObject:[self attributes] forKey:[service elementNameLazily]] error:NULL];
337 }
338 
339 //------------------------------------------------------------------------------
340 #pragma mark Object
341 //------------------------------------------------------------------------------
342 
343 - (NSString *)description
344 {
345  NSMutableString *string = [NSMutableString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self];
346  ARService *service = [self service];
347  if (service)
348  {
349  NSString *elementName = [service elementName];
350  if (elementName)
351  {
352  [string appendFormat:@" element:%@", elementName];
353  }
354  NSString *collectionName = [service collectionName];
355  if (collectionName)
356  {
357  [string appendFormat:@" collection:%@", collectionName];
358  }
359  NSDictionary *attributes = [self attributes];
360  for (NSString *attributeName in attributes)
361  {
362  [string appendFormat:@" %@=%@", attributeName, [attributes objectForKey:attributeName]];
363  }
364  }
365  return [string copy];
366 }
367 
368 @end