diff --git a/glific_phil_columns-3.1.0.tar b/glific_phil_columns-3.1.0.tar new file mode 100644 index 0000000..4cc6811 Binary files /dev/null and b/glific_phil_columns-3.1.0.tar differ diff --git a/lib/mix/phil_columns.ex b/lib/mix/phil_columns.ex index a422890..8209bfe 100644 --- a/lib/mix/phil_columns.ex +++ b/lib/mix/phil_columns.ex @@ -1,15 +1,14 @@ defmodule Mix.PhilColumns do - import Mix.EctoSQL @doc """ Ensures the given repository's seeds path exists on the file system. """ - @spec ensure_seeds_path(Ecto.Repo.t, Keyword.t) :: String.t + @spec ensure_seeds_path(Ecto.Repo.t(), Keyword.t()) :: String.t() def ensure_seeds_path(repo, opts) do path = opts[:seeds_path] || Path.join(source_repo_priv(repo), "seeds") - if not Mix.Project.umbrella? and not File.dir?(path) do + if not Mix.Project.umbrella?() and not File.dir?(path) do raise_missing_seeds(Path.relative_to_cwd(path), repo) end @@ -17,9 +16,9 @@ defmodule Mix.PhilColumns do end defp raise_missing_seeds(path, repo) do - Mix.raise """ - Could not find seeds directory #{inspect path} - for repo #{inspect repo}. + Mix.raise(""" + Could not find seeds directory #{inspect(path)} + for repo #{inspect(repo)}. This may be because you are in a new project and the migration directory has not been created yet. Creating an @@ -28,7 +27,7 @@ defmodule Mix.PhilColumns do If you expected existing seeds to be found, please make sure your repository has been properly configured and the configured path exists. - """ + """) end @doc """ @@ -36,49 +35,53 @@ defmodule Mix.PhilColumns do """ @spec restart_apps_if_migrated([atom], list()) :: :ok def restart_apps_if_migrated(_apps, []), do: :ok - def restart_apps_if_migrated(apps, [_|_]) do + + def restart_apps_if_migrated(apps, [_ | _]) do # Silence the logger to avoid application down messages. Logger.remove_backend(:console) + for app <- Enum.reverse(apps) do Application.stop(app) end + for app <- apps do Application.ensure_all_started(app) end + :ok after Logger.add_backend(:console, flush: true) end def root_mod(repo_mod) do - name = repo_mod - |> root_mod_name + name = + repo_mod + |> root_mod_name Module.concat([name]) end def root_mod_name(repo_mod) do repo_mod - |> Module.split - |> List.first + |> Module.split() + |> List.first() end - #@doc """ - #Gets a path relative to the application path. - #Raises on umbrella application. - #""" - #def no_umbrella!(task) do - #if Mix.Project.umbrella? do - #Mix.raise "cannot run task #{inspect task} from umbrella application" - #end - #end + # @doc """ + # Gets a path relative to the application path. + # Raises on umbrella application. + # """ + # def no_umbrella!(task) do + # if Mix.Project.umbrella? do + # Mix.raise "cannot run task #{inspect task} from umbrella application" + # end + # end @doc """ Gets the seeds path from a repository. """ - @spec seeds_path(Ecto.Repo.t) :: String.t + @spec seeds_path(Ecto.Repo.t()) :: String.t() def seeds_path(repo) do Path.join(source_repo_priv(repo), "seeds") end - end diff --git a/lib/mix/tasks/phil_columns.gen.seed.ex b/lib/mix/tasks/phil_columns.gen.seed.ex index 2330ff7..1a299c9 100644 --- a/lib/mix/tasks/phil_columns.gen.seed.ex +++ b/lib/mix/tasks/phil_columns.gen.seed.ex @@ -1,5 +1,4 @@ defmodule Mix.Tasks.PhilColumns.Gen.Seed do - use Mix.Task import Mix.Ecto @@ -12,20 +11,29 @@ defmodule Mix.Tasks.PhilColumns.Gen.Seed do no_umbrella!("phil_columns.gen.seed") repos = parse_repo(args) - Enum.each repos, fn repo -> + Enum.each(repos, fn repo -> case OptionParser.parse(args) do {_, [name], _} -> ensure_repo(repo, args) - path = Path.relative_to(seeds_path(repo), Mix.Project.app_path) + path = Path.relative_to(seeds_path(repo), Mix.Project.app_path()) file = Path.join(path, "#{timestamp()}_#{name}.exs") - create_directory path - create_file file, seed_template(root_mod: root_mod(repo), - mod: Module.concat([repo, Seeds, Inflex.camelize(name)])) + create_directory(path) + + create_file( + file, + seed_template( + root_mod: root_mod(repo), + mod: Module.concat([repo, Seeds, Inflex.camelize(name)]) + ) + ) + {_, _, _} -> - Mix.raise "expected phil_columns.gen.seed to receive the seed file name, " <> - "got: #{inspect Enum.join(args, " ")}" + Mix.raise( + "expected phil_columns.gen.seed to receive the seed file name, " <> + "got: #{inspect(Enum.join(args, " "))}" + ) end - end + end) end defp timestamp do @@ -33,10 +41,10 @@ defmodule Mix.Tasks.PhilColumns.Gen.Seed do "#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}" end - defp pad(i) when i < 10, do: << ?0, ?0 + i >> + defp pad(i) when i < 10, do: <> defp pad(i), do: to_string(i) - embed_template :seed, """ + embed_template(:seed, """ defmodule <%= inspect @mod %> do use <%= inspect @root_mod %>.Seed @@ -45,6 +53,5 @@ defmodule Mix.Tasks.PhilColumns.Gen.Seed do def up(_repo) do end end - """ - + """) end diff --git a/lib/mix/tasks/phil_columns.rollback.ex b/lib/mix/tasks/phil_columns.rollback.ex index a82f85c..e4e12b2 100644 --- a/lib/mix/tasks/phil_columns.rollback.ex +++ b/lib/mix/tasks/phil_columns.rollback.ex @@ -1,5 +1,4 @@ defmodule Mix.Tasks.PhilColumns.Rollback do - use Mix.Task import Mix.Ecto @@ -8,13 +7,23 @@ defmodule Mix.Tasks.PhilColumns.Rollback do @shortdoc "Executes the seeds for specified env and tags down" def run(args, seeder \\ &PhilColumns.Seeder.run/4) do - repos = parse_repo(args) - |> List.wrap() + repos = + parse_repo(args) + |> List.wrap() - {opts, _, _} = OptionParser.parse args, - switches: [all: :boolean, step: :integer, to: :integer, quiet: :boolean, - pool_size: :integer, env: :string], - aliases: [e: :env, n: :steps, v: :to] + {opts, _, _} = + OptionParser.parse(args, + switches: [ + all: :boolean, + step: :integer, + to: :integer, + quiet: :boolean, + pool_size: :integer, + env: :string, + tenant: :string + ], + aliases: [e: :env, n: :steps, v: :to] + ) opts = if opts[:to] || opts[:step] || opts[:all], @@ -36,6 +45,11 @@ defmodule Mix.Tasks.PhilColumns.Rollback do do: Keyword.put(opts, :env, String.to_atom(opts[:env])), else: Keyword.put(opts, :env, :dev) + opts = + if opts[:tenant], + do: opts, + else: Keyword.put(opts, :tenant, "main") + # Start ecto_sql explicitly before as we don't need # to restart those apps if migrated. {:ok, _} = Application.ensure_all_started(:ecto_sql) @@ -54,10 +68,12 @@ defmodule Mix.Tasks.PhilColumns.Rollback do end case PhilColumns.Seeder.with_repo(repo, fun, [mode: :temporary] ++ opts) do - {:ok, migrated, apps} -> restart_apps_if_migrated(apps, migrated) - {:error, error} -> Mix.raise "Could not start repo #{inspect repo}, error: #{inspect error}" + {:ok, migrated, apps} -> + restart_apps_if_migrated(apps, migrated) + + {:error, error} -> + Mix.raise("Could not start repo #{inspect(repo)}, error: #{inspect(error)}") end end end - end diff --git a/lib/mix/tasks/phil_columns.seed.ex b/lib/mix/tasks/phil_columns.seed.ex index 2cdb5f5..d616350 100644 --- a/lib/mix/tasks/phil_columns.seed.ex +++ b/lib/mix/tasks/phil_columns.seed.ex @@ -1,5 +1,4 @@ defmodule Mix.Tasks.PhilColumns.Seed do - use Mix.Task import Mix.Ecto @@ -8,13 +7,27 @@ defmodule Mix.Tasks.PhilColumns.Seed do @shortdoc "Executes the seeds for specified env and tags up" def run(args, seeder \\ &PhilColumns.Seeder.run/4) do - repos = parse_repo(args) - |> List.wrap() - - {opts, _, _} = OptionParser.parse args, - switches: [all: :boolean, step: :integer, to: :integer, quiet: :boolean, - pool_size: :integer, env: :string, tags: :string], - aliases: [e: :env, n: :step, t: :tags, v: :to] + # This will start our application + Mix.Task.run("app.start") + + repos = + parse_repo(args) + |> List.wrap() + + {opts, _, _} = + OptionParser.parse(args, + switches: [ + all: :boolean, + step: :integer, + to: :integer, + quiet: :boolean, + pool_size: :integer, + env: :string, + tags: :string, + tenant: :string + ], + aliases: [e: :env, n: :step, t: :tags, v: :to] + ) opts = if opts[:to] || opts[:step] || opts[:all], @@ -38,9 +51,22 @@ defmodule Mix.Tasks.PhilColumns.Seed do opts = if opts[:tags], - do: Keyword.put(opts, :tags, String.split(opts[:tags], ",") |> List.wrap |> Enum.map(fn(tag) -> String.to_atom(tag) end) |> Enum.sort), + do: + Keyword.put( + opts, + :tags, + String.split(opts[:tags], ",") + |> List.wrap() + |> Enum.map(fn tag -> String.to_atom(tag) end) + |> Enum.sort() + ), else: Keyword.put(opts, :tags, []) + opts = + if opts[:tenant], + do: opts, + else: Keyword.put(opts, :tenant, "main") + # Start ecto_sql explicitly before as we don't need # to restart those apps if migrated. {:ok, _} = Application.ensure_all_started(:ecto_sql) @@ -59,10 +85,12 @@ defmodule Mix.Tasks.PhilColumns.Seed do end case PhilColumns.Seeder.with_repo(repo, fun, [mode: :temporary] ++ opts) do - {:ok, migrated, apps} -> restart_apps_if_migrated(apps, migrated) - {:error, error} -> Mix.raise "Could not start repo #{inspect repo}, error: #{inspect error}" + {:ok, migrated, apps} -> + restart_apps_if_migrated(apps, migrated) + + {:error, error} -> + Mix.raise("Could not start repo #{inspect(repo)}, error: #{inspect(error)}") end end end - end diff --git a/lib/mix/tasks/phil_columns.seeds.ex b/lib/mix/tasks/phil_columns.seeds.ex index 5ea05bc..f83ef7d 100644 --- a/lib/mix/tasks/phil_columns.seeds.ex +++ b/lib/mix/tasks/phil_columns.seeds.ex @@ -1,5 +1,4 @@ defmodule Mix.Tasks.PhilColumns.Seeds do - use Mix.Task import Mix.Ecto @@ -27,12 +26,15 @@ defmodule Mix.Tasks.PhilColumns.Seeds do @doc false def run(args, seeds \\ &PhilColumns.Seeder.seeds/3, puts \\ &IO.puts/1) do - repos = parse_repo(args) - |> List.wrap() + repos = + parse_repo(args) + |> List.wrap() - {opts, _, _} = OptionParser.parse args, - switches: [env: :string, tags: :string], - aliases: [e: :env, t: :tags] + {opts, _, _} = + OptionParser.parse(args, + switches: [env: :string, tags: :string, tenant: :string], + aliases: [e: :env, t: :tags] + ) opts = if opts[:env], @@ -41,14 +43,27 @@ defmodule Mix.Tasks.PhilColumns.Seeds do opts = if opts[:tags], - do: Keyword.put(opts, :tags, String.split(opts[:tags], ",") |> List.wrap |> Enum.map(fn(tag) -> String.to_atom(tag) end) |> Enum.sort), + do: + Keyword.put( + opts, + :tags, + String.split(opts[:tags], ",") + |> List.wrap() + |> Enum.map(fn tag -> String.to_atom(tag) end) + |> Enum.sort() + ), else: Keyword.put(opts, :tags, []) + opts = + if opts[:tenant], + do: opts, + else: Keyword.put(opts, :tenant, "main") + for repo <- repos do ensure_repo(repo, args) path = ensure_seeds_path(repo, opts) - case PhilColumns.Seeder.with_repo(repo, &seeds.(&1, path, opts), [mode: :temporary]) do + case PhilColumns.Seeder.with_repo(repo, &seeds.(&1, path, opts), mode: :temporary) do {:ok, repo_status, _} -> puts.( """ @@ -64,7 +79,7 @@ defmodule Mix.Tasks.PhilColumns.Seeds do ) {:error, error} -> - Mix.raise "Could not start repo #{inspect repo}, error: #{inspect error}" + Mix.raise("Could not start repo #{inspect(repo)}, error: #{inspect(error)}") end end end @@ -74,5 +89,4 @@ defmodule Mix.Tasks.PhilColumns.Seeds do |> to_string() |> String.pad_trailing(pad) end - end diff --git a/lib/phil_columns/factory.ex b/lib/phil_columns/factory.ex index 2ee6c21..697dd2d 100644 --- a/lib/phil_columns/factory.ex +++ b/lib/phil_columns/factory.ex @@ -1,12 +1,11 @@ defmodule PhilColumns.Factory do - alias Ecto.Changeset defmacro __using__(opts) do quote do @before_compile PhilColumns.Factory Module.register_attribute(__MODULE__, :repo, accumulate: false) - Module.put_attribute __MODULE__, :repo, unquote(opts[:repo]) + Module.put_attribute(__MODULE__, :repo, unquote(opts[:repo])) import PhilColumns.Factory, only: [sequence: 1, sequence: 2] @@ -14,13 +13,13 @@ defmodule PhilColumns.Factory do PhilColumns.Factory.build(__MODULE__, factory_name, attrs) end - #def build_pair(factory_name, attrs \\ %{}) do - #PhilColumns.Factory.build_pair(__MODULE__, factory_name, attrs) - #end + # def build_pair(factory_name, attrs \\ %{}) do + # PhilColumns.Factory.build_pair(__MODULE__, factory_name, attrs) + # end - #def build_list(number_of_factories, factory_name, attrs \\ %{}) do - #PhilColumns.Factory.build_list(__MODULE__, factory_name, attrs) - #end + # def build_list(number_of_factories, factory_name, attrs \\ %{}) do + # PhilColumns.Factory.build_list(__MODULE__, factory_name, attrs) + # end def insert(factory_name, attrs \\ %{}) do PhilColumns.Factory.insert(__MODULE__, factory_name, attrs) @@ -34,15 +33,12 @@ defmodule PhilColumns.Factory do raise "UndefinedFactoryError: factory(:#{factory_name})" end - def factory(factory_name,_) do + def factory(factory_name, _) do raise "UndefinedFactoryError: factory(:#{factory_name}, attrs)" end - defoverridable [ - factory: 1, - factory: 2 - ] - + defoverridable factory: 1, + factory: 2 end end @@ -57,21 +53,24 @@ defmodule PhilColumns.Factory do def build(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) + apply(module, :factory, [factory_name]) - |> handle_build( attrs ) + |> handle_build(attrs) end def insert(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) repo = module.repo + apply(module, :factory, [factory_name]) - |> handle_insert( attrs, repo ) + |> handle_insert(attrs, repo) end def params_for(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) - apply(module, :factory, [factory_name]) #|> do_merge(attrs) - |> handle_params_for( attrs ) + # |> do_merge(attrs) + apply(module, :factory, [factory_name]) + |> handle_params_for(attrs) end def sequence(name), do: PhilColumns.Sequence.next(name) @@ -80,42 +79,43 @@ defmodule PhilColumns.Factory do # Private ########## - defp handle_build( %Changeset{} = changeset, attrs ) do + defp handle_build(%Changeset{} = changeset, attrs) do changeset - |> Changeset.apply_changes - |> do_merge( attrs ) + |> Changeset.apply_changes() + |> do_merge(attrs) end - defp handle_build( %{__meta__: _} = record, attrs ) do + defp handle_build(%{__meta__: _} = record, attrs) do record - |> do_merge( attrs ) + |> do_merge(attrs) end - defp handle_insert( %Changeset{} = changeset, attrs, repo ) do - record = changeset - |> Changeset.apply_changes - |> do_merge( attrs ) + defp handle_insert(%Changeset{} = changeset, attrs, repo) do + record = + changeset + |> Changeset.apply_changes() + |> do_merge(attrs) record |> repo.insert! end - defp handle_insert( %{__meta__: _} = record, attrs, repo ) do + defp handle_insert(%{__meta__: _} = record, attrs, repo) do record - |> do_merge( attrs ) + |> do_merge(attrs) |> repo.insert! end - defp handle_params_for( %Changeset{} = changeset, attrs ) do + defp handle_params_for(%Changeset{} = changeset, attrs) do changeset - |> Changeset.apply_changes - |> do_merge( attrs ) + |> Changeset.apply_changes() + |> do_merge(attrs) |> drop_ecto_fields end - defp handle_params_for( %{__meta__: _} = record, attrs ) do + defp handle_params_for(%{__meta__: _} = record, attrs) do record - |> do_merge( attrs ) + |> do_merge(attrs) |> drop_ecto_fields end @@ -127,34 +127,35 @@ defmodule PhilColumns.Factory do Map.merge(record, attrs) end - defp drop_ecto_fields(record = %{__struct__: struct, __meta__: %{__struct__: Ecto.Schema.Metadata}}) do + defp drop_ecto_fields( + record = %{__struct__: struct, __meta__: %{__struct__: Ecto.Schema.Metadata}} + ) do record - |> Map.from_struct + |> Map.from_struct() |> Map.delete(:__meta__) |> Map.drop(struct.__schema__(:associations)) |> Map.drop(struct.__schema__(:primary_key)) end defp drop_ecto_fields(record) do - raise ArgumentError, "#{inspect record} is not an Ecto model. Use `build` instead." + raise ArgumentError, "#{inspect(record)} is not an Ecto model. Use `build` instead." end - #defp module_from_struct(%{__struct__: struct_name}) do - #struct_name - #end - - #defp name_from_struct(%{__struct__: struct_name}) do - #struct_name - #|> Module.split - #|> List.last - #|> underscore - #|> String.downcase - #|> String.to_atom - #end - - #defp underscore(name) do - #Regex.split(~r/(?=[A-Z])/, name) - #|> Enum.join("_") - #end - + # defp module_from_struct(%{__struct__: struct_name}) do + # struct_name + # end + + # defp name_from_struct(%{__struct__: struct_name}) do + # struct_name + # |> Module.split + # |> List.last + # |> underscore + # |> String.downcase + # |> String.to_atom + # end + + # defp underscore(name) do + # Regex.split(~r/(?=[A-Z])/, name) + # |> Enum.join("_") + # end end diff --git a/lib/phil_columns/seed.ex b/lib/phil_columns/seed.ex index 728a1cd..489294a 100644 --- a/lib/phil_columns/seed.ex +++ b/lib/phil_columns/seed.ex @@ -1,5 +1,4 @@ defmodule PhilColumns.Seed do - defmacro __using__(_opts) do quote location: :keep do import PhilColumns.Seed @@ -14,13 +13,9 @@ defmodule PhilColumns.Seed do def up(repo) do end - defoverridable [ - down: 1, - up: 1 - ] - + defoverridable down: 1, + up: 1 end - end @doc false @@ -29,18 +24,16 @@ defmodule PhilColumns.Seed do tags = Module.get_attribute(env.module, :tags) if envs == [] do - raise "no envs have been defined in #{inspect env.module}" + raise "no envs have been defined in #{inspect(env.module)}" end quote do def __seed__, do: [disable_ddl_transaction: @disable_ddl_transaction] - def envs, do: - unquote(List.flatten(envs) |> Enum.dedup |> Enum.sort) + def envs, do: unquote(List.flatten(envs) |> Enum.dedup() |> Enum.sort()) - def tags, do: - unquote(List.flatten(tags) |> Enum.dedup |> Enum.sort) + def tags, do: unquote(List.flatten(tags) |> Enum.dedup() |> Enum.sort()) end end @@ -67,5 +60,4 @@ defmodule PhilColumns.Seed do @tags unquote(tags) end end - end diff --git a/lib/phil_columns/seed/runner.ex b/lib/phil_columns/seed/runner.ex index 57a53a3..e469861 100644 --- a/lib/phil_columns/seed/runner.ex +++ b/lib/phil_columns/seed/runner.ex @@ -1,5 +1,4 @@ defmodule PhilColumns.Seed.Runner do - @moduledoc false require Logger @@ -8,36 +7,49 @@ defmodule PhilColumns.Seed.Runner do Runs the given seeds. """ def run(repo, module, direction, operation, _migrator_direction, opts) do - log(opts[:log], "== Running #{inspect module}.#{operation}/0 #{direction}") + log(opts[:log], "== Running #{inspect(module)}.#{operation}/0 #{direction}") run_seed(repo, module, operation, opts) end defp run_seed(repo, mod, operation, opts) do - :timer.tc(mod, operation, [repo]) + :timer.tc(mod, operation, [repo, opts]) |> handle_run_seed(opts) end defp handle_run_seed({time, {:ok, artifacts}}, opts) do - createds = Map.get( artifacts, :createds, [] ) - updateds = Map.get( artifacts, :updateds, [] ) - errors = Map.get( artifacts, :errors, [] ) - existings = Map.get( artifacts, :existings, [] ) - - Enum.each(existings, fn(existing) -> - log(opts[:log], "Existing parent #{inspect existing.__struct__}{id: #{inspect existing.id}}") + createds = Map.get(artifacts, :createds, []) + updateds = Map.get(artifacts, :updateds, []) + errors = Map.get(artifacts, :errors, []) + existings = Map.get(artifacts, :existings, []) + + Enum.each(existings, fn existing -> + log( + opts[:log], + "Existing parent #{inspect(existing.__struct__)}{id: #{inspect(existing.id)}}" + ) end) - Enum.each(createds, fn(created) -> - log(opts[:log], "Created #{inspect created.__struct__}{id: #{inspect created.id}}") + Enum.each(createds, fn created -> + log(opts[:log], "Created #{inspect(created.__struct__)}{id: #{inspect(created.id)}}") end) - Enum.each(updateds, fn({updated, changes}) -> - log(opts[:log], "Updated #{inspect updated.__struct__}{id: #{inspect updated.id}} with changes: #{inspect changes}") + Enum.each(updateds, fn {updated, changes} -> + log( + opts[:log], + "Updated #{inspect(updated.__struct__)}{id: #{inspect(updated.id)}} with changes: #{ + inspect(changes) + }" + ) end) - Enum.each(errors, fn(changeset) -> - log(opts[:log], "ERROR #{inspect changeset.errors} on #{changeset.model.__struct__}{id: #{inspect changeset.model.id}} with changes #{inspect changeset.changes}") + Enum.each(errors, fn changeset -> + log( + opts[:log], + "ERROR #{inspect(changeset.errors)} on #{changeset.model.__struct__}{id: #{ + inspect(changeset.model.id) + }} with changes #{inspect(changeset.changes)}" + ) end) if Enum.count(errors) > 0, do: raise("failed due to errors") @@ -47,18 +59,21 @@ defmodule PhilColumns.Seed.Runner do defp handle_run_seed({_time, {:error, %{model: %{id: nil} = model} = changeset}}, _opts) do raise PhilColumns.SeedError, - "Failed to create #{inspect model.__struct__} due to #{inspect changeset.errors} with changes #{inspect changeset.changes}" + "Failed to create #{inspect(model.__struct__)} due to #{inspect(changeset.errors)} with changes #{ + inspect(changeset.changes) + }" end defp handle_run_seed({_time, {:error, %{model: model} = changeset}}, _opts) do raise PhilColumns.SeedError, - "Failed to update #{inspect model.__struct__} due to #{inspect changeset.errors} with changes #{inspect changeset.changes}" + "Failed to update #{inspect(model.__struct__)} due to #{inspect(changeset.errors)} with changes #{ + inspect(changeset.changes) + }" end defp handle_run_seed(_any, _opts) do end defp log(false, _msg), do: :ok - defp log(level, msg), do: Logger.log(level, msg) - + defp log(level, msg), do: Logger.log(level, msg) end diff --git a/lib/phil_columns/seed/schema_seed.ex b/lib/phil_columns/seed/schema_seed.ex index 45dcfd8..c5197f4 100644 --- a/lib/phil_columns/seed/schema_seed.ex +++ b/lib/phil_columns/seed/schema_seed.ex @@ -7,41 +7,54 @@ defmodule PhilColumns.Seed.SchemaSeed do @primary_key false schema "schema_seeds" do - field :version, :integer - timestamps updated_at: false + field(:version, :integer) + field(:tenant, :string) + timestamps(updated_at: false) end - @opts [timeout: :infinity, log: false] + @opts [timeout: :infinity, log: false, schema_migration: true] def ensure_schema_seeds_table!(repo) do - adapter = repo.__adapter__ + adapter = repo.__adapter__() create_seeds_table(adapter, repo) end - def seeded_versions(repo) do - repo.all from(p in {get_source(repo), __MODULE__}, select: p.version), @opts + def seeded_versions(repo, tenant) do + repo.all( + from(p in {get_source(repo), __MODULE__}, select: p.version, where: p.tenant == ^tenant), + @opts + ) end - def up(repo, version) do - repo.insert! %__MODULE__{version: version}, @opts + def up(repo, version, tenant) do + repo.insert!(%__MODULE__{version: version, tenant: tenant}, @opts) end - def down(repo, version) do - repo.delete_all from(p in __MODULE__, where: p.version == ^version), @opts + def down(repo, version, tenant) do + repo.delete_all( + from(p in __MODULE__, where: p.version == ^version and p.tenant == ^tenant), + @opts + ) end def get_source(repo) do - Keyword.get(repo.config, :seed_source, "schema_seeds") + Keyword.get(repo.config(), :seed_source, "schema_seeds") end defp create_seeds_table(adapter, repo) do - table_name = repo |> get_source |> String.to_atom + table_name = repo |> get_source |> String.to_atom() table = %Ecto.Migration.Table{name: table_name} # DDL queries do not log, so we do not need to pass log: false here. - adapter.execute_ddl(repo, - {:create_if_not_exists, table, [ - {:add, :version, :bigint, primary_key: true}, - {:add, :inserted_at, :naive_datetime, []}]}, @opts) + adapter.execute_ddl( + repo, + {:create_if_not_exists, table, + [ + {:add, :version, :bigint, primary_key: true}, + {:add, :tenant, :string, primary_key: true, default: "main"}, + {:add, :inserted_at, :naive_datetime, []} + ]}, + @opts + ) end end diff --git a/lib/phil_columns/seeder.ex b/lib/phil_columns/seeder.ex index 99fab49..c58df61 100644 --- a/lib/phil_columns/seeder.ex +++ b/lib/phil_columns/seeder.ex @@ -1,5 +1,4 @@ defmodule PhilColumns.Seeder do - require Logger alias PhilColumns.Seed.Runner @@ -11,77 +10,83 @@ defmodule PhilColumns.Seeder do This function ensures the migration table exists if no table has been defined yet. """ - @spec seeded_versions(Ecto.Repo.t) :: [integer] - def seeded_versions(repo) do + @spec seeded_versions(Ecto.Repo.t(), String.t()) :: list() + def seeded_versions(repo, tenant) do SchemaSeed.ensure_schema_seeds_table!(repo) - SchemaSeed.seeded_versions(repo) + SchemaSeed.seeded_versions(repo, tenant) end - #@doc """ - #Runs an up migration on the given repository. + # @doc """ + # Runs an up migration on the given repository. ### Options - #* `:log` - the level to use for logging. Defaults to `:info`. - #Can be any of `Logger.level/0` values or `false`. - #""" - #@spec up(Ecto.Repo.t, integer, Module.t, Keyword.t) :: :ok | :already_up | no_return - #def up(repo, version, module, opts \\ []) do - #versions = migrated_versions(repo) - - #if version in versions do - #:already_up - #else - #do_up(repo, version, module, opts) - #:ok - #end - #end + # * `:log` - the level to use for logging. Defaults to `:info`. + # Can be any of `Logger.level/0` values or `false`. + # """ + # @spec up(Ecto.Repo.t, integer, Module.t, Keyword.t) :: :ok | :already_up | no_return + # def up(repo, version, module, opts \\ []) do + # versions = migrated_versions(repo) + + # if version in versions do + # :already_up + # else + # do_up(repo, version, module, opts) + # :ok + # end + # end defp do_up(repo, version, module, opts) do - run_maybe_in_transaction repo, module, fn -> - attempt(repo, module, :forward, :up, :up, opts) - || attempt(repo, module, :forward, :change, :up, opts) - || raise PhilColumns.SeedError, message: "#{inspect module} does not implement a `up/0` function" - SchemaSeed.up(repo, version) - end + run_maybe_in_transaction(repo, module, fn -> + attempt(repo, module, :forward, :up, :up, opts) || + attempt(repo, module, :forward, :change, :up, opts) || + raise PhilColumns.SeedError, + message: "#{inspect(module)} does not implement a `up/0` function" + + SchemaSeed.up(repo, version, opts[:tenant]) + end) end - #@doc """ - #Runs a down migration on the given repository. + # @doc """ + # Runs a down migration on the given repository. ### Options - #* `:log` - the level to use for logging. Defaults to `:info`. - #Can be any of `Logger.level/0` values or `false`. + # * `:log` - the level to use for logging. Defaults to `:info`. + # Can be any of `Logger.level/0` values or `false`. - #""" - #@spec down(Ecto.Repo.t, integer, Module.t) :: :ok | :already_down | no_return - #def down(repo, version, module, opts \\ []) do - #versions = migrated_versions(repo) + # """ + # @spec down(Ecto.Repo.t, integer, Module.t) :: :ok | :already_down | no_return + # def down(repo, version, module, opts \\ []) do + # versions = migrated_versions(repo) - #if version in versions do - #do_down(repo, version, module, opts) - #:ok - #else - #:already_down - #end - #end + # if version in versions do + # do_down(repo, version, module, opts) + # :ok + # else + # :already_down + # end + # end defp do_down(repo, version, module, opts) do - run_maybe_in_transaction repo, module, fn -> - attempt(repo, module, :forward, :down, :down, opts) - || attempt(repo, module, :backward, :change, :down, opts) - || raise PhilColumns.SeedError, message: "#{inspect module} does not implement a `down/0` function" - SchemaSeed.down(repo, version) - end + run_maybe_in_transaction(repo, module, fn -> + attempt(repo, module, :forward, :down, :down, opts) || + attempt(repo, module, :backward, :change, :down, opts) || + raise PhilColumns.SeedError, + message: "#{inspect(module)} does not implement a `down/0` function" + + SchemaSeed.down(repo, version, opts[:tenant]) + end) end defp run_maybe_in_transaction(repo, module, fun) do cond do - module.__seed__[:disable_ddl_transaction] -> + module.__seed__()[:disable_ddl_transaction] -> fun.() + repo.__adapter__.supports_ddl_transaction? -> - repo.transaction fun, [log: false, timeout: :infinity] + repo.transaction(fun, log: false, timeout: :infinity) + true -> fun.() end @@ -89,13 +94,13 @@ defmodule PhilColumns.Seeder do defp attempt(repo, module, direction, operation, reference, opts) do if Code.ensure_loaded?(module) and - function_exported?(module, operation, 1) do + function_exported?(module, operation, 1) do Runner.run(repo, module, direction, operation, reference, opts) :ok end end - @doc """ + @doc """ Apply seeds in a directory to a repository with given strategy. A strategy must be given as an option. @@ -109,19 +114,21 @@ defmodule PhilColumns.Seeder do Can be any of `Logger.level/0` values or `false`. """ - @spec run(Ecto.Repo.t, binary, atom, Keyword.t) :: [integer] + @spec run(Ecto.Repo.t(), binary, atom, Keyword.t()) :: [integer] def run(repo, directory, direction, opts) do maybe_ensure_all_started(Application.get_env(:phil_columns, :ensure_all_started)) - versions = seeded_versions(repo) + versions = seeded_versions(repo, opts[:tenant]) cond do opts[:all] -> run_all(repo, versions, directory, direction, opts) + to = opts[:to] -> run_to(repo, versions, directory, direction, to, opts) - #step = opts[:step] -> - #run_step(repo, versions, directory, direction, step, opts) + + # step = opts[:step] -> + # run_step(repo, versions, directory, direction, step, opts) true -> raise ArgumentError, message: "expected one of :all, :to, or :step strategies" end @@ -132,75 +139,76 @@ defmodule PhilColumns.Seeder do without actually running any seeds. """ def seeds(repo, directory, opts) do - versions = seeded_versions(repo) + versions = seeded_versions(repo, opts[:tenant]) - Enum.map(pending_in_direction(versions, directory, :down, opts) |> Enum.reverse, fn {a, b, _, _} -> + Enum.map(pending_in_direction(versions, directory, :down, opts) |> Enum.reverse(), fn {a, b, + _, + _} -> {:up, a, b} - end) - ++ - Enum.map(pending_in_direction(versions, directory, :up, opts), fn {a, b, _, _} -> - {:down, a, b} - end) + end) ++ + Enum.map(pending_in_direction(versions, directory, :up, opts), fn {a, b, _, _} -> + {:down, a, b} + end) end - #defp run_to(repo, versions, directory, direction, 0, opts) do - #pending_in_direction(versions, directory, direction, opts) - #|> seed(direction, repo, opts) - #end + # defp run_to(repo, versions, directory, direction, 0, opts) do + # pending_in_direction(versions, directory, direction, opts) + # |> seed(direction, repo, opts) + # end defp run_to(repo, versions, directory, direction, target, opts) do pending_in_direction(versions, directory, direction, opts) - |> Enum.take_while(fn(seed_info) -> - within_target_version?(seed_info, target, direction) - end) + |> Enum.take_while(fn seed_info -> + within_target_version?(seed_info, target, direction) + end) |> seed(direction, repo, opts) end - defp within_target_version?({version,_,_,_}, target, :up), do: version <= target - defp within_target_version?({version,_,_,_}, target, :down), do: version >= target + defp within_target_version?({version, _, _, _}, target, :up), do: version <= target + defp within_target_version?({version, _, _, _}, target, :down), do: version >= target - #defp run_step(repo, versions, directory, direction, count, opts) do - #pending_in_direction(versions, directory, direction) - #|> Enum.take(count) - #|> seed(direction, repo, opts) - #end + # defp run_step(repo, versions, directory, direction, count, opts) do + # pending_in_direction(versions, directory, direction) + # |> Enum.take(count) + # |> seed(direction, repo, opts) + # end defp run_all(repo, versions, directory, direction, opts) do pending_in_direction(versions, directory, direction, opts) |> seed(direction, repo, opts) end - #defp pending_in_direction(versions, directory, :up) do - #seeds_for(directory) - #|> Enum.filter(fn {version, _name, _file} -> not (version in versions) end) - #end + # defp pending_in_direction(versions, directory, :up) do + # seeds_for(directory) + # |> Enum.filter(fn {version, _name, _file} -> not (version in versions) end) + # end - #defp pending_in_direction(versions, directory, :down) do - #seeds_for(directory) - #|> Enum.filter(fn {version, _name, _file} -> version in versions end) - #|> Enum.reverse - #end + # defp pending_in_direction(versions, directory, :down) do + # seeds_for(directory) + # |> Enum.filter(fn {version, _name, _file} -> version in versions end) + # |> Enum.reverse + # end defp pending_in_direction(versions, directory, :up, opts) do seeds_for(directory) |> Enum.filter(fn {version, _name, _file} -> not (version in versions) end) |> Enum.map(fn {version, name, file} -> - [{mod, _bin}] = Code.compile_file(file) - {version, name, file, mod} - end) + [{mod, _bin}] = Code.compile_file(file) + {version, name, file, mod} + end) |> Enum.filter(fn {_version, _name, _file, mod} -> - has_env_and_any_tags?(mod, opts[:env], opts[:tags]) - end) + has_env_and_any_tags?(mod, opts[:env], opts[:tags]) + end) end defp pending_in_direction(versions, directory, :down, _opts) do seeds_for(directory) |> Enum.filter(fn {version, _name, _file} -> version in versions end) |> Enum.map(fn {version, name, file} -> - [{mod, _bin}] = Code.compile_file(file) - {version, name, file, mod} - end) - |> Enum.reverse + [{mod, _bin}] = Code.compile_file(file) + {version, name, file, mod} + end) + |> Enum.reverse() end defp seeds_for(directory) do @@ -213,11 +221,12 @@ defmodule PhilColumns.Seeder do defp extract_seed_info(file) do base = Path.basename(file) - ext = Path.extname(base) + ext = Path.extname(base) case Integer.parse(Path.rootname(base)) do {integer, "_" <> name} when ext == ".exs" -> {integer, name, file} + _ -> nil end @@ -226,31 +235,34 @@ defmodule PhilColumns.Seeder do defp seed(seeds, direction, repo, opts) do log_seeding_start(direction, opts) - if Enum.empty? seeds do + if Enum.empty?(seeds) do level = Keyword.get(opts, :log, :info) log(level, "Already #{direction}") end ensure_no_duplication(seeds) - Enum.map seeds, fn {version, _name, file, mod} -> + Enum.map(seeds, fn {version, _name, file, mod} -> function_exported?(mod, :__seed__, 0) || raise_no_seed_in_file(file) case direction do - :up -> do_up(repo, version, mod, opts) + :up -> do_up(repo, version, mod, opts) :down -> do_down(repo, version, mod, opts) end version - end + end) end defp log_seeding_start(:up, opts) do - log(opts[:log], "=== Executing seeds up for env #{inspect opts[:env]} and tags #{inspect opts[:tags]}") + log( + opts[:log], + "=== Executing seeds up for env #{inspect(opts[:env])} and tags #{inspect(opts[:tags])}" + ) end defp log_seeding_start(:down, opts) do - log(opts[:log], "=== Executing seeds down for env #{inspect opts[:env]}") + log(opts[:log], "=== Executing seeds down for env #{inspect(opts[:env])}") end defp ensure_no_duplication([{version, name, _, _} | t]) do @@ -275,22 +287,22 @@ defmodule PhilColumns.Seeder do end defp has_env_and_any_tags?(mod, env, tags) do - Enum.member?(mod.envs, env) && + Enum.member?(mod.envs(), env) && any_intersection?(mod, tags) end defp any_intersection?(_mod, []), do: true defp any_intersection?(mod, tags) do - (intersection(mod.tags, tags) |> Enum.count) > 0 + intersection(mod.tags, tags) |> Enum.count() > 0 end defp intersection(list_a, list_b) do - list_a -- (list_a -- list_b) + list_a -- list_a -- list_b end defp log(false, _msg), do: :ok - defp log(level, msg), do: Logger.log(level, msg) + defp log(level, msg), do: Logger.log(level, msg) defp maybe_ensure_all_started(nil), do: nil @@ -298,7 +310,7 @@ defmodule PhilColumns.Seeder do Enum.each(apps, &Application.ensure_all_started(&1)) end - defp maybe_ensure_all_started(_other), do: raise "ensure_all_started must be a list of apps" + defp maybe_ensure_all_started(_other), do: raise("ensure_all_started must be a list of apps") def with_repo(repo, fun, opts \\ []) do config = repo.config() @@ -330,5 +342,4 @@ defmodule PhilColumns.Seeder do error end end - end diff --git a/lib/phil_columns/sequence.ex b/lib/phil_columns/sequence.ex index 6a92375..2c72059 100644 --- a/lib/phil_columns/sequence.ex +++ b/lib/phil_columns/sequence.ex @@ -1,25 +1,25 @@ defmodule PhilColumns.Sequence do def start_link do - Agent.start_link(fn -> Map.new end, name: __MODULE__) + Agent.start_link(fn -> Map.new() end, name: __MODULE__) end def reset do - Agent.update(__MODULE__, fn(_) -> Map.new end) + Agent.update(__MODULE__, fn _ -> Map.new() end) end def next(sequence_name) when is_binary(sequence_name) do - next(sequence_name, fn(n) -> sequence_name <> to_string(n) end) + next(sequence_name, fn n -> sequence_name <> to_string(n) end) end def next(sequence_name) do raise( ArgumentError, - "sequence name must be a string; got #{inspect sequence_name} instead" + "sequence name must be a string; got #{inspect(sequence_name)} instead" ) end def next(sequence_name, formatter) do - Agent.get_and_update(__MODULE__, fn(sequences) -> + Agent.get_and_update(__MODULE__, fn sequences -> current_value = Map.get(sequences, sequence_name, 0) new_sequences = Map.put(sequences, sequence_name, current_value + 1) {formatter.(current_value), new_sequences} diff --git a/mix.exs b/mix.exs index 5311b62..f3882c7 100644 --- a/mix.exs +++ b/mix.exs @@ -2,29 +2,31 @@ defmodule PhilColumns.Mixfile do use Mix.Project def project do - [app: :phil_columns, - version: "3.1.0", - build_path: "../../_build", - config_path: "../../config/config.exs", - deps_path: "../../deps", - lockfile: "../../mix.lock", - elixir: "~> 1.0", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - description: description(), - package: package(), - deps: deps()] + [ + app: :glific_phil_columns, + version: "3.1.0", + # build_path: "../../_build", + # config_path: "../../config/config.exs", + # deps_path: "../../deps", + # lockfile: "../../mix.lock", + elixir: "~> 1.0", + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + description: description(), + package: package(), + deps: deps() + ] end defp description do """ - A full featured Elixir/Ecto seeding and factory solution providing means for dev and prod seeding as well as factories for test. + A fork from a a full featured Elixir/Ecto seeding and factory solution (phil_columns) providing means for dev and prod seeding as well as factories for test. """ end defp package do [ - name: :phil_columns, + name: :glific_phil_columns, files: [ "lib", "mix.exs", @@ -34,7 +36,7 @@ defmodule PhilColumns.Mixfile do maintainers: ["C. Jason Harrelson"], licenses: ["MIT"], links: %{ - "GitHub" => "https://github.com/midas/phil_columns-ex", + "GitHub" => "https://github.com/glific/phil_columns-ex", "Docs" => "https://hexdocs.pm/phil_columns/3.0.0" } ] diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..75662f3 --- /dev/null +++ b/mix.lock @@ -0,0 +1,15 @@ +%{ + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, + "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, + "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, +} diff --git a/phil_columns-3.1.0.tar b/phil_columns-3.1.0.tar new file mode 100644 index 0000000..1e846da Binary files /dev/null and b/phil_columns-3.1.0.tar differ