Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional callbacks for functions with default params #319

Merged
merged 1 commit into from
Dec 21, 2018
Merged
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
42 changes: 25 additions & 17 deletions lib/ex_machina.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@ defmodule ExMachina do
defexception [:message]

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

Please check for typos or define your factory:
Please check for typos or define your factory:

def #{factory_name}_factory do
...
end
"""

def #{factory_name}_factory do
...
end
"""
%UndefinedFactoryError{message: message}
end
end

use Application

@doc false
def start(_type, _args), do: ExMachina.Sequence.start_link
def start(_type, _args), do: ExMachina.Sequence.start_link()

defmacro __using__(_opts) do
quote do
Expand Down Expand Up @@ -70,7 +70,7 @@ defmodule ExMachina do
raise_function_replaced_error("create_list/3", "insert_list/3")
end

@spec raise_function_replaced_error(String.t, String.t) :: no_return
@spec raise_function_replaced_error(String.t(), String.t()) :: no_return
defp raise_function_replaced_error(old_function, new_function) do
raise """
#{old_function} has been removed.
Expand All @@ -82,12 +82,12 @@ defmodule ExMachina do
"""
end

defoverridable [create: 1, create: 2, create_pair: 2, create_list: 3]
defoverridable create: 1, create: 2, create_pair: 2, create_list: 3
end
end

@doc """
Shortcut for creating unique string values.
Shortcut for creating unique string values.

This is automatically imported into a model factory when you `use ExMachina`.

Expand All @@ -114,7 +114,7 @@ defmodule ExMachina do
}
end
"""
@spec sequence(String.t) :: String.t
@spec sequence(String.t()) :: String.t()

def sequence(name), do: ExMachina.Sequence.next(name)

Expand Down Expand Up @@ -157,15 +157,20 @@ defmodule ExMachina do
%{name: "John Doe", admin: false}
end

# Returns %{name: "John Doe", admin: false}
build(:user)

# Returns %{name: "John Doe", admin: true}
build(:user, admin: true)
"""
@callback build(factory_name :: atom) :: any
@callback build(factory_name :: atom, attrs :: keyword | map) :: any

@doc false
def build(module, factory_name, attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
function_name = build_function_name(factory_name)

if Code.ensure_loaded?(module) && function_exported?(module, function_name, 0) do
apply(module, function_name, []) |> do_merge(attrs)
else
Expand All @@ -175,9 +180,9 @@ defmodule ExMachina do

defp build_function_name(factory_name) do
factory_name
|> Atom.to_string
|> Atom.to_string()
|> Kernel.<>("_factory")
|> String.to_atom
|> String.to_atom()
end

defp do_merge(%{__struct__: _} = record, attrs), do: struct!(record, attrs)
Expand All @@ -193,6 +198,7 @@ defmodule ExMachina do
# Returns a list of 2 users
build_pair(:user)
"""
@callback build_pair(factory_name :: atom) :: list
@callback build_pair(factory_name :: atom, attrs :: keyword | map) :: list

@doc false
Expand All @@ -208,7 +214,9 @@ defmodule ExMachina do
# Returns a list of 3 users
build_list(3, :user)
"""
@callback build_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) :: list
@callback build_list(number_of_records :: integer, factory_name :: atom) :: list
@callback build_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) ::
list

@doc false
def build_list(module, number_of_records, factory_name, attrs \\ %{}) do
Expand All @@ -222,7 +230,7 @@ defmodule ExMachina do
quote do
@doc "Raises a helpful error if no factory is defined."
@spec factory(any) :: no_return
def factory(factory_name), do: raise UndefinedFactoryError, factory_name
def factory(factory_name), do: raise(UndefinedFactoryError, factory_name)
end
end
end
78 changes: 54 additions & 24 deletions lib/ex_machina/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule ExMachina.Ecto do
"""
defmacro __using__(opts) do
verify_ecto_dep()

if repo = Keyword.get(opts, :repo) do
quote do
use ExMachina
Expand All @@ -37,18 +38,18 @@ defmodule ExMachina.Ecto do
end
else
raise ArgumentError,
"""
expected :repo to be given as an option. Example:
"""
expected :repo to be given as an option. Example:

use ExMachina.Ecto, repo: MyApp.Repo
"""
use ExMachina.Ecto, repo: MyApp.Repo
"""
end
end

defp verify_ecto_dep do
unless Code.ensure_loaded?(Ecto) do
raise "You tried to use ExMachina.Ecto, but the Ecto module is not loaded. " <>
"Please add ecto to your dependencies."
"Please add ecto to your dependencies."
end
end

Expand All @@ -57,21 +58,28 @@ defmodule ExMachina.Ecto do

The arguments are the same as `c:ExMachina.build/2`.
"""
@callback insert(factory_name :: atom) :: any
@callback insert(factory_name :: atom, attrs :: keyword | map) :: any

@doc """
Builds two factories and inserts them into the database.

The arguments are the same as `c:ExMachina.build_pair/2`.
"""
@callback insert_pair(factory_name :: atom) :: list
@callback insert_pair(factory_name :: atom, attrs :: keyword | map) :: list

@doc """
Builds many factories and inserts them into the database.

The arguments are the same as `c:ExMachina.build_list/3`.
"""
@callback insert_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) :: list
@callback insert_list(number_of_records :: integer, factory_name :: atom) :: list
@callback insert_list(
number_of_records :: integer,
factory_name :: atom,
attrs :: keyword | map
) :: list

@doc """
Builds a factory and returns only its fields.
Expand All @@ -96,7 +104,11 @@ defmodule ExMachina.Ecto do

# Returns %{name: "John Doe", admin: true}
params_for(:user, admin: true)

# Returns %{name: "John Doe", admin: false}
params_for(:user)
"""
@callback params_for(factory_name :: atom) :: %{optional(atom) => any}
@callback params_for(factory_name :: atom, attrs :: keyword | map) :: %{optional(atom) => any}

@doc false
Expand All @@ -121,7 +133,10 @@ defmodule ExMachina.Ecto do
# Returns %{"name" => "John Doe", "admin" => true}
string_params_for(:user, admin: true)
"""
@callback string_params_for(factory_name :: atom, attrs :: keyword | map) :: %{optional(String.t) => any}
@callback string_params_for(factory_name :: atom) :: %{optional(String.t()) => any}
@callback string_params_for(factory_name :: atom, attrs :: keyword | map) :: %{
optional(String.t()) => any
}

@doc false
def string_params_for(module, factory_name, attrs \\ %{}) do
Expand All @@ -145,7 +160,10 @@ defmodule ExMachina.Ecto do
# Inserts an author and returns %{title: "An Awesome Article", author_id: 12}
params_with_assocs(:article)
"""
@callback params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{optional(atom) => any}
@callback params_with_assocs(factory_name :: atom) :: %{optional(atom) => any}
@callback params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{
optional(atom) => any
}

@doc false
def params_with_assocs(module, factory_name, attrs \\ %{}) do
Expand All @@ -171,7 +189,10 @@ defmodule ExMachina.Ecto do
# Inserts an author and returns %{"title" => "An Awesome Article", "author_id" => 12}
string_params_with_assocs(:article)
"""
@callback string_params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{optional(String.t) => any}
@callback string_params_with_assocs(factory_name :: atom) :: %{optional(String.t()) => any}
@callback string_params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{
optional(String.t()) => any
}

@doc false
def string_params_with_assocs(module, factory_name, attrs \\ %{}) do
Expand All @@ -180,7 +201,6 @@ defmodule ExMachina.Ecto do
|> convert_atom_keys_to_strings
end


defp recursively_strip(record = %{__struct__: _}) do
record
|> set_persisted_belongs_to_ids
Expand All @@ -193,7 +213,7 @@ defmodule ExMachina.Ecto do
defp recursively_strip(record), do: record

defp handle_assocs(record = %{__struct__: struct}) do
Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) ->
Enum.reduce(struct.__schema__(:associations), record, fn association_name, record ->
case struct.__schema__(:association, association_name) do
%{__struct__: Ecto.Association.BelongsTo} ->
Map.delete(record, association_name)
Expand All @@ -203,7 +223,7 @@ defmodule ExMachina.Ecto do
|> Map.get(association_name)
|> handle_assoc(record, association_name)
end
end
end)
end

defp handle_assoc(original_assoc, record, association_name) do
Expand All @@ -225,7 +245,7 @@ defmodule ExMachina.Ecto do
end

defp handle_embeds(record = %{__struct__: struct}) do
Enum.reduce(struct.__schema__(:embeds), record, fn(embed_name, record) ->
Enum.reduce(struct.__schema__(:embeds), record, fn embed_name, record ->
record
|> Map.get(embed_name)
|> handle_embed(record, embed_name)
Expand All @@ -237,28 +257,34 @@ defmodule ExMachina.Ecto do
%{} ->
embed = recursively_strip(original_embed)
Map.put(record, embed_name, embed)

list when is_list(list) ->
embeds_many = Enum.map(original_embed, &recursively_strip/1)
Map.put(record, embed_name, embeds_many)

nil ->
Map.delete(record, embed_name)
end
end

defp set_persisted_belongs_to_ids(record = %{__struct__: struct}) do
Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) ->
Enum.reduce(struct.__schema__(:associations), record, fn association_name, record ->
association = struct.__schema__(:association, association_name)

case association do
%{__struct__: Ecto.Association.BelongsTo} ->
case Map.get(record, association_name) do
belongs_to = %{__meta__: %{__struct__: Ecto.Schema.Metadata, state: :loaded}} ->
set_belongs_to_primary_key(record, belongs_to, association)
_ -> record

_ ->
record
end
_ -> record

_ ->
record
end
end
end)
end

defp set_belongs_to_primary_key(record, belongs_to, association) do
Expand All @@ -267,14 +293,15 @@ defmodule ExMachina.Ecto do
end

defp insert_belongs_to_assocs(record = %{__struct__: struct}, module) do
Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) ->
Enum.reduce(struct.__schema__(:associations), record, fn association_name, record ->
case struct.__schema__(:association, association_name) do
association = %{__struct__: Ecto.Association.BelongsTo} ->
insert_built_belongs_to_assoc(module, association, record)

_ -> record
_ ->
record
end
end
end)
end

defp insert_built_belongs_to_assoc(module, association, record) do
Expand All @@ -291,7 +318,7 @@ defmodule ExMachina.Ecto do
@doc false
def drop_ecto_fields(record = %{__struct__: struct}) do
record
|> Map.from_struct
|> Map.from_struct()
|> Map.delete(:__meta__)
|> drop_autogenerated_ids(struct)
end
Expand All @@ -308,20 +335,23 @@ defmodule ExMachina.Ecto do

defp drop_fields_with_nil_values(map) do
map
|> Enum.reject(fn({_, value}) -> value == nil end)
|> Enum.reject(fn {_, value} -> value == nil end)
|> Enum.into(%{})
end

defp convert_atom_keys_to_strings(values) when is_list(values) do
Enum.map(values, &convert_atom_keys_to_strings/1)
end

defp convert_atom_keys_to_strings(%{__struct__: _} = record) when is_map(record) do
Map.from_struct(record) |> convert_atom_keys_to_strings()
end

defp convert_atom_keys_to_strings(record) when is_map(record) do
Enum.reduce record, Map.new, fn({key, value}, acc) ->
Enum.reduce(record, Map.new(), fn {key, value}, acc ->
Map.put(acc, to_string(key), convert_atom_keys_to_strings(value))
end
end)
end

defp convert_atom_keys_to_strings(value), do: value
end