-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add possibility to define bidirectional migrations #62
Conversation
@agemooij Any chance to take a look at this PR soon? |
@raboof what do you think of this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code seems to always apply all forward and backward migrations instead of allowing them to cancel each other out. Have you considered removing migrations if they would be cancelled out by a backward one?
def &&(secondMigration: Migration[T]): Migration[T] = { | ||
(value: T) ⇒ secondMigration(firstMigration(value)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is not used anymore.
* Creates a Migrator[T, V1] that can function as a builder for | ||
* creating Migrator[T, V2], etc. Its migration will be the identity | ||
* function so calling its migrate function will not have any effect. | ||
*/ | ||
def from[T, V <: V1: VersionInfo]: Migrator[T, V] = new Migrator[T, V](Map(Version.numberFor[V] → identityMigration[T])) | ||
def from[T, V <: V1: VersionInfo]: Migrator[T, V] = new Migrator[T, V](Map(Version.numberFor[V] → identityMigration[T]), Map.empty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make the empty map a default argument of the Migrator
class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because backward migrations are empty when we start defining migration - so empty map is natural initial value.
I am not sure what you mean under
|
@agemooij ping |
Any progress? Maybe something is unclear? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class Migrator[R, V <: Version: VersionInfo] private[stamina] (migrations: Map[Int, Migration[R]] = Map.empty) { | ||
def canMigrate(fromVersion: Int): Boolean = migrations.contains(fromVersion) | ||
class Migrator[R, V <: Version: VersionInfo] private[stamina] (forwardMigrations: Map[Int, Migration[R]] = Map.empty, backwardMigrations: Map[Int, Migration[R]] = Map.empty) { | ||
def canMigrate(fromVersion: Int, toVersion: Int): Boolean = forwardMigrations.contains(fromVersion) && forwardMigrations.contains(toVersion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? It's not obvious to me that we now also need forwardMigrations.contains(toVersion)
, and shouldn't this also take into account that we can support backwards migrations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a simple code from tests:
val migrator = from[String, V1]
.to[V2](_ + "V2")
.backTo[V1](_.replace("V2", ""))
.to[V3](_ + "V3")
.backTo[V2](_.replace("V3", ""))
.to[V4](_ + "V4")
.backTo[V3](_.replace("V4", ""))
.to[V5](_ + "V5")
The assumption is to define continuous forward migrations, so you cannot define migrations only for version 1 and version 3. You have to define migration also for version 2. So in case when we are going to migrate from version 2 to version 5, we need migrations from 2 to 3, from 3 to 4 and from 4 to 5. Migration from 4 to 5 is under 5
key, it is toVersion
, so it is needed as well. canMigrate
function checks in this case existence of 2 -> 3
and 4 -> 5
migrations, because forward migrations are defined continuously. Backward migrations are optional, so they doesn't affect canMigrate
function at all. If backward migration is not defined for particular version, identity
function is used.
@raboof ping |
@psliwa we are currently waiting on your replies to our comments. |
|
||
import stamina._ | ||
|
||
class MigratorSpec extends StaminaSpec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test suits to scalacheck, do you think I can add scalacheck as dependency and rewrite this test?
def &&(secondMigration: Migration[T]): Migration[T] = { | ||
(value: T) ⇒ secondMigration(firstMigration(value)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is not used anymore.
* Creates a Migrator[T, V1] that can function as a builder for | ||
* creating Migrator[T, V2], etc. Its migration will be the identity | ||
* function so calling its migrate function will not have any effect. | ||
*/ | ||
def from[T, V <: V1: VersionInfo]: Migrator[T, V] = new Migrator[T, V](Map(Version.numberFor[V] → identityMigration[T])) | ||
def from[T, V <: V1: VersionInfo]: Migrator[T, V] = new Migrator[T, V](Map(Version.numberFor[V] → identityMigration[T]), Map.empty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because backward migrations are empty when we start defining migration - so empty map is natural initial value.
class Migrator[R, V <: Version: VersionInfo] private[stamina] (migrations: Map[Int, Migration[R]] = Map.empty) { | ||
def canMigrate(fromVersion: Int): Boolean = migrations.contains(fromVersion) | ||
class Migrator[R, V <: Version: VersionInfo] private[stamina] (forwardMigrations: Map[Int, Migration[R]] = Map.empty, backwardMigrations: Map[Int, Migration[R]] = Map.empty) { | ||
def canMigrate(fromVersion: Int, toVersion: Int): Boolean = forwardMigrations.contains(fromVersion) && forwardMigrations.contains(toVersion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a simple code from tests:
val migrator = from[String, V1]
.to[V2](_ + "V2")
.backTo[V1](_.replace("V2", ""))
.to[V3](_ + "V3")
.backTo[V2](_.replace("V3", ""))
.to[V4](_ + "V4")
.backTo[V3](_.replace("V4", ""))
.to[V5](_ + "V5")
The assumption is to define continuous forward migrations, so you cannot define migrations only for version 1 and version 3. You have to define migration also for version 2. So in case when we are going to migrate from version 2 to version 5, we need migrations from 2 to 3, from 3 to 4 and from 4 to 5. Migration from 4 to 5 is under 5
key, it is toVersion
, so it is needed as well. canMigrate
function checks in this case existence of 2 -> 3
and 4 -> 5
migrations, because forward migrations are defined continuously. Backward migrations are optional, so they doesn't affect canMigrate
function at all. If backward migration is not defined for particular version, identity
function is used.
@agemooij Sorry, I am not sure how github conversations work. I added comments long ago, but they were marked as |
Looks like you started a review but did not finish it. This is a common mistake made by quite a few people I know since Github introduced this feature. @raboof could you also have a look at the review comments? |
This PR is for #57.
DSL hasn't changed, api is simple. If you want to define backward migration, you can use
backTo
function onMigrator
. I've assumed not for every forward migration has to exist backward migration (sobackTo
invocation is optional), because when forward migration eg. adds new field, backward migration is not needed. The second reason for optionalbackTo
migration is just not using backward migrations in the project.Usage from tests: