at_yasu's blog

ロード的なことを

CoreData Migration - xcmappingmodel 編 -

先に注意:これはメモ書きです。間違い等や読みにくい等があるけど、ご了承をば。

というわけで、CoreDataのDBをソフトウェアで移行する方法です。勿論APIはありまして、http://developer.apple.com/documentation/Cocoa/Conceptual/CoreDataVersioning/Introduction/Introduction.htmlを参考して下さい。

なお下記から、エンティティの事をテーブル、プロパティの事をフィールド、と呼びます。*1


今回は一番手軽な、MappingModelから。これは、旧テーブルのデータから新テーブルへ移行する際に、それぞれのフィールドのデータに整合性を保つ為に実行させる式などを書きます。

今回はAppleのサンプルコードMigrationV2のテーブルを元に説明。

下に旧新のテーブルの画像がありますので参考にして下さい。

変更されてる部分は結構あり、リストに表すと、こんな感じになります。なお、下にテーブルとリレーションの関連図がありますので参考に。

Chef.name Chef.firstName, Chef.lastName
Chef.training 削除
Chef.recipes そのまま
Ingredient.amount そのまま
Ingredient.name そのまま
Ingredient.recipes そのまま
Recipe.directions そのまま
Recipe.name そのまま
Recipe.cuisine 新しいテーブルCuisineになり、対多関連になり且つ、cuisine.nameの値になった
Recipe.ingredients そのまま
なし Recipe.cuisines == Cuisine.recipesという関連になった


では本題のxcmappingmodelの説明。これも、下にwindowの画像がありますので参考に。

「エンティティマッピング」がテーブルを変更する為のmappingリスト。「プロパティマッピング」がフィールドの値を操作する為のリストです。

エンティティマッピング

適当なマッピングを選択してみましょう。右の一般タブにはいろいろな情報が出てきます。色々出来るみたいです。

ソース
旧テーブルのテーブル名。つまり移行される方のテーブル名。デスティネーションを指定している場合、タイプはAddになり、テーブルが生成されるだけみたいです。
デスティネーション
新テーブルのテーブル名。つまり移行先のテーブル名。何も選択せず、ソーステーブルのみを選択した場合はタイプはRemoveとなり、データはブラックホールへ行くでしょう。おそらく…
マッピング名
新旧のテーブルのマッピング名。プロパティマッピングでも使うので、わかりやすい方が良いです。
H
バージョンハッシュと書いてるけど、今イチどういう役割か、このサンプルコードではわかりません。つか、右で言うどれに当たるのか…
カスタムポリシー
移行の際に使用するクラス名。何も入力しなければ、タイプはTransformになります。
ソース取得
「デフォルト」と「カスタム」があります。どう使うのだろう…

カスタムポリシー

移行の際に使用するクラスみたいです。
「NSEntityMigrationPolicy」クラスを継承する必要があり、Appleのサンプルの場合、

「- (NSArray *)destinationInstancesForSourceInstance:entityMapping:manager:error:」

「- (BOOL)createRelationshipsForDestinationInstance:entityMapping:manager:error:」

の二つを実装しています。*2

プロパティマッピング

T
フィールドの型です。Aだと属性(Attribute)、Rだとリレーションを表します。
名前
新テーブルのフィールド名。
値式
旧テーブルから新テーブルのフィールドに値を入れる際に、実行する、ないしは旧テーブルのフィールド名を指定する。

値式

移行に関して一番重要な部分です。

このサンプルの場合、「Recipe.cuisine」が一番変更されていますので、重点的に見ます。


まず、Recipe -> Recipeを考えます。

cuisinesというリレーションフィールドが新テーブルには増えてますので、プロパティマッピングの名前には、cuisinesというのがあります。値式は「値式を自動生成」にチェックが入り、キーパスには$sourceが入ってます。おそらくですが、$sourceは旧テーブルをさすのだと思います。*3
マッピング名には、RecipeToCuisioneが指定されてます。RecipeToCuisineはRecipeテーブルからCuisineテーブルへのマッピングです。つまりは、Cuisineテーブルを指しているのです。

値をそのまま使う所は、フィールドが属性(Attribute)の場合は、「$source.[field name]」と.でつないでいます。新しくフィールドを追加し、何も値を入れない場合は、値式は空白のままです。例えば、Recipeの「rating」フィールドは新しく追加されましたが、値は何も入れないので空白のままです。

フィールドが関連(Relation)の場合は、キーパスは「$source[.Relation name]」となり、マッピング名は先程と同様に、デスティネーションがその指定先のテーブルであるマッピング名をしていします。例えばRecipeテーブルのingredientsの場合、変更はありませんので、キーパスは「$source.ingredients」、マッピング名はingredientを指している「IngredientToIngredient」マッピングを指定します。キーパスが「$source」だけの時は旧テーブルそのものを指す事になります。


次に、増やされたテーブル「Cuisine」を見ます。

RecipeToCuisineがCuisineテーブルの生成を担っています。ソースは「Recipe」となっています。

フィールド「name」は「Recipe」テーブルの「cuisine」からなので、値式は「$source.cuisine」となっています。フィールド「recipes」は「Recipe」テーブルとのリレーションを司ってます。値式は自動生成しており、キーパスは「$source」、マッピング名は「RecipeToRecipe」となっています。


次にフィールドが分離された場合を見ましょう。

旧テーブルのChefテーブルは、「name」が「firstName」「lastName」に分離されました。つまり、nameの値を処理を通して二つに分けるのです。「fistName」の値式は下のようになっています。

FUNCTION(CAST(
    "MigrationFunctions",
    "Class"
), "getFirstName:" , $source.name)

これは、MigrationFunctionsクラスのgetFirstNameメソッドを実行すると言う事です。このようにすれば、それぞれの値を処理させることができます。なお、「getFirstName」はクラスメソッドです。

以上かな…

古い方のテーブル
新しい方のテーブル
xcmappingmodel

*1:PSQLとかMySQLを使ってると、EntityよりTableの方がしっくり来るんだ。うん、ごめん

*2:要確認

*3:ここらへん、あやふや。ご注意