-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
1,154 additions
and
299 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
erlang 25.1 | ||
elixir 1.13.4-otp-25 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
import Config | ||
|
||
config :factory_ex, FactoryExTest.MyRepo, | ||
username: System.get_env("POSTGRES_USER") || "postgres", | ||
password: System.get_env("POSTGRES_PASSWORD") || "postgres", | ||
config :factory_ex, ecto_repos: [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: String.to_integer(System.get_env("POSTGRES_POOL_SIZE", "10")) | ||
pool_size: 5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
defmodule FactoryEx.Application do | ||
@moduledoc false | ||
|
||
use Application | ||
|
||
@impl true | ||
def start(_, _) do | ||
FactoryEx.SchemaCounter.start() | ||
|
||
children = [ | ||
FactoryEx.FactoryStore | ||
] | ||
|
||
opts = [strategy: :one_for_one, name: FactoryEx.Supervisor] | ||
Supervisor.start_link(children, opts) | ||
end | ||
|
||
def apps_that_depend_on(dep) do | ||
:application.loaded_applications() | ||
|> Enum.reduce([], fn {app, _, _}, acc -> | ||
deps = Application.spec(app)[:applications] | ||
(dep in deps && acc ++ [app]) || acc | ||
end) | ||
end | ||
|
||
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() |> FactoryEx.Utils.sublist?(prefix))) | ||
|
||
_ -> raise "modules not found for app #{inspect(app)}." | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
defmodule FactoryEx.AssociationBuilder do | ||
@moduledoc """ | ||
This module implements the api for auto generating Ecto Associations with factories. | ||
To use this api you must pass keys (which can be type of `:list` or `:keyword_list`) to the | ||
`relational` option. These keys are used by the `build_params/3` to find the appropriate | ||
Factory for an Ecto Schema and invoke the `build_params/3` callback function. This allows you | ||
to build any relational data structure declaratively. | ||
In the following section we will explain how `build_params/3` works with relational keys: | ||
- For each relational key, we try to fetch the the ecto schema's associations. If the field does | ||
not exist for the given schema an error is raised otherwise the association is used to create | ||
one or many params for the field based on the association's cardinality. | ||
- If the `owner_key` of the association's schema field is not set, the factory's `build/1` | ||
function will be invoked with the field's existing value. If the owner key is set the | ||
field is skipped and the existing value will be kept. Any existing paramaters not passed | ||
as a relational key will be kept. If this behaviour is not desired you can set the | ||
`check_owner_field?` option to `false` and the parameters will be generated when the | ||
owner key is set. | ||
## Examples | ||
# Create params for a one-to-one relationship | ||
FactoryEx.AssociationBuilder.build_params( | ||
FactoryEx.Support.Factory.Accounts.User, | ||
%{pre_existing_params: "hello world!"}, | ||
relational: [:role, :team] | ||
) | ||
%{ | ||
pre_existing_params: "hello world!", | ||
role: %{code: "Utah cats"}, | ||
team: %{name: "Macejkovic Group"} | ||
} | ||
# Create params for a one-to-many relationship | ||
FactoryEx.AssociationBuilder.build_params( | ||
FactoryEx.Support.Factory.Accounts.TeamOrganization, | ||
%{teams: [%{}, %{}]}, | ||
relational: [:teams] | ||
) | ||
%{teams: [%{name: "Lindgren-Zemlak"}, %{name: "Kutch Group"}]} | ||
# Create deep relational structure and override specific field values | ||
FactoryEx.AssociationBuilder.build_params( | ||
FactoryEx.Support.Factory.Accounts.TeamOrganization, | ||
%{teams: [%{name: "team name goes here", users: [%{name: "first user name"}, %{}]}]}, | ||
relational: [teams: [users: [:labels, :role]]] | ||
) | ||
%{ | ||
teams: [ | ||
%{ | ||
name: "team name goes here", | ||
users: [ | ||
%{ | ||
birthday: ~D[1992-10-04], | ||
email: "[email protected]", | ||
gender: "male", | ||
labels: [%{label: "expedita"}], | ||
location: "someplace", | ||
name: "first user name", | ||
role: %{code: "Iowa penguins"} | ||
}, | ||
%{ | ||
birthday: ~D[1992-10-04], | ||
email: "[email protected]", | ||
gender: "male", | ||
labels: [%{label: "exercitationem"}], | ||
location: "someplace", | ||
name: "Name Zulauf Jr.", | ||
role: %{code: "New Hampshire dwarves"} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
""" | ||
|
||
@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, []) | ||
check_owner_key? = Keyword.get(options, :check_owner_key?, true) | ||
|
||
convert_fields_to_params(schema, params, assoc_fields, check_owner_key?) | ||
end | ||
|
||
defp convert_fields_to_params(schema, params, assoc_fields, check_owner_key?) do | ||
Enum.reduce(assoc_fields, params, &create_schema_params(schema, &1, &2, check_owner_key?)) | ||
end | ||
|
||
defp create_schema_params(schema, {field, assoc_fields}, params, check_owner_key?) do | ||
schema | ||
|> fetch_assoc!(field) | ||
|> create_one_or_many_params(params, field, assoc_fields, check_owner_key?) | ||
|> case do | ||
nil -> params | ||
assoc_params -> Map.put(params, field, assoc_params) | ||
end | ||
end | ||
|
||
defp create_schema_params(schema, field, params, check_owner_key?) do | ||
create_schema_params(schema, {field, []}, params, check_owner_key?) | ||
end | ||
|
||
defp create_one_or_many_params( | ||
%{cardinality: :many, queryable: queryable} = assoc, | ||
params, | ||
field, | ||
assoc_fields, | ||
check_owner_key? | ||
) do | ||
if check_owner_key? and owner_key_is_set?(assoc, params) do | ||
Map.get(params, field) | ||
else | ||
params = Map.get(params, field, [%{}]) | ||
Enum.map(params, fn params -> factory_build(queryable, params, assoc_fields, check_owner_key?) end) | ||
end | ||
end | ||
|
||
defp create_one_or_many_params( | ||
%{cardinality: :one, queryable: queryable} = assoc, | ||
params, | ||
field, | ||
assoc_fields, | ||
check_owner_key? | ||
) do | ||
if check_owner_key? and owner_key_is_set?(assoc, params) do | ||
Map.get(params, field) | ||
else | ||
params = Map.get(params, field, %{}) | ||
factory_build(queryable, params, assoc_fields, check_owner_key?) | ||
end | ||
end | ||
|
||
defp owner_key_is_set?(assoc, params) do | ||
case Map.get(params, assoc.owner_key) do | ||
nil -> false | ||
_ -> true | ||
end | ||
end | ||
|
||
defp factory_build(queryable, params, assoc_fields, check_owner_key?) do | ||
parent = FactoryEx.FactoryStore.build_params(queryable, params) | ||
assoc = convert_fields_to_params(queryable, params, assoc_fields, check_owner_key?) | ||
|
||
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 |
Oops, something went wrong.