-
Notifications
You must be signed in to change notification settings - Fork 84
Concepts
The ragtime.protocols/DataStore
protocol describes a migratable store of data. Ragtime needs an implementation of this protocol to tell it how to record which migrations have been applied.
As an example, we'll create an in-memory data store:
(require '[ragtime.protocols :as protocols])
(defrecord MemoryDatabase [data migrations]
protocols/DataStore
(add-migration-id [_ id]
(swap! migrations conj id))
(remove-migration-id [_ id]
(swap! migrations (partial filterv (complement #{id}))))
(applied-migration-ids [_]
(seq @migrations)))
(defn memory-database []
(->MemoryDatabase (atom {}) (atom [])))
We can define an new instance of this database, and see that it is empty, and has no migrations:
user=> (def db (memory-database))
#'user/db
user=> (-> db :data deref)
{}
user=> (protocols/applied-migrations db)
nil
The ragtime.protocols/Migration
protocol describes a single migration for a particular store. A migration has an identifier, and a run-up!
and run-down!
function, which apply and roll back the migration respectively.
Here's an example of one that modifies the MemoryDatabase
defined earlier.
(def add-foo
(reify protocols/Migration
(id [_] "add-foo")
(run-up! [_ db] (swap! (:data db) assoc :foo 1))
(run-down! [_ db] (swap! (:data db) dissoc :foo))})
We can apply a migration to a database using the migrate
function:
user=> (ragtime/migrate db add-foo)
["add-foo"]
user=> (ragtime/applied-migration-ids db)
("add-foo")
user=> (-> db :data deref)
{:foo 1}
And remove a migration using rollback
:
user=> (ragtime/rollback db add-foo)
[]
user=> (ragtime/applied-migration-ids db)
nil
user=> (-> db :data deref)
{}
Often we'll want to work with migrations that have already been applied to the database. For example, we may wish to roll back the latest migration. The Migration
protocol provides a way of finding the IDs of the migrations applied to the database, but we need some way of associating these IDs with the migrations themselves. In other words, we need a migration index.
An index simply maps IDs to migrations. We can create an index for a collection of migrations by using the into-index
function:
user=> (def idx (ragtime/into-index [add-foo]))
#'user/idx
user=> idx
{"add-foo" {:id "add-foo", :up ..., :down ...}}
Once we have an index, we can use the migrate-all
function to update the database with an ordered collection of migrations:
user=> (ragtime/migrate-all db idx [add-foo])
nil
user=> (ragtime/applied-migration-ids db)
("add-foo")
Or to roll back the last migrations applied to the database:
user=> (ragtime/rollback-last db idx)
nil
user=> (ragtime/applied-migration-ids db)
nil
Occasionally the list of migrations applied to the database will differ to the project's migrations, particularly during development. This results in a conflict, and how Ragtime reacts to this depends on the strategy being used.
For example, consider a database with the following migrations applied:
A B D
But the project defines migrations:
A B C D
Ragtime's default strategy is ragtime.strategy/raise-error
. This will raise an error if the migrations held in the database conflict with those in the project. This strategy is most useful for production, where ideally there should be no conflicts.
However, Ragtime also provides two more strategies. ragtime.strategy/rebase
will roll back all conflicting migrations, and then re-apply them in the correct order:
Rolling back D
Applying C
Applying D
This will result in the project and database migrations matching:
A B C D
Alternatively, you can use ragtime.strategy/apply-new
. This will always apply new migrations, irregardless of whether they conflict:
Applying C
This will result in all migrations applied, but in a different order:
A B D C
This is useful if the migrations in a project can be applied independently.