diff --git a/README.md b/README.md index b69e478..fa9e602 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,48 @@ defmodule Weather do # is for later at now end ``` +Posibility to rename a table during development +------------------------------------------------- + +`ecto_migrate` supports ability to rename a database table during development process. For this case add the +`old_tablename/0` function that will just return old table name. + +For example your old model looks like this: + +```elixir +defmodule Weather do # is for later at now + use Ecto.Model + use Ecto.Migration.Auto.Index + + schema "weather" do + field :city + field :temp_lo, :integer + field :temp_hi, :integer + field :prcp, :float, default: 0.0 + end +end +``` + +Now you can add `old_tablename/0` function and after recompilation `ecto_migrate` will update table name in the database: + +```elixir +defmodule Weather do # is for later at now + use Ecto.Model + use Ecto.Migration.Auto.Index + + schema "weather_updated" do + field :city + field :temp_lo, :integer + field :temp_hi, :integer + field :prcp, :float, default: 0.0 + end + + def old_tablename do + "weather" + end +end +``` + Upgrades in 0.3.x versions -------------------------- diff --git a/lib/ecto/migration/auto.ex b/lib/ecto/migration/auto.ex index 7f5d602..e2392ce 100644 --- a/lib/ecto/migration/auto.ex +++ b/lib/ecto/migration/auto.ex @@ -61,14 +61,13 @@ defmodule Ecto.Migration.Auto do {related_field, tablename} = get_tablename(module, tablename, opts) tableatom = tablename |> String.to_atom for_opts = {related_field, opts[:for]} - {fields_changes, relateds} = repo.get(SystemTable, tablename) |> Field.check(tableatom, module, for_opts) index_changes = (from s in SystemTable.Index, where: ^tablename == s.tablename) |> repo.all |> Index.check(tableatom, module) - if migration_module = check_gen(tableatom, module, fields_changes, index_changes, opts) do - Ecto.Migrator.up(repo, random, migration_module) - Field.update_meta(repo, module, tablename, relateds) # May be in transaction? - Index.update_meta(repo, module, tablename, index_changes) + Ecto.Migrator.up(repo, random, migration_module) + {_, rename?, _} = fields_changes + Field.update_meta(repo, module, tablename, relateds, rename?) # May be in transaction? + Index.update_meta(repo, module, tablename, index_changes, rename?) end end end @@ -77,7 +76,11 @@ defmodule Ecto.Migration.Auto do tablename = module.__schema__(:source) case function_exported?(module, :__sources__, 0) do true -> module.__sources__() - false -> [tablename] + false -> + case function_exported?(module, :old_tablename, 0) do + true -> [module.old_tablename] + false -> [tablename] + end end end @@ -98,8 +101,8 @@ defmodule Ecto.Migration.Auto do defp related_mod?(%Ecto.Association.Has{related: mod, queryable: {_, _}}, mod), do: true defp related_mod?(_, _), do: false - defp check_gen(_tablename, _module, {false, []}, {[], []}, _opts), do: nil - defp check_gen(tablename, module, {create?, changed_fields}, {create_indexes, delete_indexes}, opts) do + defp check_gen(_tablename, _module, {false, false, []}, {[], []}, _opts), do: nil + defp check_gen(tablename, module, {create?, _rename?, changed_fields}, {create_indexes, delete_indexes}, opts) do migration_module = migration_module(module, opts) up = gen_up(module, tablename, create?, changed_fields, create_indexes, delete_indexes) quote do @@ -125,20 +128,27 @@ defmodule Ecto.Migration.Auto do end end - defp gen_up_table(module, tablename, true, changed_fields) do - key? = module.__schema__(:primary_key) == [:id] + defp gen_up_table(module, tablename, changed_fields) do + rename = rename_table(module, module.__schema__(:source), tablename) quote do - create table(unquote(tablename), primary_key: unquote(key?)) do + alter table(unquote(tablename)) do unquote(changed_fields) end + unquote(rename) end end - defp gen_up_table(_module, tablename, false, changed_fields) do - quote do - alter table(unquote(tablename)) do - unquote(changed_fields) - end + defp gen_up_table(module, tablename, create?, changed_fields) do + case create? do + false -> + gen_up_table(module, tablename, changed_fields) + true -> + key? = module.__schema__(:primary_key) == [:id] + quote do + create table(unquote(tablename), primary_key: unquote(key?)) do + unquote(changed_fields) + end + end end end @@ -149,6 +159,17 @@ defmodule Ecto.Migration.Auto do end end + defp rename_table(module, new_tablename, old_tablename) do + case function_exported?(module, :old_tablename, 0) do + true -> + quote do + rename table(unquote(old_tablename)), to: table(unquote(new_tablename |> String.to_atom)) + end + false -> + "" + end + end + @migration_tables [{SystemTable.Index, SystemTable.Index.Migration}, {SystemTable, SystemTable.Migration}] defp ensure_exists(repo) do for {model, migration} <- @migration_tables do diff --git a/lib/ecto/migration/auto/field.ex b/lib/ecto/migration/auto/field.ex index 34a4948..d3edb3e 100644 --- a/lib/ecto/migration/auto/field.ex +++ b/lib/ecto/migration/auto/field.ex @@ -1,10 +1,11 @@ defmodule Ecto.Migration.Auto.Field do + import Ecto.Query alias Ecto.Migration.SystemTable @doc """ Update meta information in repository """ - def update_meta(repo, module, tablename, relateds) do + def update_meta(repo, module, tablename, relateds, table_renamed?) do metainfo = module.__schema__(:fields) |> Stream.map(&field_to_meta(&1, module, relateds)) |> Enum.join(",") updated_info = %SystemTable{tablename: tablename, metainfo: metainfo} if repo.get(SystemTable, tablename) do @@ -12,6 +13,10 @@ defmodule Ecto.Migration.Auto.Field do else repo.insert!(updated_info) end + case table_renamed? do + true -> repo.update_all(from(s in SystemTable, where: s.tablename == ^tablename), set: [tablename: module.__schema__(:source)]) + _ -> :ok + end end defp field_to_meta(field, module, relateds) do @@ -30,11 +35,9 @@ defmodule Ecto.Migration.Auto.Field do relateds = associations(module) metainfo = old_keys(old_fields) new_fields = module.__schema__(:fields) - add_fields = add(module, new_fields, metainfo, relateds, for_opts) remove_fields = remove(new_fields, metainfo) - - {{old_fields == nil, remove_fields ++ add_fields}, relateds} + {{old_fields == nil, function_exported?(module, :old_tablename, 0), remove_fields ++ add_fields}, relateds} end defp old_keys(nil), do: [] diff --git a/lib/ecto/migration/auto/index.ex b/lib/ecto/migration/auto/index.ex index 238dfd3..5766944 100644 --- a/lib/ecto/migration/auto/index.ex +++ b/lib/ecto/migration/auto/index.ex @@ -1,5 +1,7 @@ defmodule Ecto.Migration.Auto.Index do import Ecto.Query + + alias Ecto.Migration.SystemTable alias Ecto.Migration.SystemTable.Index defmacro __using__(_opts) do @@ -27,12 +29,19 @@ defmodule Ecto.Migration.Auto.Index do @doc """ Update meta information in repository """ - def update_meta(_repo, _module, _tablename, {[], []}), do: nil - def update_meta(repo, module, tablename, _) do + def update_meta(_repo, _module, _tablename, {[], []}, false), do: nil + def update_meta(repo, module, tablename, _, true) do + from(s in SystemTable.Index, where: s.tablename == ^tablename) |> repo.update_all(set: [tablename: module.__schema__(:source)]) + end + def update_meta(repo, module, tablename, _, table_renamed?) do (from s in Index, where: s.tablename == ^tablename) |> repo.delete_all for {columns, opts} <- all(module) do repo.insert!(Map.merge(%Index{tablename: tablename, index: Enum.join(columns, ",")}, :maps.from_list(opts))) end + case table_renamed? do + true -> from(s in SystemTable.Index, where: s.tablename == ^tablename) |> repo.update_all(set: [tablename: module.__schema__(:source)]) + _ -> :ok + end end @doc """ @@ -41,10 +50,8 @@ defmodule Ecto.Migration.Auto.Index do def check(old_indexes, tablename, module) do new_indexes = all(module) |> Enum.map(&merge_default/1) old_indexes = old_indexes |> Enum.map(&transform_from_db/1) - create_indexes = (new_indexes -- old_indexes) |> create(tablename) delete_indexes = (old_indexes -- new_indexes) |> delete(tablename) - {create_indexes, delete_indexes} end diff --git a/lib/ecto/migration/system_table.ex b/lib/ecto/migration/system_table.ex index 644307b..0338a6d 100644 --- a/lib/ecto/migration/system_table.ex +++ b/lib/ecto/migration/system_table.ex @@ -5,7 +5,7 @@ defmodule Ecto.Migration.SystemTable.Index.Migration do add :tablename, :string add :index, :string add :name, :string - add :concurrently + add :concurrently, :boolean add :unique, :boolean add :using, :string end diff --git a/test/ecto_migrate_test.exs b/test/ecto_migrate_test.exs index 49f6c59..df570f5 100644 --- a/test/ecto_migrate_test.exs +++ b/test/ecto_migrate_test.exs @@ -1,5 +1,5 @@ defmodule TestModel do - use Ecto.Schema + use Ecto.Model use Ecto.Migration.Auto.Index index(:l, using: "hash") @@ -72,5 +72,16 @@ defmodule EctoMigrateTest do assert tags.id == 1 assert tags.model == "Elixir.MyModel" assert tags.name == "test_tag" + + [query] = (from t in Ecto.Migration.SystemTable, where: t.tablename == "ecto_migrate_test_table") |> EctoIt.Repo.all + assert query.tablename == "ecto_migrate_test_table" + + Code.require_file(File.cwd! <> "/test/test_model.exs") + Ecto.Migration.Auto.migrate(EctoIt.Repo, TestModel) + + [query] = (from t in Ecto.Migration.SystemTable, where: t.tablename == "test_table_2") |> EctoIt.Repo.all + assert query.tablename == "test_table_2" + assert query.metainfo == "id:id,f:string,l:boolean" + assert (from t in Ecto.Migration.SystemTable, where: t.tablename == "ecto_migrate_test_table") |> EctoIt.Repo.all == [] end end diff --git a/test/test_model.exs b/test/test_model.exs new file mode 100644 index 0000000..31375fb --- /dev/null +++ b/test/test_model.exs @@ -0,0 +1,19 @@ +# We use this for testing of the renaming table. The ecto_migrate_test.exs +# contains the same module but with the other table name and set of the +# schema fields. +defmodule TestModel do + use Ecto.Model + use Ecto.Migration.Auto.Index + + index(:l, using: "hash") + index(:f) + + schema "test_table_2" do + field :f, :string + field :l, :boolean + end + + def old_tablename do + "ecto_migrate_test_table" + end +end