ユーザーとグループをRestKitを介してCoreDataオブジェクトにマップし、2つの間の関係を維持しようとしています。
ユーザーの JSON は次のようなものです
{"users":
[{"fullname": "John Doe", "_id": "1"}
...
]
}
グループのJSONは次のようなものです
{"groups":
[{"name": "John's group", "_id": "a",
"members": ["1", "2"]
...
]
}
また、対応する Core Data オブジェクト と もUser
ありGroup
、グループとユーザーの間に 1 対多の関係がマッピングされています。の関係プロパティGroup
が呼び出されmembers
、別のNSArray
呼び出されたプロパティmemberIDs
がすべてのメンバー ユーザーの文字列 ID の配列を保持します。
RestKit で達成したいことは、これらのオブジェクトをロードし、関係をマップすることです。ユーザーをロードするためのコードは単純な標準的なものであり、グループをロードするためのコードは次のようなものです
RKObjectManager* objectManager = [RKObjectManager sharedManager];
RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore];
groupMapping.primaryKeyAttribute = @"identifier";
[groupMapping mapKeyPath:@"_id" toAttribute:@"identifier"];
[groupMapping mapKeyPath:@"name" toAttribute:@"name"];
[groupMapping mapKeyPath:@"members" toAttribute:@"memberIDs"];
[objectManager.mappingProvider setMapping:groupMapping forKeyPath:@"groups"];
// Create an empty user mapping to handle the relationship mapping
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore];
[groupMapping hasMany:@"members" withMapping:userMapping];
[groupMapping connectRelationship:@"members" withObjectForPrimaryKeyAttribute:@"memberIDs"];
RKObjectRouter* router = objectManager.router;
[router routeClass:[Group class] toResourcePath:@"/rest/groups/:identifier"];
[router routeClass:[Group class] toResourcePath:@"/rest/groups/" forMethod:RKRequestMethodPOST];
// Assume url is properly defined to point to the right path...
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
このコードを実行すると、次の警告が数回吐き出されます (関係ごとに約 2 回のようです)。
2012-10-24 08:55:10.170 Test[35023:18503] W restkit.core_data.cache:RKEntityByAttributeCache.m:205 Unable to add object with nil value for attribute 'identifier': <User: 0x86f7fc0> (entity: User; id: 0x8652400 <x-coredata:///User/t01A9EDBD-A523-4806-AFC2-9B06873D764E531> ; data: {
fullname = nil;
identifier = nil;
})
奇妙なことは、関係が適切に設定されていることです(sqliteデータベースを見ても、すべてがうまく見えます)が、RestKitコードで明らかに問題が発生していることに満足できず、何か関係があるようです.上記のコードで作成した偽のユーザー マッピング (ID の配列にマップするものが何もないため、空です)。
たとえば、キーパスマッピングを追加するときなど、代替手段を試しました。
[userMapping mapKeyPath:@"" toAttribute:@"identifier"];
キーパスが空であるため、明らかに文句を言います
2012-10-24 09:24:11.735 Test[35399:14003] !! Uncaught exception !!
[<__NSCFString 0xa57f460> valueForUndefinedKey:]: this class is not key value coding-compliant for the key .
そして、実際のUser
マッピングを使用しようとすると
[groupMapping hasMany:@"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:@"users"]];
次のエラーが表示されます
2012-10-24 09:52:49.973 Test[35799:18403] !! Uncaught exception !!
[<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.
2012-10-24 09:52:49.973 Test[35799:18403] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.'
RestKit は memberIDs 配列内の ID 文字列自体をUser
オブジェクトにマップしようとしているようですが、これは意味がありません。id
リレーションシップ配列が、他のオブジェクトへの参照 ID を持つキー (たとえば と呼ばれる) を持つ辞書のリストである例を見てきました。次に例を示します。
{"groups":
[{"name": "John's group", "_id": "a",
"members": [
{"_id": "1"},
{"_id": "2"}
]
...
]
}
しかし、JSON をそのように変更したくありません (さらに、RestKit が他の方法をサポートするのに十分柔軟であることを望んでいます)。
RestKit でこの種の関係マッピングを行う適切な方法を知っている人はいますか?
アップデート
最後に、REST インターフェイスを変更して、ユーザー オブジェクト ID を含む辞書のリストを送信し (上記の最後の例と同じように)、それを機能させました。まだセットアップに完全に満足していませんが、コードは次のようになります
RKObjectManager* objectManager = [RKObjectManager sharedManager];
RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore];
groupMapping.primaryKeyAttribute = @"identifier";
[groupMapping mapKeyPath:@"_id" toAttribute:@"identifier"];
[groupMapping mapKeyPath:@"name" toAttribute:@"name"];
[groupMapping mapKeyPath:@"members._id" toAttribute:@"memberIDs"];
[objectManager.mappingProvider setMapping:groupMapping forKeyPath:@"groups"];
// Create an empty user mapping to handle the relationship mapping
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore];
userMapping.primaryKeyAttribute = @"identifier";
[userMapping mapKeyPath:@"_id" toAttribute:@"identifier"];
[groupMapping hasMany:@"members" withMapping:userMapping];
[groupMapping connectRelationship:@"members" withObjectForPrimaryKeyAttribute:@"memberIDs"];
RKObjectRouter* router = objectManager.router;
[router routeClass:[Group class] toResourcePath:@"/rest/groups/:identifier"];
[router routeClass:[Group class] toResourcePath:@"/rest/groups/" forMethod:RKRequestMethodPOST];
// Assume url is properly defined to point to the right path...
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
念のため、これが同様の状況にある他の人に役立ちます。私が取ったアプローチにはまだいくつかの問題があるかもしれませんが、これまでのところうまくいっています (順不同の読み込みも処理できます。これは素晴らしいことです)。