diff --git a/README.md b/README.md index 80f428d..ed27809 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ This is not meant to generate data for your tests, if this is what you need, checkout [ecto_fixtures](https://github.com/dockyard/ecto_fixtures) or [ex_machina](https://github.com/thoughtbot/ex_machina) instead. +## Compatible versions + +Version `0.3.0` or lower: + +* Elixir ~> `1.11.x` or lower. + +Version `0.4.0`: + +* Elixir ~> `1.13.x`. + +Version `0.5.0` or higher: + +* Elixir ~> `1.14.x` or higher. + ## Installation Add `seedex` to your list of dependencies in `mix.exs`: diff --git a/config/config.exs b/config/config.exs index 12d81f3..ae29279 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :seedex, ecto_repos: [Seedex.Repo], @@ -8,6 +8,7 @@ config :seedex, config :seedex, Seedex.Repo, database: "seedex_test", username: "postgres", + password: "postgres", hostname: "localhost", pool: Ecto.Adapters.SQL.Sandbox diff --git a/lib/mix/tasks/seedex.seed.ex b/lib/mix/tasks/seedex.seed.ex index 2ed8571..477fe36 100644 --- a/lib/mix/tasks/seedex.seed.ex +++ b/lib/mix/tasks/seedex.seed.ex @@ -24,7 +24,10 @@ defmodule Mix.Tasks.Seedex.Seed do @doc false def run(args) do - {opts, _, _} = OptionParser.parse(args, switches: [env: :string, seeds_path: :string, debug: :boolean, quiet: :boolean]) + {opts, _, _} = + OptionParser.parse(args, + switches: [env: :string, seeds_path: :string, debug: :boolean, quiet: :boolean] + ) if opts[:debug] do Logger.configure(level: :debug) @@ -35,20 +38,20 @@ defmodule Mix.Tasks.Seedex.Seed do Mix.Task.run("app.start", []) seeds_path = Keyword.get(opts, :seeds_path, default_path()) - env = Keyword.get(opts, :env, to_string(Mix.env)) + env = Keyword.get(opts, :env, to_string(Mix.env())) unless File.dir?(seeds_path) do - Mix.raise """ + Mix.raise(""" seeeds_path is not a directory, create priv/repo/seeds or configure in :seedex configuration - """ + """) end seeds_path |> seeds_files(env) - |> Enum.each(&Code.load_file(&1)) + |> Enum.each(&Code.compile_file(&1)) unless opts[:quiet] do - Mix.shell.info("Database has been populated with seeds from #{seeds_path}") + Mix.shell().info("Database has been populated with seeds from #{seeds_path}") end end @@ -60,7 +63,7 @@ defmodule Mix.Tasks.Seedex.Seed do defp exs_files(path) do path |> Path.join("*.exs") - |> Path.wildcard + |> Path.wildcard() end defp default_path do diff --git a/lib/seedex.ex b/lib/seedex.ex index 4706e3f..c8b9044 100644 --- a/lib/seedex.ex +++ b/lib/seedex.ex @@ -34,7 +34,8 @@ defmodule Seedex do ] ``` """ - @spec seed(module :: atom, constraints :: [atom], data :: [map], process :: (struct -> struct)) :: :ok + @spec seed(module :: atom, constraints :: [atom], data :: [map], process :: (struct -> struct)) :: + :ok def seed(module, constraints \\ [:id], data \\ [], process \\ nil) do dispatch_seed(module, constraints, data, process, update: true) end @@ -42,7 +43,12 @@ defmodule Seedex do @doc """ Same as `seed/3` but does not update the record if it already exists """ - @spec seed_once(module :: atom, constraints :: [atom], data :: (struct -> struct) | [map], process :: (struct -> struct)) :: :ok + @spec seed_once( + module :: atom, + constraints :: [atom], + data :: (struct -> struct) | [map], + process :: (struct -> struct) + ) :: :ok def seed_once(module, constraints \\ [:id], data \\ [], process \\ nil) do dispatch_seed(module, constraints, data, process, update: false) end @@ -52,39 +58,48 @@ defmodule Seedex do # arguments were all pased defp dispatch_seed(module, constraints, data, func, opts) when is_function(func, 1), do: do_seed(module, constraints, data, func, opts) + # 3 arguments passed defp dispatch_seed(module, [h | t], data, nil, opts) when is_atom(h) and is_list(data), do: do_seed(module, [h | t], data, &identity/1, opts) + defp dispatch_seed(module, [h | t], func, nil, opts) when is_atom(h) and is_function(func, 1), do: do_seed(module, [h | t], [], func, opts) + defp dispatch_seed(module, [h | t], func, nil, opts) when is_map(h) and is_function(func, 1), do: do_seed(module, [:id], [h | t], func, opts) + # 2 arguments passed defp dispatch_seed(module, func, [], nil, opts) when is_function(func, 1), do: do_seed(module, [:id], [], func, opts) + defp dispatch_seed(module, [h | t], [], nil, opts) when is_map(h), do: do_seed(module, [:id], [h | t], &identity/1, opts) + defp dispatch_seed(_module, _constraints, _data, _func, _opts), - do: raise ArgumentError, "invalid arguments to seed" + do: raise(ArgumentError, "invalid arguments to seed") - defp do_seed(module, constraints, [], process, opts), do: - do_seed(module, constraints, [%{}], process, opts) + defp do_seed(module, constraints, [], process, opts), + do: do_seed(module, constraints, [%{}], process, opts) defp do_seed(module, constraints, data, process, opts) do - Enum.each data, fn record -> + Enum.each(data, fn record -> record = struct(module, record) |> process.() insert_seed(module, record, constraints, opts) - end + end) end defp insert_seed(module, record, constraints, opts) do existing = fetch_record(module, record, constraints) + cond do existing && opts[:update] -> update_record(record, existing) + !existing -> Logger.debug("Inserting record #{inspect(record)}") repo().insert(record) + true -> :ok end @@ -94,6 +109,7 @@ defmodule Seedex do case make_query(record, constraints) do [] -> nil + query -> repo().get_by(module, query) end @@ -101,7 +117,7 @@ defmodule Seedex do defp make_query(record, constraints) do constraints - |> Enum.map(& {&1, Map.fetch!(record, &1)}) + |> Enum.map(&{&1, Map.fetch!(record, &1)}) |> Enum.reject(fn {_k, v} -> is_nil(v) end) end @@ -113,18 +129,23 @@ defmodule Seedex do defp make_changeset(record, existing) do {changeset, changes} = {Ecto.Changeset.change(existing), Map.from_struct(record)} - Enum.reduce changes, changeset, fn + + Enum.reduce(changes, changeset, fn {_key, %Ecto.Association.NotLoaded{}}, changeset -> changeset + {_key, nil}, changeset -> changeset + {key, _value}, changeset when key in ["__meta__", :__meta__] -> changeset + {key, %Ecto.Association.BelongsTo{} = assoc}, changeset -> Ecto.Changeset.put_assoc(changeset, key, assoc) + {key, value}, changeset -> Ecto.Changeset.put_change(changeset, key, value) - end + end) end defp repo do diff --git a/mix.exs b/mix.exs index ddaca29..03a437e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,39 +1,46 @@ defmodule Seedex.Mixfile do use Mix.Project - @version "0.3.0" + @version "0.3.1" def project do - [app: :seedex, - version: @version, - elixir: "~> 1.1", - description: "Seed data generation for Ecto", - source_url: "https://github.com/danhper/seedex", - elixirc_paths: elixirc_paths(Mix.env), - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - package: package(), - deps: deps(), - docs: [source_ref: "#{@version}", extras: ["README.md"], main: "readme"]] + [ + app: :seedex, + version: @version, + elixir: "~> 1.13", + description: "Seed data generation for Ecto", + source_url: "https://github.com/danhper/seedex", + elixirc_paths: elixirc_paths(Mix.env()), + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + package: package(), + deps: deps(), + docs: [source_ref: "#{@version}", extras: ["README.md"], main: "readme"] + ] end def application do - [applications: applications(Mix.env), - description: 'Seed data generation for Ecto'] + [ + extra_applications: [:ecto], + applications: applications(Mix.env()), + description: 'Seed data generation for Ecto' + ] end defp applications(:test), do: applications(:all) ++ [:ecto, :postgrex] - defp applications(_all), do: [:logger] + defp applications(_all), do: [:logger] defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_all), do: ["lib"] + defp elixirc_paths(_all), do: ["lib"] defp deps do - [{:ecto, "~> 1.1 or ~> 2.1 or ~> 3.0"}, - {:ecto_sql, "~> 3.0"}, - {:postgrex, "~> 0.13", only: [:test]}, - {:earmark, "~> 1.0", only: :docs}, - {:ex_doc, "~> 0.14", only: :docs}] + [ + {:ecto, "~> 3.9"}, + {:ecto_sql, "~> 3.9"}, + {:postgrex, "~> 0.16.1", only: [:test]}, + {:earmark, "~> 1.4.20", only: :docs}, + {:ex_doc, "~> 0.14", only: :docs} + ] end defp package do diff --git a/mix.lock b/mix.lock index d14ead0..ca81cb0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,16 @@ %{ - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.1.1", "d6677f95f1e0bd39bc3db3db6b23a59977cb154ed2cceec69a56becd805128be", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.1.1", "af2458e7a467d75a6389e1d4ebfb57c328ccc684d6ee52145f7b34e94efb5fc4", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "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": {:hex, :earmark, "1.4.20", "d5097b1c7417a03c73a2985fcf01c3f72192c427b8a498719737dca5273938cb", [:mix], [{:earmark_parser, "== 1.4.18", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "7be744242dbde74c858279f4a65d9d31f37d163190d739340015c30038c1edb3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"}, + "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [: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", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.1", "9bd5894eecc53d5b39d0c95180d4466aff00e10679e13a5cfa725f6f85c03c22", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [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", "5fd470a4fff2e829bbf9dcceb7f3f9f6d1e49b4241e802f614de6b8b67c51118"}, + "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "8e24fc8ff9a50b9f557ff020d6c91a03cded7e59ac3e0eec8a27e771430c7d27"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5fbc8e549aa9afeea2847c0769e3970537ed302f93a23ac612602e805d9d1e7f"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "adf0218695e22caeda2820eaba703fa46c91820d53813a2223413da3ef4ba515"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.14.2", "6680591bbce28d92f043249205e8b01b36cab9ef2a7911abc43649242e1a3b78", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, }