-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathLOFaultObject.j
266 lines (219 loc) · 8.9 KB
/
LOFaultObject.j
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/*
* Created by Martin Carlberg on July 8, 2013.
* Copyright 2013, All rights reserved.
*/
@import <Foundation/CPArray.j>
@import <Foundation/CPPredicate.j>
@import "LOFault.j"
@class LOObjectContext;
@implementation LOFaultObject : CPObject <LOFault> {
LOObjectContext objectContext @accessors;
// id masterObject @accessors;
// CPString relationshipKey @accessors;
CPString entityName @accessors;
CPString primaryKey @accessors;
BOOL faultFired @accessors;
BOOL faultPopulated @accessors;
id forwardingTarget;
}
+ (LOFaultObject)faultObjectWithObjectContext:(CPObjectContext)anObjectContext entityName:(CPString)anEntityName primaryKey:(CPString)aPrimaryKey {
return [[LOFaultObject alloc] initWithObjectContext:anObjectContext entityName:anEntityName primaryKey:aPrimaryKey];
}
- (id)initWithObjectContext:(CPObjectContext)anObjectContext entityName:(CPString)anEntityName primaryKey:(CPString)aPrimaryKey {
self = [super init];
if (self) {
faultFired = NO;
objectContext = anObjectContext;
entityName = anEntityName;
primaryKey = aPrimaryKey;
forwardingTarget = [anObjectContext createNewObjectForType:anEntityName];
if (forwardingTarget == nil)
return nil;
var objectStore = [anObjectContext objectStore];
// Set the type of the object. Depending on how the type is stored it should live on
// after the fault is morphed to the real object
[objectStore setType:anEntityName onObject:self];
}
return self;
}
- (id)copy {
var copy = [super copy];
copy.objectContext = self.objectContext;
copy.entityName = self.entityName;
copy.primaryKey = self.primaryKey;
copy.faultFired = self.faultFired;
copy.faultPopulated = self.faultPopulated;
return copy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return forwardingTarget;
}
- (void)setValue:(id)aValue forKey:(CPString)aKey {
if (@"faultFired" === aKey) {
[self willChangeValueForKey:aKey];
faultFired = aValue;
[self didChangeValueForKey:aKey];
} else if (@"faultPopulated" === aKey) {
[self willChangeValueForKey:aKey];
faultPopulated = aValue;
[self didChangeValueForKey:aKey];
} else {
[self _requestFaultIfNecessary];
// TODO: Save all the set values and maybe apply them later???
[super setValue:aValue forKey:aKey];
}
}
- (id)valueForKey:(CPString)aKey {
switch (aKey) {
case @"faultFired":
return faultFired;
case @"faultPopulated":
return faultPopulated;
// TODO: Take the primary key attribute name from the model
case @"primaryKey":
return primaryKey;
}
[self _requestFaultIfNecessary];
// We do never have a valid value in the fault object so we just return nil
return nil;
}
- (void)_requestFaultIfNecessary {
if (!faultFired) {
[self requestFaultWithCompletionHandler:nil];
}
}
/*!
Designated method for requesting a fault.
This is hard coded: The relationshipKey from the master object is used as the entity name
This can be changed when we are using a model.
*/
- (void)requestFaultWithRequestId:(id)aRequestId completionHandler:(Function)aCompletionBlock {
if (!faultFired) {
[self setFaultFired:YES];
faultFired = YES;
faultPopulated = NO;
[objectContext requestFaultObject:self withRequestId:aRequestId withCompletionHandler:aCompletionBlock];
//CPLog.trace([self className] + " " + _cmd + " Fire fault: '" + entityName + "' q: " + [qualifier description]);
} else if (aCompletionBlock) {
if (faultPopulated) {
aCompletionBlock(self);
} else {
[[objectContext objectStore] addCompletionHandler:aCompletionBlock toTriggeredFault:self];
}
}
}
/*!
Convenience method for calling requestFaultWithRequestId:completionHandler: without a requestId.
*/
- (void)requestFaultWithCompletionHandler:(Function)aCompletionBlock {
[self requestFaultWithRequestId:nil completionHandler:aCompletionBlock];
}
- (void)faultReceivedWithObjects:(CPArray)objectList {
// Here we morph this fault object to the real object now when we are getting its data.
var object;
for (var i = 0, size = [objectList count]; i < size; i++) {
var anObject = [objectList objectAtIndex:i];
if (primaryKey === [anObject valueForKey:@"primaryKey"]) {
object = anObject;
break;
}
}
if (object)
[self morphObjectToX:object];
}
- (void)morphObjectToX:(id)object {
[self morphObjectTo:object];
[objectContext awakeFromFetchForObjects:[object]];
}
- (id)morphObjectTo:(id)anObject {
if (!anObject) {
// If the fetch didn't find an object leave this fault alone. It will never fire again and it will return nil on all keys for 'valueForKey:'
return self;
}
// Save the object context as it will be removed from the fault object below.
var anObjectContext = objectContext,
objectStore = [anObjectContext objectStore],
allAttributes = [objectStore attributeKeysForObject:anObject withType:entityName],
attributes = [];
//CPLog.trace([self className] + " " + _cmd + " Morph to object: '" + [anObject description]);
// All to one relationsships need to be translated from foreign key attribute.
for (var i = 0, size = allAttributes.length; i < size; i++) {
var attributeKey = allAttributes[i];
if ([objectStore isForeignKeyAttribute:attributeKey forType:entityName objectContext:anObjectContext]) // Handle to one relationship.
attributes.push([objectStore toOneRelationshipAttributeForForeignKeyAttribute:attributeKey forType:entityName objectContext:anObjectContext]); // Remove "_fk" at end
else
attributes.push(attributeKey);
}
// Add to many relationships
attributes = [attributes arrayByAddingObjectsFromArray:[objectStore relationshipKeysForObject:anObject withType:entityName]];
// Here we set this a little early, but we can't do it later as the object will morph to another type after this
[self setFaultPopulated:YES];
// Make sure the object context does not record any of these changes. Should not be needed but so we can remove this.
[anObjectContext setDoNotObserveValues:YES];
// Should we morph to a dictionary we have a special case when we need to initiate the CFDictionary
var isDictionary = [anObject isKindOfClass:CPDictionary];
if (isDictionary) {
CFDictionary.call(self);
}
// Start morph.
var oldIsa = self.isa;
self.isa = anObject.isa;
self._UID = anObject._UID;
// If we have a proxy someone is observing this object. Copy over the observing stuff
var oldProxy = self.$KVOPROXY;
if (oldProxy) {
delete self.$KVOPROXY;
var newProxy = [_CPKVOProxy proxyForObject:self];
// Move observers to new Proxy
newProxy._observersForKey = oldProxy._observersForKey;
newProxy._observersForKeyLength = oldProxy._observersForKeyLength;
// Move and create replaced keys.
var replacedKeys = [oldProxy._replacedKeys allObjects];
for (var i = 0, size = replacedKeys.length; i < size; i++) {
[newProxy _replaceModifiersForKey:replacedKeys[i]];
}
}
// Do willChange for all attributes in object
for (var i = 0, size = attributes.length; i < size; i++) {
[self willChangeValueForKey:attributes[i]];
}
// Remove current ivars and copy new ivars from anObject
copyIvars(self, oldIsa, anObject);
// If it is a dictionary we need to copy the object.
if (isDictionary) {
// Only copy the __proto__ when a dictionary as all nornal objective-j objects has 'objj_object'
self.__proto__ = anObject.__proto__;
[self addEntriesFromDictionary:anObject];
}
// Do didChange for all attributes in object
for (var i = 0, size = attributes.length; i < size; i++) {
[self didChangeValueForKey:attributes[i]];
}
[anObjectContext setDoNotObserveValues:NO];
// Return the morphed object.
return self;
}
@end
var copyIvars = function(anObject, anObjectIsa, toObject) {
var ivars = ivarsForClass(anObjectIsa);
for (var i = 0, size = ivars.length; i < size; i++)
delete anObject[ivars[i]];
var ivars = ivarsForClass(toObject.isa);
for (var i = 0, size = ivars.length; i < size; i++) {
var key = ivars[i];
anObject[key] = toObject[key];
}
anObject._UID = toObject._UID;
}
var ivarsForClass = function(aClass) {
var returnIvars = [];
while(aClass) {
var ivars = class_copyIvarList(aClass);
for (var i = 0, size = ivars.length; i < size; i++) {
returnIvars.push(ivars[i].name);
}
aClass = class_getSuperclass(aClass);
}
return returnIvars;
}