mysterioustrousers / MTMigration

Manages blocks of code that only need to run once on version updates in iOS apps.
BSD 2-Clause "Simplified" License
914 stars 50 forks source link

Differentiate between upgrade and new install #3

Open Sjors opened 11 years ago

Sjors commented 11 years ago

A common use case for me is to distinguish new installs from updates. Often I need to correct something in the database of old users, but don't touch it for fresh installs (because it's fixed out of the box for them).

Perhaps something like this would make sense:

[MTMigration migrateToVersion:@"1.1" block:^{
    if([[MTMigration firstVersionKnownOnThisDevice] isEqualToString:@"1.1"]) {
        // Treat new users differently
    }
    [obj doSomeDataStuff];
}];

I try to avoid this situation; if often happens when you ship some initial data with the app and the app doesn't sync with a server.

pwightman commented 11 years ago

This is a great point, I've been thinking about this for a while too. I had been thinking about an API like:

[MTMigration migrateFromVersion:@"1.0" block:^{ ... }];

That way you could use migrateFromVersion when you need to correct something from a previous version, and migrateToVersion when it's something related to the new version (database migrations, etc.)

Does that work for the situations you are thinking? Or is it not granular enough?

Sjors commented 11 years ago

It doesn't always make sense to "blame" a specific previous version, so although this would work for me, it might be confusing.

pwightman commented 11 years ago

True, both have their uses. If you have time to make a pull request with this change, that'd be great, otherwise I can look into it sometime next week. The only change I'd make is to name the method firstKnownVersion.

Sjors commented 11 years ago

Another solution is to only run a migration if MTMigration knows that there has been a previous version. So if a user installs version 1.1 from the app store, migrateToVersion:@"1.1" should not run. It should only run if he updated from an earlier version.

This means that it should track each new version, even if there is no migration. It's also not backwards compatible, so you'd need a new method name for it.

Sjors commented 11 years ago

I'd like to use it in some other projects first before I change anything.

pwightman commented 11 years ago

I'd have to flesh out the details a bit of whether I like the idea of not migrating on fresh installs. Initially, I can't see a problem though, since migrating on v1.0 of your app doesn't really make sense/isn't necessary.

Sjors commented 11 years ago

Many apps do something specifically on their first run, such as creating the sqlite file for Core Data and populating it with some initial data. I don't think that belongs in a migration. I also think it makes sense to improve such code on each version, so that new users get the latest version instantly. I think that's better than the alternative: always starting with the initial version and then applying fixes one by one through migrations. Mostly because that's very error prone and partially because it could be slow.

That would be my argument in favor for not migrating on fresh installs.

I also suspect that Core Data works this way too; i.e. it won't look at old versions unless it needs to migrate.

On the other hand, all past Ruby on Rails migrations are run even when you install it on a fresh server. Of course that doesn't happen very often and it's also a best practice to delete these migrations every once in a while and replace them with what's in schema.rb (if you're sure none of your servers or colleagues still needs those migrations). You're also not supposed to populate a database using migrations in Rails.

But it's probably a good idea to sleep on this for a while :-)

jeddison commented 11 years ago

Have you had any further thought on this topic?

Whilst initially thinking that a migration on a new install was inefficient, I realised that in practise I can remove old intermediate migrations from the code as there is only ever the one new version available. That reduces the potential errors and slow down for me at least.

However I think it would still be useful to be able to distinguish between a new install or upgrade, so would welcome a - (BOOL) firstVersionKnownOnThis Device (or similar) method. Beyond data updates, a scenario for using this could be as simple as displaying a different welcome message.

peymano commented 11 years ago

:+1: to not running migrations on first install.

pwightman commented 11 years ago

@jeddison That was exactly my thinking, that if you don't want migrations to run anymore then they should just be removed.

Differentiating between new install and upgrade is certainly helpful, I agree. The one hard part about it is that I couldn't think of a way to make the method 100% reliable in all cases because it's hard to distinguish between an upgrade and a fresh install. MTMigration keeps track of it, so as long as you had MTMigration in your app from your first release, then a migrateOnFreshInstall method (or whatever) would be reliable. However, if you added MTMigration on your second release (or anytime thereafter) then I couldn't think of a way to distinguish between an upgrade and a fresh install since from MTMigration's point of view, they would both look like fresh installs.

It's also worth noting that this has implications on whether to run general migrations on first install or not for the same reasons. /cc @peymano

If you have any thoughts on ways around this, I'm more than willing to hear it. I hesitate to add a method that only sometimes works as advertised, but if there's really no possible way around this, then it's probably worth adding the method anyway with clear disclaimers. Discussions and pull requests welcome if this is something you're currently working on in your apps.

ageorgios commented 10 years ago

+1 for migrateFromLowerVersionThan

or

(BOOL)shouldPerformMigration

fechu commented 10 years ago

Or we chould just add a method like this

+ (BOOL)isCleanInstall;

and let the users do whatever they want with it. :smile: I would like to see this in MTMigration and would also implement it. Just tell me, what you want.

timshadel commented 10 years ago

I think it's backwards to ask MTMigration to figure out if it's a new install. Instead, we can avoid old migrations if we can tell MTMigration that this is a new install. One way is to look at the app store receipt (search pods), but really you can look at the file system or NSUserDefaults or Keychain or really anything you want. From there, we tell MTMigration that all the migrations up through the current version have been run.

This is workable even now with class extension to expose the methods. It'll avoid running older migrations.

[MTMigration setLastMigrationVersion:[MTMigration appVersion]];

We could hide the detail behind a new method.

[MTMigration markAsNewInstall];

We can use the existing applicationUpdateBlock: to run the install check before any of our migrateToVersion: calls.

[MTMigration applicationUpdateBlock:^{
    // Check if this is a new install
    if (freshInstall) {
      // also run install steps here
      [MTMigration markAsNewInstall];
    }
}];

[MTMigration migrateToVersion:@"0.9" block:^{
    // Some 0.9 stuff
}];

[MTMigration migrateToVersion:@"1.0" block:^{
    // Some 1.0 stuff
}];

Thoughts?

peymano commented 10 years ago

I've been using MTMigration for a while now and while I originally thought I needed this feature, I've done without it and I don't think it's necessary. It's been easy to write migrations that become no-ops for first-time installs.

On Aug 23, 2014, at 6:00 PM, Tim Shadel notifications@github.com wrote:

I think it's backwards to ask MTMigration to figure out if it's a new install. Instead, we can avoid old migrations if we can tell MTMigration that this is a new install. One way is to look at the app store receipt (search pods), but really you can look at the file system or NSUserDefaults or Keychain or really anything you want. From there, we tell MTMigration that all the migrations up through the current version have been run.

This is workable even now with class extension to expose the methods. It'll avoid running older migrations.

[MTMigration setLastMigrationVersion:[MTMigration appVersion]];

We could hide the detail behind a new method.

[MTMigration markAsNewInstall];

We can use the existing applicationUpdateBlock: to run the install check before any of our migrateToVersion: calls.

[MTMigration applicationUpdateBlock:^{ // Check if this is a new install if (freshInstall) { // also run install steps here [MTMigration markAsNewInstall]; }}]; [MTMigration migrateToVersion:@"0.9" block:^{ // Some 0.9 stuff}]; [MTMigration migrateToVersion:@"1.0" block:^{ // Some 1.0 stuff}];

Thoughts?

— Reply to this email directly or view it on GitHub https://github.com/mysterioustrousers/MTMigration/issues/3#issuecomment-53172803 .

ajmccall commented 9 years ago

Hi, I created a pull-request for this. https://github.com/mysterioustrousers/MTMigration/pull/25

I agree though with @pwightman concerns, mostly that

I hesitate to add a method that only sometimes works as advertised, but if there's really no possible way around this, then it's probably worth adding the method anyway with clear disclaimers.

I can't for the life of me workout how to detect a try first install, or the first time MTMigration has been used in the project.

Thoughts?

revolter commented 8 years ago

Is this maintained anymore?

XabierGoros commented 7 years ago

I miss to the same option in this library. It would be nice to include this check to avoid unnecessary migration blocks or custom boiler plate checks in our AppDelegate.