Skip to content

Commit 6412c69

Browse files
committed
chore: add full control of the migration process
1 parent eade19b commit 6412c69

File tree

3 files changed

+146
-43
lines changed

3 files changed

+146
-43
lines changed

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,64 @@ The following options are available:
634634
* `:otp_app` - the name of the otp_app to resolve the `priv` folder, defaults to `:mongodb_driver`. In most cases you use your application name.
635635
* `:topology` - the topology for running the migrations, `:topology` defaults to `:mongo`
636636

637+
### Supporting multiple topologies:
638+
639+
Each function `lock/1, unlock/1, migrate/1, drop/1` accepts a keyword list (options) to override the default config having
640+
full control of the migration process. The options are passed through the migration scripts.
641+
642+
That means you can support multiple topologies, databases and migration collections. Example
643+
644+
Mongo.start_link(name: :topology_1, url: "mongodb://localhost:27017/mig_test_1", timeout: 60_000, pool_size: 5, idle_interval: 10_000)
645+
Mongo.start_link(name: :topology_2, url: "mongodb://localhost:27017/mig_test_2", timeout: 60_000, pool_size: 5, idle_interval: 10_000)
646+
647+
IO.puts("running default migration")
648+
Mongo.Migration.migrate() ## default values specified in the configs
649+
650+
IO.puts("running topology_2 migration")
651+
Mongo.Migration.migrate([topology: :topology_2]) ## override the topology
652+
653+
Adding the options parameter in the `up/1` and `down/1` function of the migration script is supported as well. It is
654+
possible to pass additional parameters to the migration scripts.
655+
656+
defmodule Mongo.Migrations.Topology.CreateIndex do
657+
def up(opts) do
658+
IO.inspect(opts)
659+
...
660+
end
661+
662+
def down(opts) do
663+
IO.inspect(opts)
664+
...
665+
end
666+
end
667+
668+
The topology is part of the namespace and of the migration path as well. The default value is defined in the configuration.
669+
You can specify the topology in the case of creating a new migration script by appending the name to the script call:
670+
```elixir
671+
672+
mix mongo.gen.migration add_indexes topology_2
673+
674+
```
675+
676+
In `priv/topology_2/mongo/migrations` you will find an Elixir script like `20220322173354_add_indexes.exs`:
677+
678+
```elixr
679+
defmodule Mongo.Migrations.Topology2.AddIndexes do
680+
...
681+
end
682+
683+
```
684+
685+
By using the `:topology` keyword, you can organise the migration scripts in different sub-folders. The migration path is prefixed with the `priv` folder of the application and the topology name.
686+
687+
If you call
688+
689+
Mongo.Migration.migrate([topology: :topology_2])
690+
691+
then the migration scripts under `/priv/topology_2/` are used and the options keyword list is passed through
692+
to the `up/1` function if it is implemented. That means you can create migration scripts for multiple topologies
693+
separated in sub folders and module namespaces.
694+
637695
## Auth Mechanisms
638696

639697
For versions of Mongo 3.0 and greater, the auth mechanism defaults to SCRAM.

lib/mongo/migration.ex

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,39 @@ defmodule Mongo.Migration do
22
@moduledoc false
33
use Mongo.Collection
44

5-
def migrate() do
6-
with :locked <- lock() do
7-
migration_files!()
5+
def migrate(opts \\ []) do
6+
with :locked <- lock(opts) do
7+
migration_files!(opts)
88
|> compile_migrations()
9-
|> Enum.each(fn {mod, version} -> run_up(version, mod) end)
9+
|> Enum.each(fn {mod, version} -> run_up(version, mod, opts) end)
1010

11-
unlock()
11+
unlock(opts)
1212
end
1313
rescue
1414
error ->
1515
IO.puts("🚨 Error when migrating: #{inspect(error)}")
16-
unlock()
16+
unlock(opts)
1717
end
1818

19-
def drop() do
20-
with :locked <- lock() do
21-
migration_files!()
19+
def drop(opts \\ []) do
20+
with :locked <- lock(opts) do
21+
migration_files!(opts)
2222
|> compile_migrations()
2323
|> Enum.reverse()
24-
|> Enum.each(fn {mod, version} -> run_down(version, mod) end)
24+
|> Enum.each(fn {mod, version} -> run_down(version, mod, opts) end)
2525

26-
unlock()
26+
unlock(opts)
2727
end
2828
rescue
2929
error ->
3030
IO.puts("🚨 Error when dropping: #{inspect(error)}")
31-
unlock()
31+
unlock(opts)
3232
end
3333

34-
def lock() do
35-
topology = get_config()[:topology]
36-
collection = get_config()[:collection]
34+
def lock(opts \\ []) do
35+
topology = get_config(opts)[:topology]
36+
collection = get_config(opts)[:collection]
37+
3738
query = %{_id: "lock", used: false}
3839
set = %{"$set": %{used: true}}
3940

@@ -51,9 +52,9 @@ defmodule Mongo.Migration do
5152
end
5253
end
5354

54-
def unlock() do
55-
topology = get_config()[:topology]
56-
collection = get_config()[:collection]
55+
def unlock(opts \\ []) do
56+
topology = get_config(opts)[:topology]
57+
collection = get_config(opts)[:collection]
5758
query = %{_id: "lock", used: true}
5859
set = %{"$set": %{used: false}}
5960

@@ -67,13 +68,25 @@ defmodule Mongo.Migration do
6768
end
6869
end
6970

70-
defp run_up(version, mod) do
71-
topology = get_config()[:topology]
72-
collection = get_config()[:collection]
71+
defp run_up(version, mod, opts) do
72+
topology = get_config(opts)[:topology]
73+
collection = get_config(opts)[:collection]
7374

7475
case Mongo.find_one(topology, collection, %{version: version}) do
7576
nil ->
76-
mod.up()
77+
## check, if the function supports options
78+
79+
cond do
80+
function_exported?(mod, :up, 1) ->
81+
apply(mod, :up, [opts])
82+
83+
function_exported?(mod, :up, 0) ->
84+
apply(mod, :up, [])
85+
86+
true ->
87+
raise "The module does not export the up function!"
88+
end
89+
7790
Mongo.insert_one(topology, collection, %{version: version})
7891
IO.puts("⚡️ Successfully migrated #{mod}")
7992

@@ -87,13 +100,24 @@ defmodule Mongo.Migration do
87100
reraise e, __STACKTRACE__
88101
end
89102

90-
defp run_down(version, mod) do
91-
topology = get_config()[:topology]
92-
collection = get_config()[:collection]
103+
defp run_down(version, mod, opts) do
104+
topology = get_config(opts)[:topology]
105+
collection = get_config(opts)[:collection]
93106

94107
case Mongo.find_one(topology, collection, %{version: version}) do
95108
%{"version" => _version} ->
96-
mod.down()
109+
## check, if the function supports options
110+
cond do
111+
function_exported?(mod, :down, 1) ->
112+
apply(mod, :down, [opts])
113+
114+
function_exported?(mod, :down, 0) ->
115+
apply(mod, :down, [])
116+
117+
true ->
118+
raise "The module does not export the down function!"
119+
end
120+
97121
Mongo.delete_one(topology, collection, %{version: version})
98122
IO.puts("💥 Successfully dropped #{mod}")
99123

@@ -107,30 +131,39 @@ defmodule Mongo.Migration do
107131
reraise e, __STACKTRACE__
108132
end
109133

110-
def get_config() do
134+
def get_config(opts \\ []) do
111135
defaults = [topology: :mongo, collection: "migrations", path: "mongo/migrations", otp_app: :mongodb_driver]
112-
Keyword.merge(defaults, Application.get_env(:mongodb_driver, :migration, []))
136+
137+
defaults
138+
|> Keyword.merge(Application.get_env(:mongodb_driver, :migration, []))
139+
|> Keyword.merge(opts)
113140
end
114141

115-
def migration_file_path() do
116-
path = get_config()[:path]
117-
otp_app = get_config()[:otp_app]
118-
Path.join([:code.priv_dir(otp_app), path])
142+
def migration_file_path(opts \\ []) do
143+
path = get_config(opts)[:path]
144+
topology = get_config(opts)[:topology]
145+
otp_app = get_config(opts)[:otp_app]
146+
Path.join([:code.priv_dir(otp_app), to_string(topology), path])
119147
end
120148

121-
defp migration_files!() do
122-
file_path = migration_file_path()
149+
def migration_files!(opts) do
150+
file_path = migration_file_path(opts)
151+
152+
case File.ls(file_path) do
153+
{:ok, files} ->
154+
files
155+
|> Enum.sort()
156+
|> Enum.map(fn file_name -> file_path <> "/" <> file_name end)
123157

124-
case File.ls(migration_file_path()) do
125-
{:ok, files} -> Enum.sort(files)
126-
{:error, _error} -> raise "Could not find migrations file path #{inspect(file_path)}"
158+
{:error, _error} ->
159+
raise "Could not find migrations file path #{inspect(file_path)}"
127160
end
128161
end
129162

130163
defp compile_migrations(files) do
131164
Enum.map(files, fn file ->
132165
mod =
133-
(migration_file_path() <> "/" <> file)
166+
file
134167
|> Code.compile_file()
135168
|> Enum.map(&elem(&1, 0))
136169
|> List.first()

lib/tasks/gen/migration.ex

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,20 @@ defmodule Mix.Tasks.Mongo.Gen.Migration do
1212

1313
@spec run([String.t()]) :: integer()
1414
def run(args) do
15-
migrations_path = migration_file_path()
16-
name = List.first(args)
15+
{name, topology} =
16+
case args do
17+
[name | [topology | _xs]] ->
18+
{name, topology}
19+
20+
[name | _xs] ->
21+
{name, Migration.get_config()[:topology]}
22+
23+
_other ->
24+
Mix.raise("Filename is missing")
25+
end
26+
27+
migrations_path = migration_file_path(topology)
28+
1729
base_name = "#{underscore(name)}.exs"
1830
current_timestamp = timestamp()
1931
file = Path.join(migrations_path, "#{current_timestamp}_#{base_name}")
@@ -24,16 +36,16 @@ defmodule Mix.Tasks.Mongo.Gen.Migration do
2436
Mix.raise("Migration can't be created, there is already a migration file with name #{name}.")
2537
end
2638

27-
assigns = [mod: Module.concat([Mongo, Migrations, camelize(name)])]
39+
assigns = [mod: Module.concat([Mongo, Migrations, camelize(to_string(topology)), camelize(name)])]
2840
create_file(file, migration_template(assigns))
2941
String.to_integer(current_timestamp)
3042
end
3143

3244
@doc """
3345
Returns the private repository path relative to the source.
3446
"""
35-
def migration_file_path() do
36-
path = "priv/" <> Migration.get_config()[:path]
47+
def migration_file_path(topology) do
48+
path = "priv/#{topology}/#{Migration.get_config()[:path]}"
3749
otp_app = Migration.get_config()[:otp_app]
3850
Path.join(Mix.Project.deps_paths()[otp_app] || File.cwd!(), path)
3951
end

0 commit comments

Comments
 (0)