Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 75 additions & 10 deletions lib/ex_machina.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ defmodule ExMachina do
use Application

alias ExMachina.UndefinedFactoryError
alias ExMachina.UndefinedGeneratorError

@callback generate(generator_name :: atom) :: any
@callback generate(generator_name :: atom, attrs :: keyword | map) :: any
@callback build(factory_name :: atom) :: any
@callback build(factory_name :: atom, attrs :: keyword | map) :: any
@callback build_list(number_of_records :: integer, factory_name :: atom) :: list
Expand Down Expand Up @@ -38,6 +41,12 @@ defmodule ExMachina do

alias ExMachina.UndefinedFactoryError

if Code.ensure_loaded?(StreamData) do
def generate(generator_name, attrs \\ %{}) do
ExMachina.generate(__MODULE__, generator_name, attrs)
end
end

def build(factory_name, attrs \\ %{}) do
ExMachina.build(__MODULE__, factory_name, attrs)
end
Expand Down Expand Up @@ -217,7 +226,8 @@ defmodule ExMachina do
def build(module, factory_name, attrs \\ %{}) do
attrs = Enum.into(attrs, %{})

function_name = build_function_name(factory_name)
function_name = build_factory_name(factory_name)
generator_name = build_generator_name(factory_name)

cond do
factory_accepting_attributes_defined?(module, function_name) ->
Expand All @@ -229,19 +239,49 @@ defmodule ExMachina do
|> merge_attributes(attrs)
|> evaluate_lazy_attributes()

Code.ensure_loaded?(StreamData) and
(generator_accepting_attributes_defined?(module, generator_name) or
generator_without_attributes_defined?(module, generator_name)) ->
module |> generate(factory_name, attrs) |> Enum.take(1) |> hd

true ->
raise UndefinedFactoryError, factory_name
end
end

defp build_function_name(factory_name) do
factory_name
if Code.ensure_loaded?(StreamData) do
def generate(module, generator_name, attrs \\ %{}) do
attrs = Enum.into(attrs, %{})

function_name = build_generator_name(generator_name)

cond do
generator_accepting_attributes_defined?(module, function_name) ->
apply(module, function_name, [attrs])

generator_without_attributes_defined?(module, function_name) ->
module
|> apply(function_name, [])
|> StreamData.map(&merge_attributes(&1, attrs))

true ->
raise UndefinedGeneratorError, generator_name
end
end
end

defp build_function_name(name, postfix) do
name
|> Atom.to_string()
|> Kernel.<>("_factory")
|> then(&"#{&1}_#{postfix}")
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
|> String.to_atom()
end

defp build_factory_name(factory_name) do
build_function_name(factory_name, "factory")
end

defp factory_accepting_attributes_defined?(module, function_name) do
Code.ensure_loaded?(module) && function_exported?(module, function_name, 1)
end
Expand All @@ -250,6 +290,20 @@ defmodule ExMachina do
Code.ensure_loaded?(module) && function_exported?(module, function_name, 0)
end

if Code.ensure_loaded?(StreamData) do
defp build_generator_name(factory_name) do
build_function_name(factory_name, "generator")
end

defp generator_accepting_attributes_defined?(module, function_name) do
Code.ensure_loaded?(module) && function_exported?(module, function_name, 1)
end

defp generator_without_attributes_defined?(module, function_name) do
Code.ensure_loaded?(module) && function_exported?(module, function_name, 0)
end
end

@doc """
Helper function to merge attributes into a factory that could be either a map
or a struct.
Expand Down Expand Up @@ -347,12 +401,23 @@ defmodule ExMachina do
build_list(3, :user)
"""
def build_list(module, number_of_records, factory_name, attrs \\ %{}) do
stream =
Stream.repeatedly(fn ->
ExMachina.build(module, factory_name, attrs)
end)

Enum.take(stream, number_of_records)
function_name = build_factory_name(factory_name)
generator_name = build_generator_name(factory_name)

if not factory_accepting_attributes_defined?(module, function_name) and
not factory_without_attributes_defined?(module, function_name) and
Code.ensure_loaded?(StreamData) and
(generator_accepting_attributes_defined?(module, generator_name) or
generator_without_attributes_defined?(module, generator_name)) do
module |> generate(factory_name, attrs) |> Enum.take(number_of_records)
else
stream =
Stream.repeatedly(fn ->
ExMachina.build(module, factory_name, attrs)
end)

Enum.take(stream, number_of_records)
end
end

defmacro __before_compile__(_env) do
Expand Down
23 changes: 23 additions & 0 deletions lib/ex_machina/undefined_generator_error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
if Code.ensure_loaded?(StreamData) do
defmodule ExMachina.UndefinedGeneratorError do
@moduledoc """
Error raised when trying to build or create a generator that is undefined.
"""

defexception [:message]

def exception(generator_name) do
message = """
No generator defined for #{inspect(generator_name)}.

Please check for typos or define your generator:

def #{generator_name}_generator do
...
end
"""

%__MODULE__{message: message}
end
end
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ defmodule ExMachina.Mixfile do
[
{:ecto, "~> 2.2 or ~> 3.0", optional: true},
{:ecto_sql, "~> 3.0", optional: true},
{:stream_data, "~> 1.2", optional: true},

# Dev and Test dependencies
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [: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", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
"stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
}