Skip to content
Samuel Tschiedel edited this page Feb 15, 2017 · 17 revisions

Data Stores

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

Migrations

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)
{}

Indexes

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

Strategies

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.

Clone this wiki locally