diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1fb730f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.13 +erlang 24.2.2 \ No newline at end of file diff --git a/README.md b/README.md index a9d2076..0ee6e69 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,69 @@ $ mix factory_ex.gen --repo FactoryEx.Support.Repo FactoryEx.Support.{Accounts.{ ``` To read more info run `mix factory_ex.gen` + +### Build Relational Associations + +FactoryEx makes it possible to create associated records with your factories. This is +similar to creating to creating records with Ecto.Changeset `cast_assoc` or `put_assoc` +with the addition of using your factory to generate the parameters. To use this feature +you must pass the `relational` option. The `relational` option accepts a list of keys +that map to associations, for example if a Team has many users you would pass +`[relational: [:users]]`: + +```elixir + FactoryEx.AssociationBuilder.build_params( + FactoryEx.Support.Factory.Accounts.Team, + %{}, + relational: [:users] + ) +``` + +The goal of this feature is to reduce boilerplate code in your test. In this example we +create a team with 3 users that each have a label and a role: + +```elixir +setup do + team = FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Team) + + user_one = FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.User, %{team_id: team.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Label, %{user_id: user_one.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Role, %{user_id: user_one.id}) + + user_two = FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.User, %{team_id: team.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Label, %{user_id: user_two.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Role, %{user_id: user_two.id}) + + user_three = FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.User, %{team_id: team.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Label, %{user_id: user_three.id}) + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.Role, %{user_id: user_three.id}) +end +``` + +With the relational feature this can be written as: + +```elixir +setup do + team = + FactoryEx.insert!( + FactoryEx.Support.Factory.Accounts.Team, + %{users: {3, %{}}}, + relational: [users: [:labels, :role]] + ) +end +``` + +You can create many associations by specifying a tuple of `{count, params}` which is expanded +to a list of params before building the params with a factory. For example if you pass a +tuple of `{2, %{name: "John"}}` it will be expanded to `[%{name: "John"}, %{name: "John"}]`. +The count tuples can be added as elements inside a list or as values in the map of +parameters. You can also manually add parameters which is useful for setting specific values +while creating a specific amount. For example given three items if you wanted to customize +the name for one you can do `[%{}, %{name: "custom"}, %{}]` or `[{2, %{}}, %{name: "custom"}]`. + +By default parameters are validated by Ecto.Changeset. If this behavior is not desired you can +set the `validate` option to false which converts params to structs only. + +While this can simplify the amount of boilerplate you have to write it comes with a trade off +of creating large complex objects that can hurt readability and/or make accessing specific +data harder. diff --git a/config/config.exs b/config/config.exs index 955391a..6797d28 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,9 +1,17 @@ import Config -config :factory_ex, FactoryExTest.MyRepo, - username: System.get_env("POSTGRES_USER") || "postgres", - password: System.get_env("POSTGRES_PASSWORD") || "postgres", - database: "factory_ex_test", - hostname: "localhost", - pool: Ecto.Adapters.SQL.Sandbox, - pool_size: String.to_integer(System.get_env("POSTGRES_POOL_SIZE", "10")) +if Mix.env() == :test do + config :factory_ex, ecto_repos: [FactoryEx.Support.Repo] + config :factory_ex, repo: FactoryEx.Support.Repo + config :factory_ex, :sql_sandbox, true + config :factory_ex, FactoryEx.Support.Repo, + username: "postgres", + password: "postgres", + database: "factory_ex_test", + hostname: "localhost", + pool: Ecto.Adapters.SQL.Sandbox, + pool_size: 10, + show_sensitive_data_on_connection_error: true, + log: :debug, + stacktrace: true +end diff --git a/lib/factory_ex.ex b/lib/factory_ex.ex index 590ff16..9796591 100644 --- a/lib/factory_ex.ex +++ b/lib/factory_ex.ex @@ -2,7 +2,12 @@ defmodule FactoryEx do @build_definition [ keys: [ type: {:in, [:atom, :string, :camel_string]}, - doc: "Sets the type of keys to have in the built object, can be one of `:atom`, `:string` or `:camel_string`" + doc: + "Sets the type of keys to have in the built object, can be one of `:atom`, `:string` or `:camel_string`" + ], + relational: [ + type: {:or, [{:list, :any}, :keyword_list]}, + doc: "Sets the ecto schema association fields to generate, can be a list of `:atom` or `:keyword_list`" ] ] @@ -15,11 +20,11 @@ defmodule FactoryEx do #{NimbleOptions.docs(@build_definition)} """ - alias FactoryEx.Utils + alias FactoryEx.{AssociationBuilder, Utils} @type build_opts :: [ - keys: :atom | :string | :camel_string - ] + keys: :atom | :string | :camel_string + ] @doc """ Callback that returns the schema module. @@ -72,7 +77,9 @@ defmodule FactoryEx do opts = NimbleOptions.validate!(opts, @build_definition) params + |> Utils.expand_count_tuples() |> module.build() + |> then(&AssociationBuilder.build_params(module, &1, opts)) |> Utils.deep_struct_to_map() |> maybe_encode_keys(opts) end @@ -94,18 +101,20 @@ defmodule FactoryEx do schema = module.schema() Code.ensure_loaded(schema) - field = schema.__schema__(:fields) + field = + schema.__schema__(:fields) |> Kernel.--([:updated_at, :inserted_at, :id]) |> Enum.reject(&(schema.__schema__(:type, &1) === :id)) - |> Enum.random + |> Enum.random() field_type = schema.__schema__(:type, field) - field_value = case field_type do - :integer -> "asdfd" - :string -> 1239 - _ -> 4321 - end + field_value = + case field_type do + :integer -> "asdfd" + :string -> 1239 + _ -> 4321 + end Map.put(params, field, field_value) end @@ -127,8 +136,10 @@ defmodule FactoryEx do validate = Keyword.get(options, :validate, true) params + |> Utils.expand_count_tuples() |> module.build() - |> maybe_changeset(module, validate) + |> then(&AssociationBuilder.build_params(module, &1, options)) + |> maybe_create_changeset(module, validate) |> case do %Ecto.Changeset{} = changeset -> Ecto.Changeset.apply_action!(changeset, :insert) struct when is_struct(struct) -> struct @@ -149,11 +160,13 @@ defmodule FactoryEx do def insert!(module, params, options) do Code.ensure_loaded(module.schema()) - validate? = Keyword.get(options, :validate, true) + validate = Keyword.get(options, :validate, true) params + |> Utils.expand_count_tuples() |> module.build() - |> maybe_changeset(module, validate?) + |> then(&AssociationBuilder.build_params(module, &1, options)) + |> maybe_create_changeset(module, validate) |> module.repo().insert!(options) end @@ -176,20 +189,61 @@ defmodule FactoryEx do module.repo().delete_all(module.schema(), options) end - defp maybe_changeset(params, module, validate?) do - if validate? && schema?(module) do + defp maybe_create_changeset(params, module, validate) do + if validate && schema?(module) do params = Utils.deep_struct_to_map(params) if create_changeset_defined?(module.schema()) do - module.schema().create_changeset(params) + params + |> module.schema().create_changeset() + |> maybe_put_assocs(params) else - module.schema().changeset(struct(module.schema(), %{}), params) + module.schema() + |> struct(%{}) + |> module.schema().changeset(params) + |> maybe_put_assocs(params) end else - struct!(module.schema, params) + deep_struct!(module.schema, params) end end + defp maybe_put_assocs(%{data: %module{}} = changeset, params) do + :associations + |> module.__schema__() + |> Enum.reduce(changeset, fn field, changeset -> + case Map.get(params, field) do + nil -> changeset + attrs -> Ecto.Changeset.put_assoc(changeset, field, attrs) + end + end) + end + + defp deep_struct!(schema_module, params) when is_list(params) do + Enum.map(params, &deep_struct!(schema_module, &1)) + end + + defp deep_struct!(schema_module, params) do + Enum.reduce(params, struct!(schema_module, params), &convert_to_struct(&1, schema_module, &2)) + end + + defp convert_to_struct({field, attrs}, schema_module, acc) do + attrs = + case :associations + |> schema_module.__schema__() + |> Enum.find(&(&1 === field)) + |> then(&schema_module.__schema__(:association, &1)) do + nil -> + attrs + + ecto_assoc -> + deep_struct!(ecto_assoc.queryable, attrs) + + end + + Map.put(acc, field, attrs) + end + defp create_changeset_defined?(module) do function_exported?(module, :create_changeset, 1) end diff --git a/lib/factory_ex/association_builder.ex b/lib/factory_ex/association_builder.ex new file mode 100644 index 0000000..62da673 --- /dev/null +++ b/lib/factory_ex/association_builder.ex @@ -0,0 +1,158 @@ +defmodule FactoryEx.AssociationBuilder do + @moduledoc """ + + This module implements the api for auto generating Ecto Associations with factories. + + ## Requirements + + This module expects the `FactoryEx.FactoryCache` to have been started and initialized. + See the module documentation for more information. + + ## Relational Builder + + This is an introduction to the `relational` option which is used to auto-generate + factory parameters based on the keys. Let's look at an example. + + If a `Team` has many `User` associations you can build many params using the `Team` + factory and pass `users` as the field in the relational option. + + ```elixir + FactoryEx.AssociationBuilder.build_params( + FactoryEx.Support.Factory.Accounts.Team, + %{}, + relational: [:users] + ) + ``` + + Note that we did not add the field `team` since the team is the parent schema and all fields + must be valid associations of the team schema. The association builder may raise if a field + is not a valid ecto association for the given factory's schema. + + ## Examples + + ```elixir + # Create params for a one-to-one relationship + FactoryEx.AssociationBuilder.build_params( + FactoryEx.Support.Factory.Accounts.User, + %{name: "Jane Doe"}, + relational: [:role, :team] + ) + %{ + name: "Jane Doe", + role: %{code: "Utah cats"}, + team: %{name: "Macejkovic Group"} + } + + # Create a specific count params for a one-to-many relationship + FactoryEx.AssociationBuilder.build_params( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{teams: [%{}, %{name: "awesome team name"}]}, + relational: [:teams] + ) + %{teams: [%{name: "Lindgren-Zemlak"}, %{name: "awesome team name"}]} + + # You can also build deep relational structures + FactoryEx.AssociationBuilder.build_params( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{}, + relational: [teams: [users: [:labels, :role]]] + ) + %{ + teams: [ + %{ + name: "Leffler Group", + users: [ + %{ + birthday: ~D[1992-10-04], + email: "suzanne.yundt@armstrong.info", + gender: "male", + labels: [%{label: "autem"}], + location: "someplace", + name: "Gerda Waelchi", + role: %{code: "North Carolina whales"} + } + ] + } + ] + } + ``` + """ + + @doc """ + Builds Ecto Association parameters. + """ + @spec build_params(module(), map(), Keyword.t()) :: map() + def build_params(factory_module, params \\ %{}, options \\ []) do + schema = factory_module.schema() + assoc_fields = Keyword.get(options, :relational, []) + + convert_fields_to_params(schema, params, assoc_fields) + end + + defp convert_fields_to_params(schema, params, assoc_fields) do + Enum.reduce(assoc_fields, params, &create_schema_params(schema, &1, &2)) + end + + defp create_schema_params(schema, {field, assoc_fields}, params) do + schema + |> fetch_assoc!(field) + |> create_one_or_many_params(params, field, assoc_fields) + |> then(fn + nil -> params + assoc_params -> Map.put(params, field, assoc_params) + end) + end + + defp create_schema_params(schema, field, params) do + create_schema_params(schema, {field, []}, params) + end + + defp create_one_or_many_params( + %{cardinality: :many, queryable: queryable}, + params, + field, + assoc_fields + ) do + params + |> Map.get(field, [%{}]) + |> Enum.map(&factory_build(queryable, &1, assoc_fields)) + end + + defp create_one_or_many_params( + %{cardinality: :one, queryable: queryable}, + params, + field, + assoc_fields + ) do + params = Map.get(params, field, %{}) + factory_build(queryable, params, assoc_fields) + end + + defp factory_build(queryable, params, assoc_fields) do + parent = FactoryEx.FactoryCache.build_params(queryable, params) + assoc = convert_fields_to_params(queryable, params, assoc_fields) + + Map.merge(parent, assoc) + end + + defp fetch_assoc!(schema, field) do + assocs = schema.__schema__(:associations) + + if Enum.member?(assocs, field) do + schema.__schema__(:association, field) + else + raise """ + The field '#{inspect(field)}' you entered was not found on schema '#{inspect(schema)}'. + + Did you mean one of the following fields? + #{inspect(assocs)} + + To fix this error: + + - Ensure the field exists on the schema '#{inspect(schema)}'. + + - Return a schema from the `schema/0` callback function that contains the field '#{inspect(field)}'. + """ + end + end +end diff --git a/lib/factory_ex/factory_cache.ex b/lib/factory_ex/factory_cache.ex new file mode 100644 index 0000000..f5b48e1 --- /dev/null +++ b/lib/factory_ex/factory_cache.ex @@ -0,0 +1,168 @@ +defmodule FactoryEx.FactoryCache do + @moduledoc """ + This module implements a key-value store that maps Ecto.Schema modules + to Factory module values. This is used to automatically generate + relational ecto data structures through the `relational` option. + + ## Getting Started + + To use this cache you must first start the cache and call setup. + You can add this to your `test_helper.exs` file: + + ```elixir + # test_helper.exs + Cache.start_link([FactoryEx.FactoryCache]) + FactoryEx.FactoryCache.setup() + ``` + + In umbrella apps this may raise an error if you attempt to start multiple instances + of the cache at once. To fix this you can check if the cache has already started: + + ```elixir + # test_helper.exs + if !FactoryEx.FactoryCache.already_started?() do + Cache.start_link([FactoryEx.FactoryCache]) + FactoryEx.FactoryCache.setup() + end + ``` + + ## Requirements + + The following steps are required to detect your factories: + + - Your application must have `factory_ex` as a dependency in `mix.exs`. + - Your application defines a factory module for each schema used as a relational key + - Your module contains the prefix `Factory`, ie. YourApp.Support.Factory.Schemas.Schema. + - Your factory module defines the factory_ex `schema/0` callback function. + + In umbrella applications these requirements are per application. + + ## Prefix + + If your application's factory modules do not use the prefix `Factory` or you want to change + which factory modules are loaded during tests you can configure the module prefix option at + compile time with the following config: + + ```elixir + config :factory_ex, :factory_module_prefix, Factory + ``` + """ + use Cache, + adapter: Cache.ETS, + name: :factory_ex_factory_cache, + sandbox?: false, + opts: [] + + @app :factory_ex + @factory_prefix Application.compile_env(@app, :factory_module_prefix, Factory) + @store :store + + @doc """ + Returns true if the cache is already started + """ + @spec already_started?() :: true | false + def already_started?, do: cache_name() |> :ets.whereis() |> is_reference() + + @doc """ + Returns the result of the `build/1` function of the Factory associated with the given schema. + """ + @spec build_params(module, map) :: map + def build_params(ecto_schema, params \\ %{}), do: fetch_factory!(ecto_schema).build(params) + + @doc """ + Creates the key-value store that maps Ecto.Schema modules to + their factory modules. + + This function aggregates all factory modules from every app that + depends on `factory_ex` and adds them to the store if they define + the `schema/0` callback function. + """ + @spec setup :: :ok + def setup do + [@app | FactoryEx.Utils.apps_that_depend_on(@app)] + |> Enum.reduce(%{}, &aggregate_factory_modules/2) + |> put_store() + end + + defp aggregate_factory_modules(app, acc) do + app + |> FactoryEx.Utils.find_app_modules(@factory_prefix) + |> Enum.reduce(acc, &maybe_put_factory_module/2) + end + + defp maybe_put_factory_module(module, acc) do + if FactoryEx.Utils.ensure_function_exported?(module, :schema, 0) do + Map.put(acc, module.schema(), module) + else + acc + end + end + + defp fetch_factory!(ecto_schema) do + with nil <- Map.get(fetch_store!(), ecto_schema) do + raise """ + Factory not found for schema '#{inspect(ecto_schema)}'. + + This means the factory module does not exist or it was not loaded. + + To fix this error: + + - Add `factory_ex` as a depedency in `mix.exs` of the application that contains + the schema '#{inspect(ecto_schema)}'. In umbrella applications you must add + `factory_ex` as a dependency to each application that contain factory modules. + + - Create a factory for the schema '#{inspect(ecto_schema)}' + + - Add the prefix '#{inspect(@factory_prefix)}' to the factory module name. For example: + YourApp.#{inspect(@factory_prefix)}.Module. + """ + end + end + + defp put_store(state), do: put(@store, state) + + defp fetch_store! do + with :ok <- ensure_already_started!() do + case get(@store) do + {:ok, nil} -> + raise """ + Factories not found! + + Add the following to your test_helper.exs: + + ``` + # test_helper.exs + FactoryEx.FactoryCache.setup() + ``` + + If setup/0 is already called ensure your application meets the requirements. + See the module documentation for FactoryEx.FactoryCache for more information + or `h FactoryEx.FactoryCache` in iex. + """ + + {:ok, state} -> + state + + end + end + end + + defp ensure_already_started! do + if already_started?() do + :ok + else + raise """ + FactoryEx.FactoryCache not started! + + The cache must be started and call setup before it can be used. + You can do this by adding the following to your test_helper.exs file: + + ``` + # test_helpers.exs + Cache.start_link([FactoryEx.FactoryCache]) + FactoryEx.FactoryCache.setup() + ``` + """ + end + end +end diff --git a/lib/factory_ex/utils.ex b/lib/factory_ex/utils.ex index 10daec2..a55508d 100644 --- a/lib/factory_ex/utils.ex +++ b/lib/factory_ex/utils.ex @@ -38,17 +38,17 @@ defmodule FactoryEx.Utils do end def underscore_schema(ecto_schema) do - ecto_schema |> String.replace(".", "") |> Macro.underscore + ecto_schema |> String.replace(".", "") |> Macro.underscore() end def context_schema_name(ecto_schema) do ecto_schema - |> String.split(".") - |> Enum.take(-2) - |> Enum.map_join("_", &Macro.underscore/1) + |> String.split(".") + |> Enum.take(-2) + |> Enum.map_join("_", &Macro.underscore/1) end - @doc """ + @doc """ Converts all string keys to string ### Example @@ -106,4 +106,132 @@ defmodule FactoryEx.Utils do defp camelize_list([h | tail], :upper) do [String.capitalize(h)] ++ camelize_list(tail, :upper) end + + @doc """ + Returns `true` if the second list exists in the first list or `false`. + + ## Example + iex> FactoryEx.Util.List.sublist?([:a, :b, :c], [:b, :c]) + true + """ + def sublist?([], _), do: false + + def sublist?([_ | t] = l1, l2) do + List.starts_with?(l1, l2) or sublist?(t, l2) + end + + @doc """ + Ensure the module with the public function and arity is defined + + Note: `function_exported/3` does not load the module in case it is not loaded. + If the BEAM is running in `interactive` mode there is a chance this module has not + been loaded yet. `Code.ensure_loaded/1` is used to ensure the module is loaded + first. + + Docs: https://hexdocs.pm/elixir/1.12/Kernel.html#function_exported?/3 + """ + def ensure_function_exported?(module, fun, arity) do + case Code.ensure_loaded(module) do + {:module, module} -> + function_exported?(module, fun, arity) + + {:error, reason} -> + raise """ + Code failed to load module `#{inspect(module)}` with reason: #{inspect(reason)}! + Ensure the module name is correct and it exists. + """ + end + end + + @doc """ + Returns all apps that have a dependency. + + ## Examples + + iex> FactoryEx.Utils.apps_that_depend_on(:ecto) + [:factory_ex, :ecto_sql] + """ + def apps_that_depend_on(dep) do + :application.loaded_applications() + |> Enum.reduce([], fn {app, _, _}, acc -> + deps = Application.spec(app)[:applications] + if dep in deps, do: acc ++ [app], else: acc + end) + end + + @doc """ + Returns all application modules that start with a specific prefix. + + ## Examples + + iex> FactoryEx.Utils.find_app_modules(:factory_ex, Factory) + [ + FactoryEx.Support.Factory.Accounts.Label, + FactoryEx.Support.Factory.Accounts.Role, + FactoryEx.Support.Factory.Accounts.Team, + FactoryEx.Support.Factory.Accounts.TeamOrganization, + FactoryEx.Support.Factory.Accounts.User + ] + """ + def find_app_modules(app, prefix) do + case :application.get_key(app, :modules) do + {:ok, modules} -> + prefix = Module.split(prefix) + Enum.filter(modules, &(&1 |> Module.split() |> sublist?(prefix))) + + _ -> raise "modules not found for app #{inspect(app)}." + end + end + + @doc """ + Deep Converts `{count, attrs}` to list of `attrs`. + + ## Examples + + iex> FactoryEx.Utils.expand_count_tuples(%{hello: {2, %{world: {2, %{foo: :bar}}}}}) + %{ + hello: [ + %{world: [%{foo: :bar}, %{foo: :bar}]}, + %{world: [%{foo: :bar}, %{foo: :bar}]} + ] + } + + iex> FactoryEx.Utils.expand_count_tuples(%{hello: [%{foo: "bar"}, {2, %{}}]}) + %{hello: [%{foo: "bar"}, %{}, %{}]} + + iex> FactoryEx.Utils.expand_count_tuples(%{hello: [%{foo: {1, %{}}}, {1, %{qux: {1, %{bux: "hello world"}}}}]}) + %{hello: [%{foo: [%{}]}, %{qux: [%{bux: "hello world"}]}]} + """ + @spec expand_count_tuples(map() | list()) :: map() + def expand_count_tuples(enum) when is_map(enum) or is_list(enum), do: enum |> Enum.map(&transform/1) |> Map.new() + + def expand_count_tuples(val), do: val + + defp expand_many_count_tuples(count, attrs), do: Enum.map(1..count, fn _ -> expand_count_tuples(attrs) end) + + defp transform({_key, %_{} = _struct} = val), do: val + + defp transform({key, attrs}) when is_map(attrs), do: {key, expand_count_tuples(attrs)} + + defp transform({key, many_attrs}) when is_list(many_attrs) do + attrs = + Enum.reduce(many_attrs, [], fn + {count, attrs}, acc -> + acc ++ expand_many_count_tuples(count, attrs) + + attrs, acc -> + acc ++ [expand_count_tuples(attrs)] + + end) + + {key, attrs} + end + + defp transform({key, {count, attrs}}) do + {key, expand_many_count_tuples(count, attrs)} + end + + defp transform(attrs) do + attrs + end end diff --git a/lib/mix/factory_ex_helpers.ex b/lib/mix/factory_ex_helpers.ex index 82a67e5..005ced9 100644 --- a/lib/mix/factory_ex_helpers.ex +++ b/lib/mix/factory_ex_helpers.ex @@ -20,7 +20,6 @@ defmodule Mix.FactoryExHelpers do ])), __STACKTRACE__ end - def schema_primary_key(ecto_schema) do ecto_schema.__schema__(:primary_key) end diff --git a/mix.exs b/mix.exs index 3c345f5..b686610 100644 --- a/mix.exs +++ b/mix.exs @@ -15,9 +15,9 @@ defmodule FactoryEx.MixProject do test_coverage: [tool: ExCoveralls], dialyzer: [ plt_add_apps: [:ex_unit, :mix, :credo, :ecto_sql], - list_unused_filters: true, plt_local_path: "dialyzer", plt_core_path: "dialyzer", + list_unused_filters: true, flags: [:unmatched_returns] ], preferred_cli_env: [ @@ -41,19 +41,17 @@ defmodule FactoryEx.MixProject do defp deps do [ {:ecto, "~> 3.0"}, - {:faker, ">= 0.0.0"}, - - {:nimble_options, "~> 1.0"}, - - {:ecto_sql, "~> 3.0", only: [:test, :dev], optional: true}, - {:postgrex, "~> 0.16", only: [:test, :dev], optional: true}, + {:ecto_sql, "~> 3.0", optional: true}, + {:postgrex, "~> 0.16", optional: true}, + {:nimble_options, "~> 0.4 or ~> 1.0"}, + {:elixir_cache, "~> 0.3"}, + {:faker, ">= 0.0.0", only: [:dev, :test]}, - {:credo, "~> 1.6", only: [:test, :dev], runtime: false}, - {:blitz_credo_checks, "~> 0.1", only: [:test, :dev], runtime: false}, - - {:excoveralls, "~> 0.10", only: :test}, - {:ex_doc, ">= 0.0.0", optional: true, only: :dev}, - {:dialyxir, "~> 1.0", optional: true, only: :test, runtime: false} + {:credo, "~> 1.6", only: [:dev, :test], runtime: false, optional: true}, + {:blitz_credo_checks, "~> 0.1", only: [:dev, :test], runtime: false, optional: true}, + {:excoveralls, "~> 0.10", only: :test, runtime: false, optional: true}, + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false, optional: true}, + {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false, optional: true} ] end @@ -70,15 +68,12 @@ defmodule FactoryEx.MixProject do [ main: "FactoryEx", source_url: "https://github.com/theblitzapp/factory_ex", - groups_for_modules: [ - "General": [ + General: [ FactoryEx, - FactoryEx.SchemaCounter - ], - - "Adapters": [ - FactoryEx.Adapter + FactoryEx.SchemaCounter, + FactoryEx.AssociationBuilder, + FactoryEx.FactoryCache ] ] ] diff --git a/mix.lock b/mix.lock index 86d35ff..d76c72e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,33 +1,34 @@ %{ - "blitz_credo_checks": {:hex, :blitz_credo_checks, "0.1.8", "6f4e27d1db5d0fffd6e47fa36c972fa5efca411b5bb9fc84c65b59adca39dd83", [:mix], [{:credo, "~> 1.4", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "34d4b29a8a5a83ae1d759b0819635f55160f628a9c7a77c5be9507e29360ab3b"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, - "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, - "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "blitz_credo_checks": {:hex, :blitz_credo_checks, "0.1.10", "54ae0aa673101e3edd4f2ab0ee7860f90c3240e515e268546dd9f01109d2b917", [:mix], [{:credo, "~> 1.4", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "b3248dd2c88a6fe907e84ed104e61b863c6451d8755aa609b36d3eb6c7bab9db"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "con_cache": {:hex, :con_cache, "1.1.0", "45c7c6cd6dc216e47636232e8c683734b7fe293221fccd9454fa1757bc685044", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8655f2ae13a1e56c8aef304d250814c7ed929c12810f126fc423ecc8e871593b"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [: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", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.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", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.30.4", "e8395c8e3c007321abb30a334f9f7c0858d80949af298302daf77553468c0c39", [:mix], [{:earmark_parser, "~> 1.4.31", [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", "9a19f0c50ffaa02435668f5242f2b2a61d46b541ebf326884505dfd3dd7af5e4"}, - "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, - "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "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.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{: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", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 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", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, + "elixir_cache": {:hex, :elixir_cache, "0.3.3", "ba90617426e866709596a477b2c433f6dddf17e136f4595a20049f4c5c3105b3", [:mix], [{:con_cache, "~> 1.0", [hex: :con_cache, repo: "hexpm", optional: false]}, {:error_message, "~> 0.3", [hex: :error_message, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:redix, "~> 1.2", [hex: :redix, repo: "hexpm", optional: false]}, {:sandbox_registry, "~> 0.1", [hex: :sandbox_registry, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "6d92c8e2716c4254dad5dd76d4188bcf0ca3f540c706644fbed733ceaf2e710c"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "error_message": {:hex, :error_message, "0.3.2", "01fe015ba807b515ad1d9fcfcbeb49c399374393ef3fecf9de148a471198b300", [:mix], [{:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b3c31bf0c618f7d5b812f581fdedff5829c89439134c724aab26aa958b5ad8fa"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"}, + "faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, + "redix": {:hex, :redix, "1.5.2", "ab854435a663f01ce7b7847f42f5da067eea7a3a10c0a9d560fa52038fd7ab48", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78538d184231a5d6912f20567d76a49d1be7d3fca0e1aaaa20f4df8e1142dcb8"}, + "sandbox_registry": {:hex, :sandbox_registry, "0.1.1", "db6a116bf8e9553111820274701e48d1971185e89b53044f47c2a8f8e74956d7", [:mix], [], "hexpm", "88e12dc6be1bb98a05439e2b1de87db4bc0c043222b4fe4b46b5da7a0cc44948"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, } diff --git a/priv/repo/migrations/20230331151546_create_team_organizations.exs b/priv/repo/migrations/20230331151546_create_team_organizations.exs new file mode 100644 index 0000000..6b7c30b --- /dev/null +++ b/priv/repo/migrations/20230331151546_create_team_organizations.exs @@ -0,0 +1,9 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateTeamOrganizations do + use Ecto.Migration + + def change do + create table(:team_organizations) do + add :name, :string + end + end +end diff --git a/priv/repo/migrations/20230331151636_create_teams.exs b/priv/repo/migrations/20230331151636_create_teams.exs new file mode 100644 index 0000000..fbd0360 --- /dev/null +++ b/priv/repo/migrations/20230331151636_create_teams.exs @@ -0,0 +1,11 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateTeams do + use Ecto.Migration + + def change do + create table(:teams) do + add :name, :string + + add :team_organization_id, references(:team_organizations, on_delete: :delete_all) + end + end +end diff --git a/priv/repo/migrations/20230331151723_create_account_roles.exs b/priv/repo/migrations/20230331151723_create_account_roles.exs new file mode 100644 index 0000000..42b1cad --- /dev/null +++ b/priv/repo/migrations/20230331151723_create_account_roles.exs @@ -0,0 +1,9 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateAccountRoles do + use Ecto.Migration + + def change do + create table(:account_roles) do + add :code, :string + end + end +end diff --git a/priv/repo/migrations/20230331151733_create_account_labels.exs b/priv/repo/migrations/20230331151733_create_account_labels.exs new file mode 100644 index 0000000..abdb07d --- /dev/null +++ b/priv/repo/migrations/20230331151733_create_account_labels.exs @@ -0,0 +1,9 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateAccountLabels do + use Ecto.Migration + + def change do + create table(:account_labels) do + add :label, :text + end + end +end diff --git a/priv/repo/migrations/20230331152052_create_account_users.exs b/priv/repo/migrations/20230331152052_create_account_users.exs new file mode 100644 index 0000000..7d8ff03 --- /dev/null +++ b/priv/repo/migrations/20230331152052_create_account_users.exs @@ -0,0 +1,20 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateAccountUsers do + use Ecto.Migration + + def change do + create table(:account_users) do + add :name, :string + add :age, :integer + add :email, :string + add :email_updated_at, :utc_datetime_usec + add :location, :string + add :gender, :string + add :birthday, :date + + add :role_id, references(:account_roles, on_delete: :delete_all) + add :team_id, references(:teams, on_delete: :delete_all) + + timestamps() + end + end +end diff --git a/priv/repo/migrations/20230331152519_create_account_user_labels.exs b/priv/repo/migrations/20230331152519_create_account_user_labels.exs new file mode 100644 index 0000000..7821fe5 --- /dev/null +++ b/priv/repo/migrations/20230331152519_create_account_user_labels.exs @@ -0,0 +1,10 @@ +defmodule FactoryEx.Support.Repo.Migrations.CreateAccountUserLabels do + use Ecto.Migration + + def change do + create table(:account_user_labels) do + add :user_id, references(:account_users, on_delete: :delete_all) + add :label_id, references(:account_labels, on_delete: :delete_all) + end + end +end diff --git a/test/factory_ex_test.exs b/test/factory_ex_test.exs index 2cc7ddb..583ad91 100644 --- a/test/factory_ex_test.exs +++ b/test/factory_ex_test.exs @@ -1,5 +1,5 @@ defmodule FactoryExTest do - use ExUnit.Case + use FactoryEx.DataCase doctest FactoryEx defmodule MyRepo do @@ -14,9 +14,9 @@ defmodule FactoryExTest do import Ecto.Changeset schema "my_schmeas" do - field :foo, :integer - field :bar, :integer - field :foo_bar_baz, :integer + field(:foo, :integer) + field(:bar, :integer) + field(:foo_bar_baz, :integer) end @required_params [:foo, :bar] @@ -24,8 +24,8 @@ defmodule FactoryExTest do def changeset(%__MODULE__{} = user, attrs \\ %{}) do user - |> cast(attrs, @available_params) - |> validate_required(@required_params) + |> cast(attrs, @available_params) + |> validate_required(@required_params) end end @@ -64,17 +64,97 @@ defmodule FactoryExTest do test "can generate a factory with string keys" do assert %{ - "foo" => 21, - "bar" => 42, - "foo_bar_baz" => 11 - } = FactoryEx.build_params(TestFactory, %{}, keys: :string) + "foo" => 21, + "bar" => 42, + "foo_bar_baz" => 11 + } = FactoryEx.build_params(TestFactory, %{}, keys: :string) end test "can generate a factory with camelCase keys" do assert %{ - "foo" => 21, - "bar" => 42, - "fooBarBaz" => 11 - } = FactoryEx.build_params(TestFactory, %{}, keys: :camel_string) + "foo" => 21, + "bar" => 42, + "fooBarBaz" => 11 + } = FactoryEx.build_params(TestFactory, %{}, keys: :camel_string) + end + + test "can build ecto schema associations with changeset validation" do + assert %FactoryEx.Support.Schema.Accounts.TeamOrganization{ + teams: [ + %{ + users: [ + %FactoryEx.Support.Schema.Accounts.User{ + role: %FactoryEx.Support.Schema.Accounts.Role{}, + labels: [%FactoryEx.Support.Schema.Accounts.Label{}] + } + ] + } + ] + } = + FactoryEx.build( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{}, + relational: [teams: [users: [:role, :labels]]] + ) + end + + test "can build ecto schema associations without changeset validation" do + assert %FactoryEx.Support.Schema.Accounts.TeamOrganization{ + teams: [ + %{ + users: [ + %FactoryEx.Support.Schema.Accounts.User{ + role: %FactoryEx.Support.Schema.Accounts.Role{}, + labels: [%FactoryEx.Support.Schema.Accounts.Label{}] + } + ] + } + ] + } = + FactoryEx.build( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{}, + relational: [teams: [users: [:role, :labels]]], + validate: false + ) + end + + test "can insert" do + assert %FactoryEx.Support.Schema.Accounts.User{} = + FactoryEx.insert!(FactoryEx.Support.Factory.Accounts.User) + + assert %FactoryEx.Support.Schema.Accounts.TeamOrganization{ + teams: [ + %{ + users: [ + %FactoryEx.Support.Schema.Accounts.User{ + role: %FactoryEx.Support.Schema.Accounts.Role{}, + labels: [%FactoryEx.Support.Schema.Accounts.Label{}] + } + ] + } + ] + } = + FactoryEx.insert!( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{}, + relational: [teams: [users: [:role, :labels]]] + ) + + assert %FactoryEx.Support.Schema.Accounts.TeamOrganization{ + teams: [ + %{ + users: [ + %FactoryEx.Support.Schema.Accounts.User{}, + %FactoryEx.Support.Schema.Accounts.User{} + ] + } + ] + } = + FactoryEx.insert!( + FactoryEx.Support.Factory.Accounts.TeamOrganization, + %{teams: [%{users: {2, %{}}}]}, + relational: [teams: [:users]] + ) end end diff --git a/test/support/accounts/role.ex b/test/support/accounts/role.ex deleted file mode 100644 index 0b1eee3..0000000 --- a/test/support/accounts/role.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule FactoryEx.Support.Accounts.Role do - @moduledoc false - - use Ecto.Schema - - import Ecto.Changeset, only: [cast: 3, validate_required: 2] - - alias FactoryEx.Support.Accounts.{User, Role} - - schema "account_roles" do - field :code, :string - - has_many :users, User - end - - @required_params [:code] - - def changeset(%Role{} = user, attrs \\ %{}) do - user - |> cast(attrs, @required_params) - |> validate_required(@required_params) - end -end - - diff --git a/test/support/accounts/team.ex b/test/support/accounts/team.ex deleted file mode 100644 index 4a79269..0000000 --- a/test/support/accounts/team.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule FactoryEx.Support.Accounts.Team do - @moduledoc false - - use Ecto.Schema - - import Ecto.Changeset, only: [cast: 3, validate_required: 2] - - alias FactoryEx.Support.Accounts.{User, Team, TeamOrganization} - - schema "teams" do - field :name, :string - - has_many :users, User - belongs_to :team_organization, TeamOrganization - end - - @required_params [:name] - - def changeset(%Team{} = user, attrs \\ %{}) do - user - |> cast(attrs, @required_params) - |> validate_required(@required_params) - end -end - - diff --git a/test/support/accounts/team_organization.ex b/test/support/accounts/team_organization.ex deleted file mode 100644 index 4670c5f..0000000 --- a/test/support/accounts/team_organization.ex +++ /dev/null @@ -1,23 +0,0 @@ -defmodule FactoryEx.Support.Accounts.TeamOrganization do - @moduledoc false - - use Ecto.Schema - - import Ecto.Changeset, only: [cast: 3, validate_required: 2] - - alias FactoryEx.Support.Accounts.{Team, TeamOrganization} - - schema "team_organizations" do - field :name, :string - - has_many :team, Team - end - - @required_params [:name] - - def changeset(%TeamOrganization{} = user, attrs \\ %{}) do - user - |> cast(attrs, @required_params) - |> validate_required(@required_params) - end -end diff --git a/test/support/accounts/user.ex b/test/support/accounts/user.ex deleted file mode 100644 index 3a8d313..0000000 --- a/test/support/accounts/user.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule FactoryEx.Support.Accounts.User do - @moduledoc false - - use Ecto.Schema - - import Ecto.Changeset, only: [cast: 3, validate_required: 2, update_change: 3, validate_length: 3] - - alias FactoryEx.Support.Accounts.{User, Role, Team, Label} - - @username_min 3 - @username_max 15 - @email_max_length 255 - - schema "account_users" do - field :name, :string - field :email, :string - field :email_updated_at, :utc_datetime_usec - field :location, :string - field :gender, :string - field :birthday, :date - - belongs_to :role, Role - belongs_to :team, Team - - many_to_many :labels, Label, join_through: "account_user_labels" - - timestamps(type: :utc_datetime_usec) - end - - @required_params [:email] - @available_params [ - :email_updated_at, - :name, - :birthday, - :location, - :gender | @required_params - ] - - def changeset(%User{} = user, attrs \\ %{}) do - user - |> cast(attrs, @available_params) - |> validate_required(@required_params) - |> update_change(:location, &String.upcase/1) - |> validate_length(:name, min: @username_min, max: @username_max) - |> validate_length(:email, max: @email_max_length) - end -end diff --git a/test/support/datacase.ex b/test/support/datacase.ex new file mode 100644 index 0000000..7d6c70a --- /dev/null +++ b/test/support/datacase.ex @@ -0,0 +1,48 @@ +defmodule FactoryEx.DataCase do + @moduledoc false + use ExUnit.CaseTemplate + + using do + quote do + alias FactoryEx.Support.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import FactoryEx.DataCase + end + end + + setup tags do + setup_sandbox(tags) + :ok + end + + def setup_sandbox(tags) do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(FactoryEx.Support.Repo) + + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(FactoryEx.Support.Repo, {:shared, self()}) + end + end + + @doc """ + A helper that transforms changeset errors into a map of messages. + + assert {:error, changeset} = Accounts.create_user(%{password: "short"}) + assert "password is too short" in errors_on(changeset).password + assert %{password: ["password is too short"]} = errors_on(changeset) + + """ + def errors_on(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> + Regex.replace(~r"%{(\w+)}", message, fn _, key -> + key_string_to_atom = String.to_existing_atom(key) + + opts + |> Keyword.get(key_string_to_atom, key) + |> to_string() + end) + end) + end +end diff --git a/test/support/factory/accounts/label.ex b/test/support/factory/accounts/label.ex new file mode 100644 index 0000000..1e650bb --- /dev/null +++ b/test/support/factory/accounts/label.ex @@ -0,0 +1,15 @@ +defmodule FactoryEx.Support.Factory.Accounts.Label do + @moduledoc false + @behaviour FactoryEx + + @impl FactoryEx + def schema, do: FactoryEx.Support.Schema.Accounts.Label + + @impl FactoryEx + def repo, do: FactoryEx.Support.Repo + + @impl FactoryEx + def build(attrs \\ %{}) do + Map.merge(%{label: Faker.Lorem.word()}, attrs) + end +end diff --git a/test/support/factory/accounts/role.ex b/test/support/factory/accounts/role.ex new file mode 100644 index 0000000..b4c2eb4 --- /dev/null +++ b/test/support/factory/accounts/role.ex @@ -0,0 +1,15 @@ +defmodule FactoryEx.Support.Factory.Accounts.Role do + @moduledoc false + @behaviour FactoryEx + + @impl FactoryEx + def schema, do: FactoryEx.Support.Schema.Accounts.Role + + @impl FactoryEx + def repo, do: FactoryEx.Support.Repo + + @impl FactoryEx + def build(attrs \\ %{}) do + Map.merge(%{code: Faker.Team.name()}, attrs) + end +end diff --git a/test/support/factory/accounts/team.ex b/test/support/factory/accounts/team.ex new file mode 100644 index 0000000..5e476e4 --- /dev/null +++ b/test/support/factory/accounts/team.ex @@ -0,0 +1,15 @@ +defmodule FactoryEx.Support.Factory.Accounts.Team do + @moduledoc false + @behaviour FactoryEx + + @impl FactoryEx + def schema, do: FactoryEx.Support.Schema.Accounts.Team + + @impl FactoryEx + def repo, do: FactoryEx.Support.Repo + + @impl FactoryEx + def build(attrs \\ %{}) do + Map.merge(%{name: Faker.Company.name()}, attrs) + end +end diff --git a/test/support/factory/accounts/team_organization.ex b/test/support/factory/accounts/team_organization.ex new file mode 100644 index 0000000..1d50614 --- /dev/null +++ b/test/support/factory/accounts/team_organization.ex @@ -0,0 +1,15 @@ +defmodule FactoryEx.Support.Factory.Accounts.TeamOrganization do + @moduledoc false + @behaviour FactoryEx + + @impl FactoryEx + def schema, do: FactoryEx.Support.Schema.Accounts.TeamOrganization + + @impl FactoryEx + def repo, do: FactoryEx.Support.Repo + + @impl FactoryEx + def build(attrs \\ %{}) do + Map.merge(%{name: Faker.Company.name()}, attrs) + end +end diff --git a/test/support/factory/accounts/user.ex b/test/support/factory/accounts/user.ex new file mode 100644 index 0000000..97b0461 --- /dev/null +++ b/test/support/factory/accounts/user.ex @@ -0,0 +1,21 @@ +defmodule FactoryEx.Support.Factory.Accounts.User do + @moduledoc false + @behaviour FactoryEx + + @impl FactoryEx + def schema, do: FactoryEx.Support.Schema.Accounts.User + + @impl FactoryEx + def repo, do: FactoryEx.Support.Repo + + @impl FactoryEx + def build(attrs \\ %{}) do + Map.merge(%{ + name: Faker.Person.name(), + email: Faker.Internet.email(), + gender: Enum.random(~w[male female]), + location: "someplace", + birthday: ~D[1992-10-04] + }, attrs) + end +end diff --git a/test/support/accounts/label.ex b/test/support/schema/accounts/label.ex similarity index 53% rename from test/support/accounts/label.ex rename to test/support/schema/accounts/label.ex index fed707e..80e1942 100644 --- a/test/support/accounts/label.ex +++ b/test/support/schema/accounts/label.ex @@ -1,24 +1,21 @@ -defmodule FactoryEx.Support.Accounts.Label do +defmodule FactoryEx.Support.Schema.Accounts.Label do @moduledoc false use Ecto.Schema import Ecto.Changeset, only: [cast: 3, validate_required: 2] - alias FactoryEx.Support.Accounts.Label + alias FactoryEx.Support.Schema.Accounts.Label schema "account_labels" do - field :label, :string + field(:label, :string) end @required_params [:label] def changeset(%Label{} = user, attrs \\ %{}) do user - |> cast(attrs, @required_params) - |> validate_required(@required_params) + |> cast(attrs, @required_params) + |> validate_required(@required_params) end end - - - diff --git a/test/support/schema/accounts/role.ex b/test/support/schema/accounts/role.ex new file mode 100644 index 0000000..605209f --- /dev/null +++ b/test/support/schema/accounts/role.ex @@ -0,0 +1,23 @@ +defmodule FactoryEx.Support.Schema.Accounts.Role do + @moduledoc false + + use Ecto.Schema + + import Ecto.Changeset, only: [cast: 3, validate_required: 2] + + alias FactoryEx.Support.Schema.Accounts.{Role, User} + + schema "account_roles" do + field(:code, :string) + + has_many(:users, User) + end + + @required_params [:code] + + def changeset(%Role{} = user, attrs \\ %{}) do + user + |> cast(attrs, @required_params) + |> validate_required(@required_params) + end +end diff --git a/test/support/schema/accounts/team.ex b/test/support/schema/accounts/team.ex new file mode 100644 index 0000000..d85d857 --- /dev/null +++ b/test/support/schema/accounts/team.ex @@ -0,0 +1,24 @@ +defmodule FactoryEx.Support.Schema.Accounts.Team do + @moduledoc false + + use Ecto.Schema + + import Ecto.Changeset, only: [cast: 3, validate_required: 2] + + alias FactoryEx.Support.Schema.Accounts.{Team, TeamOrganization, User} + + schema "teams" do + field(:name, :string) + + has_many(:users, User) + belongs_to(:team_organization, TeamOrganization) + end + + @required_params [:name] + + def changeset(%Team{} = user, attrs \\ %{}) do + user + |> cast(attrs, @required_params) + |> validate_required(@required_params) + end +end diff --git a/test/support/schema/accounts/team_organization.ex b/test/support/schema/accounts/team_organization.ex new file mode 100644 index 0000000..3662e4c --- /dev/null +++ b/test/support/schema/accounts/team_organization.ex @@ -0,0 +1,23 @@ +defmodule FactoryEx.Support.Schema.Accounts.TeamOrganization do + @moduledoc false + + use Ecto.Schema + + import Ecto.Changeset, only: [cast: 3, validate_required: 2] + + alias FactoryEx.Support.Schema.Accounts.{Team, TeamOrganization} + + schema "team_organizations" do + field(:name, :string) + + has_many(:teams, Team) + end + + @required_params [:name] + + def changeset(%TeamOrganization{} = user, attrs \\ %{}) do + user + |> cast(attrs, @required_params) + |> validate_required(@required_params) + end +end diff --git a/test/support/schema/accounts/user.ex b/test/support/schema/accounts/user.ex new file mode 100644 index 0000000..c00b6be --- /dev/null +++ b/test/support/schema/accounts/user.ex @@ -0,0 +1,48 @@ +defmodule FactoryEx.Support.Schema.Accounts.User do + @moduledoc false + + use Ecto.Schema + + import Ecto.Changeset, + only: [cast: 3, validate_required: 2, update_change: 3, validate_length: 3] + + alias FactoryEx.Support.Schema.Accounts.{Label, Role, Team, User} + + @username_min 3 + @username_max 30 + @email_max_length 255 + + schema "account_users" do + field(:name, :string) + field(:email, :string) + field(:email_updated_at, :utc_datetime_usec) + field(:location, :string) + field(:gender, :string) + field(:birthday, :date) + + belongs_to(:role, Role) + belongs_to(:team, Team) + + many_to_many(:labels, Label, join_through: "account_user_labels") + + timestamps(type: :utc_datetime_usec) + end + + @required_params [:email] + @available_params [ + :email_updated_at, + :name, + :birthday, + :location, + :gender | @required_params + ] + + def changeset(%User{} = user, attrs \\ %{}) do + user + |> cast(attrs, @available_params) + |> validate_required(@required_params) + |> update_change(:location, &String.upcase/1) + |> validate_length(:name, min: @username_min, max: @username_max) + |> validate_length(:email, max: @email_max_length) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..29d6138 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,8 @@ ExUnit.start() +Faker.start() + +{:ok, _} = :application.ensure_all_started([:postgrex]) +{:ok, _} = FactoryEx.Support.Repo.start_link() + +Cache.start_link([FactoryEx.FactoryCache]) +FactoryEx.FactoryCache.setup()