{"action":"closed","number":46,"pull_request":{"url":"https://api.github.com/repos/etoile/CoreObject/pulls/46","id":68465101,"html_url":"https://github.com/etoile/CoreObject/pull/46","diff_url":"https://github.com/etoile/CoreObject/pull/46.diff","patch_url":"https://github.com/etoile/CoreObject/pull/46.patch","issue_url":"https://api.github.com/repos/etoile/CoreObject/issues/46","number":46,"state":"closed","locked":false,"title":"Custom migration support","user":{"login":"qmathe","id":74360,"avatar_url":"https://avatars.githubusercontent.com/u/74360?v=3","gravatar_id":"","url":"https://api.github.com/users/qmathe","html_url":"https://github.com/qmathe","followers_url":"https://api.github.com/users/qmathe/followers","following_url":"https://api.github.com/users/qmathe/following{/other_user}","gists_url":"https://api.github.com/users/qmathe/gists{/gist_id}","starred_url":"https://api.github.com/users/qmathe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qmathe/subscriptions","organizations_url":"https://api.github.com/users/qmathe/orgs","repos_url":"https://api.github.com/users/qmathe/repos","events_url":"https://api.github.com/users/qmathe/events{/privacy}","received_events_url":"https://api.github.com/users/qmathe/received_events","type":"User","site_admin":false},"body":"The existing schema migration API works well 95% of the time when changing the schema, but in some special cases it's not enough and the item graph needs to be patched directly (e.g. to change items entities/properties accross packages without altering the metamodel). \r\n\r\nTo do this, I have exposed COSchemaMigrationDriver API and made possible to initialize COObjectGraphContext with a custom driver subclass. A driver subclass can override `-migrateItems:` to implement a custom migrations strategy. Here is an example in Swift:\r\n\r\n```\r\nclass PlaceboardMigrationDriver : COSchemaMigrationDriver {\r\n\r\n\t// For Placeboard 1.0.2 -> 1.0.3\r\n\t//\r\n\t// Forgot to subclass COTagLibrary entity located in CoreObject package when creating Placeboard metamodel, this migration fixes the item graph directly.\r\n\tfunc migrateTagLibrary(storeItems: [AnyObject]!) -> [AnyObject]! {\r\n\t\t\r\n\t\t// The guard clause below is equivalent to:\r\n\t\t// if storeItems.count != 1 || item.packageVersion != 0 || item.entityName != \"COTagLibrary\" { return storeItems }\r\n\t\tguard let item = storeItems.first\r\n\t\t    where storeItems.count == 1 && item.packageVersion == 0 && item.entityName == \"COTagLibrary\" else {\r\n\t\t\t\treturn storeItems\r\n\t\t}\r\n\t\tprecondition(item.packageName == \"org.etoile-project.CoreObject\")\r\n\r\n\t\tlet newItem = item.mutableCopy() as! COMutableItem\r\n\t\tlet package = modelDescriptionRepository.descriptionForName(NSBundle.mainBundle().bundleIdentifier) as! ETPackageDescription\r\n\t\t\t\r\n\t\tnewItem.entityName = \"PWTagLibrary\"\r\n\t\tnewItem.packageName = package.name\r\n\t\tnewItem.packageVersion = Int64(package.version)\r\n\t\t\t\r\n\t\treturn [newItem]\r\n\t}\r\n\r\n\toverride func migrateItems(storeItems: [AnyObject]!) -> [AnyObject]! {\r\n\t\tlet patchedItems = migrateTagLibrary(storeItems)\r\n\r\n\t\treturn super.migrateItems(patchedItems)\r\n\t}\r\n}\r\n```\r\n\r\nTo create editing contexts that leverage the custom driver above:\r\n```\r\n\t\tlet ctx = COEditingContext(store: store,\r\n\t\t   modelDescriptionRepository: repo,\r\n\t\t         migrationDriverClass: PlaceboardMigrationDriver.self,\r\n\t\t               undoTrackStore: COUndoTrackStore(URL: undoTrackStoreURL))\r\n```","created_at":"2016-04-30T11:03:07Z","updated_at":"2016-05-02T17:15:16Z","closed_at":"2016-05-02T17:15:16Z","merged_at":"2016-05-02T17:15:16Z","merge_commit_sha":"7820c260e49946da6cf3ddc9072c06d70bad31ab","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/etoile/CoreObject/pulls/46/commits","review_comments_url":"https://api.github.com/repos/etoile/CoreObject/pulls/46/comments","review_comment_url":"https://api.github.com/repos/etoile/CoreObject/pulls/comments{/number}","comments_url":"https://api.github.com/repos/etoile/CoreObject/issues/46/comments","statuses_url":"https://api.github.com/repos/etoile/CoreObject/statuses/20cebc750c45717a363841ebfc10bcb0b4a88228","head":{"label":"etoile:custom-migration-support","ref":"custom-migration-support","sha":"20cebc750c45717a363841ebfc10bcb0b4a88228","user":{"login":"etoile","id":641984,"avatar_url":"https://avatars.githubusercontent.com/u/641984?v=3","gravatar_id":"","url":"https://api.github.com/users/etoile","html_url":"https://github.com/etoile","followers_url":"https://api.github.com/users/etoile/followers","following_url":"https://api.github.com/users/etoile/following{/other_user}","gists_url":"https://api.github.com/users/etoile/gists{/gist_id}","starred_url":"https://api.github.com/users/etoile/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/etoile/subscriptions","organizations_url":"https://api.github.com/users/etoile/orgs","repos_url":"https://api.github.com/users/etoile/repos","events_url":"https://api.github.com/users/etoile/events{/privacy}","received_events_url":"https://api.github.com/users/etoile/received_events","type":"Organization","site_admin":false},"repo":{"id":15001136,"name":"CoreObject","full_name":"etoile/CoreObject","owner":{"login":"etoile","id":641984,"avatar_url":"https://avatars.githubusercontent.com/u/641984?v=3","gravatar_id":"","url":"https://api.github.com/users/etoile","html_url":"https://github.com/etoile","followers_url":"https://api.github.com/users/etoile/followers","following_url":"https://api.github.com/users/etoile/following{/other_user}","gists_url":"https://api.github.com/users/etoile/gists{/gist_id}","starred_url":"https://api.github.com/users/etoile/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/etoile/subscriptions","organizations_url":"https://api.github.com/users/etoile/orgs","repos_url":"https://api.github.com/users/etoile/repos","events_url":"https://api.github.com/users/etoile/events{/privacy}","received_events_url":"https://api.github.com/users/etoile/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/etoile/CoreObject","description":"Distributed version control + object persistence framework ","fork":false,"url":"https://api.github.com/repos/etoile/CoreObject","forks_url":"https://api.github.com/repos/etoile/CoreObject/forks","keys_url":"https://api.github.com/repos/etoile/CoreObject/keys{/key_id}","collaborators_url":"https://api.github.com/repos/etoile/CoreObject/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/etoile/CoreObject/teams","hooks_url":"https://api.github.com/repos/etoile/CoreObject/hooks","issue_events_url":"https://api.github.com/repos/etoile/CoreObject/issues/events{/number}","events_url":"https://api.github.com/repos/etoile/CoreObject/events","assignees_url":"https://api.github.com/repos/etoile/CoreObject/assignees{/user}","branches_url":"https://api.github.com/repos/etoile/CoreObject/branches{/branch}","tags_url":"https://api.github.com/repos/etoile/CoreObject/tags","blobs_url":"https://api.github.com/repos/etoile/CoreObject/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/etoile/CoreObject/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/etoile/CoreObject/git/refs{/sha}","trees_url":"https://api.github.com/repos/etoile/CoreObject/git/trees{/sha}","statuses_url":"https://api.github.com/repos/etoile/CoreObject/statuses/{sha}","languages_url":"https://api.github.com/repos/etoile/CoreObject/languages","stargazers_url":"https://api.github.com/repos/etoile/CoreObject/stargazers","contributors_url":"https://api.github.com/repos/etoile/CoreObject/contributors","subscribers_url":"https://api.github.com/repos/etoile/CoreObject/subscribers","subscription_url":"https://api.github.com/repos/etoile/CoreObject/subscription","commits_url":"https://api.github.com/repos/etoile/CoreObject/commits{/sha}","git_commits_url":"https://api.github.com/repos/etoile/CoreObject/git/commits{/sha}","comments_url":"https://api.github.com/repos/etoile/CoreObject/comments{/number}","issue_comment_url":"https://api.github.com/repos/etoile/CoreObject/issues/comments{/number}","contents_url":"https://api.github.com/repos/etoile/CoreObject/contents/{+path}","compare_url":"https://api.github.com/repos/etoile/CoreObject/compare/{base}...{head}","merges_url":"https://api.github.com/repos/etoile/CoreObject/merges","archive_url":"https://api.github.com/repos/etoile/CoreObject/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/etoile/CoreObject/downloads","issues_url":"https://api.github.com/repos/etoile/CoreObject/issues{/number}","pulls_url":"https://api.github.com/repos/etoile/CoreObject/pulls{/number}","milestones_url":"https://api.github.com/repos/etoile/CoreObject/milestones{/number}","notifications_url":"https://api.github.com/repos/etoile/CoreObject/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/etoile/CoreObject/labels{/name}","releases_url":"https://api.github.com/repos/etoile/CoreObject/releases{/id}","deployments_url":"https://api.github.com/repos/etoile/CoreObject/deployments","created_at":"2013-12-07T06:34:10Z","updated_at":"2016-03-27T14:55:58Z","pushed_at":"2016-05-02T17:15:17Z","git_url":"git://github.com/etoile/CoreObject.git","ssh_url":"git@github.com:etoile/CoreObject.git","clone_url":"https://github.com/etoile/CoreObject.git","svn_url":"https://github.com/etoile/CoreObject","homepage":"http://coreobject.org","size":19998,"stargazers_count":267,"watchers_count":267,"language":"Objective-C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":10,"forks":12,"open_issues":10,"watchers":267,"default_branch":"master"}},"base":{"label":"etoile:master","ref":"master","sha":"ebe02e414833a3b7dc8bd85312937c58ae9314f1","user":{"login":"etoile","id":641984,"avatar_url":"https://avatars.githubusercontent.com/u/641984?v=3","gravatar_id":"","url":"https://api.github.com/users/etoile","html_url":"https://github.com/etoile","followers_url":"https://api.github.com/users/etoile/followers","following_url":"https://api.github.com/users/etoile/following{/other_user}","gists_url":"https://api.github.com/users/etoile/gists{/gist_id}","starred_url":"https://api.github.com/users/etoile/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/etoile/subscriptions","organizations_url":"https://api.github.com/users/etoile/orgs","repos_url":"https://api.github.com/users/etoile/repos","events_url":"https://api.github.com/users/etoile/events{/privacy}","received_events_url":"https://api.github.com/users/etoile/received_events","type":"Organization","site_admin":false},"repo":{"id":15001136,"name":"CoreObject","full_name":"etoile/CoreObject","owner":{"login":"etoile","id":641984,"avatar_url":"https://avatars.githubusercontent.com/u/641984?v=3","gravatar_id":"","url":"https://api.github.com/users/etoile","html_url":"https://github.com/etoile","followers_url":"https://api.github.com/users/etoile/followers","following_url":"https://api.github.com/users/etoile/following{/other_user}","gists_url":"https://api.github.com/users/etoile/gists{/gist_id}","starred_url":"https://api.github.com/users/etoile/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/etoile/subscriptions","organizations_url":"https://api.github.com/users/etoile/orgs","repos_url":"https://api.github.com/users/etoile/repos","events_url":"https://api.github.com/users/etoile/events{/privacy}","received_events_url":"https://api.github.com/users/etoile/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/etoile/CoreObject","description":"Distributed version control + object persistence framework ","fork":false,"url":"https://api.github.com/repos/etoile/CoreObject","forks_url":"https://api.github.com/repos/etoile/CoreObject/forks","keys_url":"https://api.github.com/repos/etoile/CoreObject/keys{/key_id}","collaborators_url":"https://api.github.com/repos/etoile/CoreObject/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/etoile/CoreObject/teams","hooks_url":"https://api.github.com/repos/etoile/CoreObject/hooks","issue_events_url":"https://api.github.com/repos/etoile/CoreObject/issues/events{/number}","events_url":"https://api.github.com/repos/etoile/CoreObject/events","assignees_url":"https://api.github.com/repos/etoile/CoreObject/assignees{/user}","branches_url":"https://api.github.com/repos/etoile/CoreObject/branches{/branch}","tags_url":"https://api.github.com/repos/etoile/CoreObject/tags","blobs_url":"https://api.github.com/repos/etoile/CoreObject/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/etoile/CoreObject/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/etoile/CoreObject/git/refs{/sha}","trees_url":"https://api.github.com/repos/etoile/CoreObject/git/trees{/sha}","statuses_url":"https://api.github.com/repos/etoile/CoreObject/statuses/{sha}","languages_url":"https://api.github.com/repos/etoile/CoreObject/languages","stargazers_url":"https://api.github.com/repos/etoile/CoreObject/stargazers","contributors_url":"https://api.github.com/repos/etoile/CoreObject/contributors","subscribers_url":"https://api.github.com/repos/etoile/CoreObject/subscribers","subscription_url":"https://api.github.com/repos/etoile/CoreObject/subscription","commits_url":"https://api.github.com/repos/etoile/CoreObject/commits{/sha}","git_commits_url":"https://api.github.com/repos/etoile/CoreObject/git/commits{/sha}","comments_url":"https://api.github.com/repos/etoile/CoreObject/comments{/number}","issue_comment_url":"https://api.github.com/repos/etoile/CoreObject/issues/comments{/number}","contents_url":"https://api.github.com/repos/etoile/CoreObject/contents/{+path}","compare_url":"https://api.github.com/repos/etoile/CoreObject/compare/{base}...{head}","merges_url":"https://api.github.com/repos/etoile/CoreObject/merges","archive_url":"https://api.github.com/repos/etoile/CoreObject/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/etoile/CoreObject/downloads","issues_url":"https://api.github.com/repos/etoile/CoreObject/issues{/number}","pulls_url":"https://api.github.com/repos/etoile/CoreObject/pulls{/number}","milestones_url":"https://api.github.com/repos/etoile/CoreObject/milestones{/number}","notifications_url":"https://api.github.com/repos/etoile/CoreObject/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/etoile/CoreObject/labels{/name}","releases_url":"https://api.github.com/repos/etoile/CoreObject/releases{/id}","deployments_url":"https://api.github.com/repos/etoile/CoreObject/deployments","created_at":"2013-12-07T06:34:10Z","updated_at":"2016-03-27T14:55:58Z","pushed_at":"2016-05-02T17:15:17Z","git_url":"git://github.com/etoile/CoreObject.git","ssh_url":"git@github.com:etoile/CoreObject.git","clone_url":"https://github.com/etoile/CoreObject.git","svn_url":"https://github.com/etoile/CoreObject","homepage":"http://coreobject.org","size":19998,"stargazers_count":267,"watchers_count":267,"language":"Objective-C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":10,"forks":12,"open_issues":10,"watchers":267,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/etoile/CoreObject/pulls/46"},"html":{"href":"https://github.com/etoile/CoreObject/pull/46"},"issue":{"href":"https://api.github.com/repos/etoile/CoreObject/issues/46"},"comments":{"href":"https://api.github.com/repos/etoile/CoreObject/issues/46/comments"},"review_comments":{"href":"https://api.github.com/repos/etoile/CoreObject/pulls/46/comments"},"review_comment":{"href":"https://api.github.com/repos/etoile/CoreObject/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/etoile/CoreObject/pulls/46/commits"},"statuses":{"href":"https://api.github.com/repos/etoile/CoreObject/statuses/20cebc750c45717a363841ebfc10bcb0b4a88228"}},"merged":true,"mergeable":null,"mergeable_state":"unknown","merged_by":{"login":"qmathe","id":74360,"avatar_url":"https://avatars.githubusercontent.com/u/74360?v=3","gravatar_id":"","url":"https://api.github.com/users/qmathe","html_url":"https://github.com/qmathe","followers_url":"https://api.github.com/users/qmathe/followers","following_url":"https://api.github.com/users/qmathe/following{/other_user}","gists_url":"https://api.github.com/users/qmathe/gists{/gist_id}","starred_url":"https://api.github.com/users/qmathe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qmathe/subscriptions","organizations_url":"https://api.github.com/users/qmathe/orgs","repos_url":"https://api.github.com/users/qmathe/repos","events_url":"https://api.github.com/users/qmathe/events{/privacy}","received_events_url":"https://api.github.com/users/qmathe/received_events","type":"User","site_admin":false},"comments":3,"review_comments":0,"commits":5,"additions":142,"deletions":42,"changed_files":13}}