defmodule DynamoMigration do
@moduledoc """
Version management module for migration file of DynamoDB.
Dependes on ExAws and ExAws.Dynamo.
See also https://github.com/ex-aws/ex_aws_dynamo.
Usage:
1. $ mix dynamo.setup # Creates migrations table.
2. $ mix dynamo.gen.migration create_tests_table # Generates migration file.
```elixir
defmodule Dynamo.Migrations.CreateTestsTable do
def change do
# Rewrite any migration code.
ExAws.Dynamo.create_table(
"Tests",
[id: :hash],
%{id: :number},
1,
1,
:provisioned
)
|> ExAws.request!()
end
end
```
3. $ mix dynamo.migrate # Migrates `priv/dynamo/migrations/*`.
"""
require Logger
alias ExAws.Dynamo
@table "Migrations"
@migration_file_path "priv/dynamo/migrations"
@doc """
Called from `mix dynamo.migrate`
Executes migration files if there had not migrated.
"""
@spec migrate :: :ok
def migrate do
migration_files!()
|> compile_migrations()
|> REnum.each(fn {mod, version} ->
if migration_required?(version) do
mod.change()
insert_migration_version(version)
Logger.debug("Migrate #{mod} was succeed")
end
end)
end
@doc """
Returns true if migration version does not exists in migrations table.
"""
@spec migration_required?(integer()) :: boolean
def migration_required?(version) do
result =
Dynamo.query(
@table,
limit: 1,
expression_attribute_values: [version: version],
key_condition_expression: "version = :version"
)
|> ExAws.request!()
RMap.get(result, "Count") == 0
end
@doc """
Creates migrations table for version management.
Called from `mix dynamo.setup`
"""
@spec setup :: :ok
def setup do
tables = Dynamo.list_tables() |> ExAws.request!() |> RMap.get("TableNames")
if @table in tables do
Logger.debug("Migrations table was already created.")
else
Dynamo.create_table(
@table,
[version: :hash],
[version: :number],
1,
1,
:provisioned
)
|> ExAws.request!()
Logger.debug("Migrations table was created.")
end
:ok
end
@spec migration_file_path :: String.t()
def migration_file_path do
@migration_file_path
end
defp insert_migration_version(version) do
Dynamo.put_item(
@table,
%{version: version}
)
|> ExAws.request!()
end
defp migration_files!() do
case File.ls(migration_file_path()) do
{:ok, files} -> files
{:error, _} -> raise "test"
end
end
defp compile_migrations(files) do
files
|> Enum.map(fn file ->
mod =
(migration_file_path() <> "/" <> file)
|> Code.compile_file()
|> REnum.map(&elem(&1, 0))
|> REnum.first()
version =
Regex.scan(~r/[0-9]/, file)
|> REnum.join()
|> String.to_integer()
{mod, version}
end)
end
end